From 3c4e2a9cbb8cdd722249fc37a49ae24d07a38011 Mon Sep 17 00:00:00 2001 From: Rodrigo Verdiani Date: Wed, 29 Oct 2025 16:21:39 -0300 Subject: [PATCH] chore(scaffold): add vite base project --- .vite/deps/_metadata.json | 8 + .vite/deps/package.json | 3 + README.md | 73 + api/api.ts | 49 - api/mangamochi.ts | 1737 -- app/globals.css | 122 - app/import-review/page.tsx | 70 - app/layout.tsx | 36 - app/loading.tsx | 3 - app/login/page.tsx | 97 - .../[id]/chapter/[chapterNumber]/page.tsx | 211 - app/manga/[id]/page.tsx | 319 - app/page.tsx | 145 - app/profile/page.tsx | 206 - app/register/page.tsx | 136 - biome.json | 42 + components.json | 39 +- components/auth-header.tsx | 85 - components/failed-import-card.tsx | 134 - components/filter-sidebar.tsx | 224 - components/import-dropdown.tsx | 73 - components/manga-card.tsx | 203 - components/manga-chapter.tsx | 155 - components/manga-dex-import-dialog.tsx | 99 - components/manga-grid.tsx | 17 - components/manga-manual-import-dialog.tsx | 178 - components/manga-pagination.tsx | 90 - components/sort-options.tsx | 132 - components/theme-provider.tsx | 57 - components/theme-toggle.tsx | 25 - components/ui/accordion.tsx | 66 - components/ui/alert-dialog.tsx | 157 - components/ui/alert.tsx | 66 - components/ui/aspect-ratio.tsx | 11 - components/ui/avatar.tsx | 53 - components/ui/badge.tsx | 46 - components/ui/breadcrumb.tsx | 109 - components/ui/button-group.tsx | 83 - components/ui/button.tsx | 60 - components/ui/calendar.tsx | 213 - components/ui/card.tsx | 92 - components/ui/carousel.tsx | 241 - components/ui/chart.tsx | 353 - components/ui/checkbox.tsx | 32 - components/ui/collapsible.tsx | 33 - components/ui/command.tsx | 184 - components/ui/context-menu.tsx | 252 - components/ui/dialog.tsx | 143 - components/ui/drawer.tsx | 135 - components/ui/dropdown-menu.tsx | 257 - components/ui/empty.tsx | 104 - components/ui/field.tsx | 244 - components/ui/form.tsx | 168 - components/ui/hover-card.tsx | 44 - components/ui/input-group.tsx | 169 - components/ui/input-otp.tsx | 77 - components/ui/input.tsx | 21 - components/ui/item.tsx | 193 - components/ui/kbd.tsx | 28 - components/ui/label.tsx | 24 - components/ui/menubar.tsx | 276 - components/ui/navigation-menu.tsx | 166 - components/ui/pagination.tsx | 127 - components/ui/popover.tsx | 48 - components/ui/progress.tsx | 31 - components/ui/radio-group.tsx | 45 - components/ui/resizable.tsx | 56 - components/ui/scroll-area.tsx | 58 - components/ui/select.tsx | 185 - components/ui/separator.tsx | 28 - components/ui/sheet.tsx | 139 - components/ui/sidebar.tsx | 726 - components/ui/skeleton.tsx | 13 - components/ui/slider.tsx | 63 - components/ui/sonner.tsx | 40 - components/ui/spinner.tsx | 16 - components/ui/switch.tsx | 31 - components/ui/table.tsx | 116 - components/ui/tabs.tsx | 66 - components/ui/textarea.tsx | 18 - components/ui/toast.tsx | 129 - components/ui/toaster.tsx | 35 - components/ui/toggle-group.tsx | 73 - components/ui/toggle.tsx | 47 - components/ui/tooltip.tsx | 61 - components/ui/use-mobile.tsx | 21 - components/ui/use-toast.ts | 191 - contexts/auth-context.tsx | 137 - eslint.config.js | 23 + hooks/use-mobile.ts | 21 - hooks/use-reading-tracker.ts | 52 - hooks/use-toast.ts | 191 - index.html | 13 + next.config.mjs | 14 - orval.config.ts | 41 +- package-lock.json | 16515 +++++++--------- package.json | 115 +- pnpm-lock.yaml | 8030 -------- postcss.config.mjs | 8 - public/placeholder-logo.png | Bin 568 -> 0 bytes public/placeholder-logo.svg | 1 - public/placeholder-user.jpg | Bin 1635 -> 0 bytes public/placeholder.jpg | Bin 1064 -> 0 bytes public/placeholder.svg | 1 - public/vite.svg | 1 + src/App.tsx | 3 + src/api/api.ts | 49 + src/api/generated/api.schemas.ts | 249 + src/api/generated/auth/auth.ts | 202 + .../dev-controller/dev-controller.ts | 101 + .../favorite-mangas/favorite-mangas.ts | 193 + src/api/generated/genre/genre.ts | 158 + .../generated/manga-chapter/manga-chapter.ts | 554 + .../manga-import-review.ts | 350 + .../generated/manga-import/manga-import.ts | 208 + src/api/generated/manga/manga.ts | 588 + src/components/ui/input.tsx | 21 - src/components/ui/sonner.tsx | 38 + src/contexts/AuthContext.tsx | 127 + src/index.css | 120 + {lib => src/lib}/utils.ts | 4 +- src/main.tsx | 10 + styles/globals.css | 125 - tsconfig.app.json | 32 + tsconfig.json | 36 +- tsconfig.node.json | 26 + vite.config.ts | 13 + 127 files changed, 10926 insertions(+), 28675 deletions(-) create mode 100644 .vite/deps/_metadata.json create mode 100644 .vite/deps/package.json create mode 100644 README.md delete mode 100644 api/api.ts delete mode 100644 api/mangamochi.ts delete mode 100644 app/globals.css delete mode 100644 app/import-review/page.tsx delete mode 100644 app/layout.tsx delete mode 100644 app/loading.tsx delete mode 100644 app/login/page.tsx delete mode 100644 app/manga/[id]/chapter/[chapterNumber]/page.tsx delete mode 100644 app/manga/[id]/page.tsx delete mode 100644 app/page.tsx delete mode 100644 app/profile/page.tsx delete mode 100644 app/register/page.tsx create mode 100644 biome.json delete mode 100644 components/auth-header.tsx delete mode 100644 components/failed-import-card.tsx delete mode 100644 components/filter-sidebar.tsx delete mode 100644 components/import-dropdown.tsx delete mode 100644 components/manga-card.tsx delete mode 100644 components/manga-chapter.tsx delete mode 100644 components/manga-dex-import-dialog.tsx delete mode 100644 components/manga-grid.tsx delete mode 100644 components/manga-manual-import-dialog.tsx delete mode 100644 components/manga-pagination.tsx delete mode 100644 components/sort-options.tsx delete mode 100644 components/theme-provider.tsx delete mode 100644 components/theme-toggle.tsx delete mode 100644 components/ui/accordion.tsx delete mode 100644 components/ui/alert-dialog.tsx delete mode 100644 components/ui/alert.tsx delete mode 100644 components/ui/aspect-ratio.tsx delete mode 100644 components/ui/avatar.tsx delete mode 100644 components/ui/badge.tsx delete mode 100644 components/ui/breadcrumb.tsx delete mode 100644 components/ui/button-group.tsx delete mode 100644 components/ui/button.tsx delete mode 100644 components/ui/calendar.tsx delete mode 100644 components/ui/card.tsx delete mode 100644 components/ui/carousel.tsx delete mode 100644 components/ui/chart.tsx delete mode 100644 components/ui/checkbox.tsx delete mode 100644 components/ui/collapsible.tsx delete mode 100644 components/ui/command.tsx delete mode 100644 components/ui/context-menu.tsx delete mode 100644 components/ui/dialog.tsx delete mode 100644 components/ui/drawer.tsx delete mode 100644 components/ui/dropdown-menu.tsx delete mode 100644 components/ui/empty.tsx delete mode 100644 components/ui/field.tsx delete mode 100644 components/ui/form.tsx delete mode 100644 components/ui/hover-card.tsx delete mode 100644 components/ui/input-group.tsx delete mode 100644 components/ui/input-otp.tsx delete mode 100644 components/ui/input.tsx delete mode 100644 components/ui/item.tsx delete mode 100644 components/ui/kbd.tsx delete mode 100644 components/ui/label.tsx delete mode 100644 components/ui/menubar.tsx delete mode 100644 components/ui/navigation-menu.tsx delete mode 100644 components/ui/pagination.tsx delete mode 100644 components/ui/popover.tsx delete mode 100644 components/ui/progress.tsx delete mode 100644 components/ui/radio-group.tsx delete mode 100644 components/ui/resizable.tsx delete mode 100644 components/ui/scroll-area.tsx delete mode 100644 components/ui/select.tsx delete mode 100644 components/ui/separator.tsx delete mode 100644 components/ui/sheet.tsx delete mode 100644 components/ui/sidebar.tsx delete mode 100644 components/ui/skeleton.tsx delete mode 100644 components/ui/slider.tsx delete mode 100644 components/ui/sonner.tsx delete mode 100644 components/ui/spinner.tsx delete mode 100644 components/ui/switch.tsx delete mode 100644 components/ui/table.tsx delete mode 100644 components/ui/tabs.tsx delete mode 100644 components/ui/textarea.tsx delete mode 100644 components/ui/toast.tsx delete mode 100644 components/ui/toaster.tsx delete mode 100644 components/ui/toggle-group.tsx delete mode 100644 components/ui/toggle.tsx delete mode 100644 components/ui/tooltip.tsx delete mode 100644 components/ui/use-mobile.tsx delete mode 100644 components/ui/use-toast.ts delete mode 100644 contexts/auth-context.tsx create mode 100644 eslint.config.js delete mode 100644 hooks/use-mobile.ts delete mode 100644 hooks/use-reading-tracker.ts delete mode 100644 hooks/use-toast.ts create mode 100644 index.html delete mode 100644 next.config.mjs delete mode 100644 pnpm-lock.yaml delete mode 100644 postcss.config.mjs delete mode 100644 public/placeholder-logo.png delete mode 100644 public/placeholder-logo.svg delete mode 100644 public/placeholder-user.jpg delete mode 100644 public/placeholder.jpg delete mode 100644 public/placeholder.svg create mode 100644 public/vite.svg create mode 100644 src/App.tsx create mode 100644 src/api/api.ts create mode 100644 src/api/generated/api.schemas.ts create mode 100644 src/api/generated/auth/auth.ts create mode 100644 src/api/generated/dev-controller/dev-controller.ts create mode 100644 src/api/generated/favorite-mangas/favorite-mangas.ts create mode 100644 src/api/generated/genre/genre.ts create mode 100644 src/api/generated/manga-chapter/manga-chapter.ts create mode 100644 src/api/generated/manga-import-review/manga-import-review.ts create mode 100644 src/api/generated/manga-import/manga-import.ts create mode 100644 src/api/generated/manga/manga.ts delete mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/sonner.tsx create mode 100644 src/contexts/AuthContext.tsx create mode 100644 src/index.css rename {lib => src/lib}/utils.ts (53%) create mode 100644 src/main.tsx delete mode 100644 styles/globals.css create mode 100644 tsconfig.app.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.vite/deps/_metadata.json b/.vite/deps/_metadata.json new file mode 100644 index 0000000..6a3e23c --- /dev/null +++ b/.vite/deps/_metadata.json @@ -0,0 +1,8 @@ +{ + "hash": "d39447c8", + "configHash": "f0629bb7", + "lockfileHash": "e3b0c442", + "browserHash": "18b7c892", + "optimized": {}, + "chunks": {} +} diff --git a/.vite/deps/package.json b/.vite/deps/package.json new file mode 100644 index 0000000..bedb411 --- /dev/null +++ b/.vite/deps/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2e7761 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/api/api.ts b/api/api.ts deleted file mode 100644 index 8fefde1..0000000 --- a/api/api.ts +++ /dev/null @@ -1,49 +0,0 @@ -import axios, { AxiosRequestConfig, isAxiosError } from "axios"; -import { User } from "@/contexts/auth-context"; -import { toast } from "sonner"; - -export const Api = axios.create({ - baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, - responseType: "json", -}); - -interface ErrorResponseDTO { - timestamp: string; - message: string; -} - -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}`; - } - - return config; -}); - -Api.interceptors.response.use( - (response) => response, - (error) => { - if (!isAxiosError(error)) { - console.log(error); - return; - } - - const errorDTO = error.response?.data as ErrorResponseDTO; - toast.error(errorDTO.message); - }, -); - -export const customInstance = ( - config: AxiosRequestConfig, - options?: AxiosRequestConfig, -): Promise => { - const source = axios.CancelToken.source(); - return Api({ - ...config, - ...options, - cancelToken: source.token, - paramsSerializer: { indexes: null }, - }).then(({ data }) => data); -}; diff --git a/api/mangamochi.ts b/api/mangamochi.ts deleted file mode 100644 index 0372a64..0000000 --- a/api/mangamochi.ts +++ /dev/null @@ -1,1737 +0,0 @@ -/** - * Generated by orval v7.14.0 🍺 - * Do not edit manually. - * OpenAPI definition - * OpenAPI spec version: v0 - */ -import { - useMutation, - useQuery -} from '@tanstack/react-query'; -import type { - DataTag, - DefinedInitialDataOptions, - DefinedUseQueryResult, - MutationFunction, - QueryClient, - QueryFunction, - QueryKey, - UndefinedInitialDataOptions, - UseMutationOptions, - UseMutationResult, - UseQueryOptions, - UseQueryResult -} from '@tanstack/react-query'; - -import { customInstance } from './api'; -export interface UpdateMangaDataCommand { - mangaId?: number; -} - -export interface DefaultResponseDTOVoid { - timestamp?: string; - data?: unknown; - message?: string; -} - -export interface ImportMangaDexRequestDTO { - id: string; -} - -export interface DefaultResponseDTOImportMangaDexResponseDTO { - timestamp?: string; - data?: ImportMangaDexResponseDTO; - message?: string; -} - -export interface ImportMangaDexResponseDTO { - id: number; -} - -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 DefaultResponseDTOAuthenticationResponseDTO { - timestamp?: string; - data?: AuthenticationResponseDTO; - message?: string; -} - -export interface DefaultResponseDTOPageMangaListDTO { - timestamp?: string; - data?: PageMangaListDTO; - message?: string; -} - -export interface MangaListDTO { - id: number; - /** @minLength 1 */ - title: string; - coverImageKey?: string; - status?: string; - publishedFrom?: string; - publishedTo?: string; - providerCount?: number; - genres: string[]; - authors: string[]; - score: number; - favorite: boolean; -} - -export interface PageMangaListDTO { - totalPages?: number; - totalElements?: number; - size?: number; - content?: MangaListDTO[]; - number?: number; - pageable?: PageableObject; - first?: boolean; - last?: boolean; - sort?: SortObject; - numberOfElements?: number; - empty?: boolean; -} - -export interface PageableObject { - offset?: number; - paged?: boolean; - pageNumber?: number; - pageSize?: number; - sort?: SortObject; - unpaged?: boolean; -} - -export interface SortObject { - empty?: boolean; - sorted?: boolean; - unsorted?: boolean; -} - -export interface DefaultResponseDTOListMangaChapterDTO { - timestamp?: string; - data?: MangaChapterDTO[]; - message?: string; -} - -export interface MangaChapterDTO { - id: number; - /** @minLength 1 */ - title: string; - downloaded: boolean; - isRead: boolean; -} - -export interface DefaultResponseDTOMangaDTO { - timestamp?: string; - data?: MangaDTO; - message?: string; -} - -export interface MangaDTO { - id: number; - /** @minLength 1 */ - title: string; - coverImageKey?: string; - status?: string; - publishedFrom?: string; - publishedTo?: string; - synopsis?: string; - providerCount?: number; - alternativeTitles: string[]; - genres: string[]; - authors: string[]; - score: number; - providers: MangaProviderDTO[]; - chapterCount: number; -} - -export interface MangaProviderDTO { - id: number; - /** @minLength 1 */ - providerName: string; - chaptersAvailable: number; - chaptersDownloaded: number; - supportsChapterFetch: boolean; -} - -export interface DefaultResponseDTOMangaChapterImagesDTO { - timestamp?: string; - data?: MangaChapterImagesDTO; - message?: string; -} - -export interface MangaChapterImagesDTO { - id: number; - /** @minLength 1 */ - mangaTitle: string; - chapterImageKeys: string[]; -} - -export interface DefaultResponseDTOListImportReviewDTO { - timestamp?: string; - data?: ImportReviewDTO[]; - message?: string; -} - -export interface ImportReviewDTO { - id: number; - /** @minLength 1 */ - title: string; - /** @minLength 1 */ - providerName: string; - externalUrl?: string; - /** @minLength 1 */ - reason: string; - createdAt: string; -} - -export interface DefaultResponseDTOListGenreDTO { - timestamp?: string; - data?: GenreDTO[]; - message?: string; -} - -export interface GenreDTO { - id: number; - /** @minLength 1 */ - name: string; -} - -export type DownloadChapterArchiveParams = { -archiveFileType: DownloadChapterArchiveArchiveFileType; -}; - -export type DownloadChapterArchiveArchiveFileType = typeof DownloadChapterArchiveArchiveFileType[keyof typeof DownloadChapterArchiveArchiveFileType]; - - -// eslint-disable-next-line @typescript-eslint/no-redeclare -export const DownloadChapterArchiveArchiveFileType = { - CBZ: 'CBZ', - CBR: 'CBR', -} as const; - -export type ImportMultipleFilesBody = { - /** @minLength 1 */ - malId: string; - /** List of files to upload */ - files: Blob[]; -}; - -export type ResolveImportReviewParams = { -importReviewId: number; -malId: string; -}; - -export type GetMangasParams = { -searchQuery?: string; -genreIds?: number[]; -statuses?: string[]; -userFavorites?: boolean; -score?: number; -/** - * 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]; - - - -export const sendRecord = ( - updateMangaDataCommand: UpdateMangaDataCommand, - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/records`, method: 'POST', - headers: {'Content-Type': 'application/json', }, - data: updateMangaDataCommand, signal - }, - options); - } - - - -export const getSendRecordMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: UpdateMangaDataCommand}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{data: UpdateMangaDataCommand}, TContext> => { - -const mutationKey = ['sendRecord']; -const {mutation: mutationOptions, request: requestOptions} = options ? - options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? - options - : {...options, mutation: {...options.mutation, mutationKey}} - : {mutation: { mutationKey, }, request: undefined}; - - - - - const mutationFn: MutationFunction>, {data: UpdateMangaDataCommand}> = (props) => { - const {data} = props ?? {}; - - return sendRecord(data,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type SendRecordMutationResult = NonNullable>> - export type SendRecordMutationBody = UpdateMangaDataCommand - export type SendRecordMutationError = unknown - - export const useSendRecord = (options?: { mutation?:UseMutationOptions>, TError,{data: UpdateMangaDataCommand}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {data: UpdateMangaDataCommand}, - TContext - > => { - - const mutationOptions = getSendRecordMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - -/** - * 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 -) => { - - - return customInstance( - {url: `/mangas/${encodeURIComponent(String(mangaProviderId))}/fetch-chapters`, method: 'POST', signal - }, - options); - } - - - -export const getFetchMangaChaptersMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{mangaProviderId: number}, TContext>, request?: SecondParameter} -): UseMutationOptions>, 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}; - - - - - const mutationFn: MutationFunction>, {mangaProviderId: number}> = (props) => { - const {mangaProviderId} = props ?? {}; - - 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 - > => { - - const mutationOptions = getFetchMangaChaptersMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - -/** - * Fetch all not yet downloaded chapters from the provider - * @summary Fetch all chapters - */ -export const fetchAllChapters = ( - mangaProviderId: number, - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/mangas/${encodeURIComponent(String(mangaProviderId))}/fetch-all-chapters`, method: 'POST', signal - }, - options); - } - - - -export const getFetchAllChaptersMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{mangaProviderId: number}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{mangaProviderId: number}, TContext> => { - -const mutationKey = ['fetchAllChapters']; -const {mutation: mutationOptions, request: requestOptions} = options ? - options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? - options - : {...options, mutation: {...options.mutation, mutationKey}} - : {mutation: { mutationKey, }, request: undefined}; - - - - - const mutationFn: MutationFunction>, {mangaProviderId: number}> = (props) => { - const {mangaProviderId} = props ?? {}; - - return fetchAllChapters(mangaProviderId,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type FetchAllChaptersMutationResult = NonNullable>> - - export type FetchAllChaptersMutationError = unknown - - /** - * @summary Fetch all chapters - */ -export const useFetchAllChapters = (options?: { mutation?:UseMutationOptions>, TError,{mangaProviderId: number}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {mangaProviderId: number}, - TContext - > => { - - const mutationOptions = getFetchAllChaptersMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - -/** - * Remove a manga from favorites for the logged user. - * @summary Unfavorite a manga - */ -export const setUnfavorite = ( - id: number, - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/mangas/${encodeURIComponent(String(id))}/unfavorite`, method: 'POST', signal - }, - options); - } - - - -export const getSetUnfavoriteMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{id: number}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{id: number}, TContext> => { - -const mutationKey = ['setUnfavorite']; -const {mutation: mutationOptions, request: requestOptions} = options ? - options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? - options - : {...options, mutation: {...options.mutation, mutationKey}} - : {mutation: { mutationKey, }, request: undefined}; - - - - - const mutationFn: MutationFunction>, {id: number}> = (props) => { - const {id} = props ?? {}; - - return setUnfavorite(id,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type SetUnfavoriteMutationResult = NonNullable>> - - export type SetUnfavoriteMutationError = unknown - - /** - * @summary Unfavorite a manga - */ -export const useSetUnfavorite = (options?: { mutation?:UseMutationOptions>, TError,{id: number}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {id: number}, - TContext - > => { - - const mutationOptions = getSetUnfavoriteMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - -/** - * Set a manga as favorite for the logged user. - * @summary Favorite a manga - */ -export const setFavorite = ( - id: number, - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/mangas/${encodeURIComponent(String(id))}/favorite`, method: 'POST', signal - }, - options); - } - - - -export const getSetFavoriteMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{id: number}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{id: number}, TContext> => { - -const mutationKey = ['setFavorite']; -const {mutation: mutationOptions, request: requestOptions} = options ? - options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? - options - : {...options, mutation: {...options.mutation, mutationKey}} - : {mutation: { mutationKey, }, request: undefined}; - - - - - const mutationFn: MutationFunction>, {id: number}> = (props) => { - const {id} = props ?? {}; - - return setFavorite(id,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type SetFavoriteMutationResult = NonNullable>> - - export type SetFavoriteMutationError = unknown - - /** - * @summary Favorite a manga - */ -export const useSetFavorite = (options?: { mutation?:UseMutationOptions>, TError,{id: number}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {id: number}, - TContext - > => { - - const mutationOptions = getSetFavoriteMutationOptions(options); - - 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 -) => { - - - return customInstance( - {url: `/mangas/chapters/${encodeURIComponent(String(chapterId))}/mark-as-read`, method: 'POST', signal - }, - options); - } - - - -export const getMarkAsReadMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{chapterId: number}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{chapterId: number}, TContext> => { - -const mutationKey = ['markAsRead']; -const {mutation: mutationOptions, request: requestOptions} = options ? - options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? - options - : {...options, mutation: {...options.mutation, mutationKey}} - : {mutation: { mutationKey, }, request: undefined}; - - - - - const mutationFn: MutationFunction>, {chapterId: number}> = (props) => { - const {chapterId} = props ?? {}; - - 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 - > => { - - const mutationOptions = getMarkAsReadMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - -/** - * Fetch the chapter from the provider - * @summary Fetch chapter - */ -export const fetchChapter = ( - chapterId: number, - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/mangas/chapters/${encodeURIComponent(String(chapterId))}/fetch`, method: 'POST', signal - }, - options); - } - - - -export const getFetchChapterMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{chapterId: number}, TContext>, request?: SecondParameter} -): UseMutationOptions>, 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}; - - - - - const mutationFn: MutationFunction>, {chapterId: number}> = (props) => { - const {chapterId} = props ?? {}; - - 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 - > => { - - const mutationOptions = getFetchChapterMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - -/** - * Download a chapter as a compressed file by its ID. - * @summary Download chapter archive - */ -export const downloadChapterArchive = ( - chapterId: number, - params: DownloadChapterArchiveParams, - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/mangas/chapters/${encodeURIComponent(String(chapterId))}/download`, method: 'POST', - params, - responseType: 'blob', signal - }, - options); - } - - - -export const getDownloadChapterArchiveMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{chapterId: number;params: DownloadChapterArchiveParams}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{chapterId: number;params: DownloadChapterArchiveParams}, TContext> => { - -const mutationKey = ['downloadChapterArchive']; -const {mutation: mutationOptions, request: requestOptions} = options ? - options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? - options - : {...options, mutation: {...options.mutation, mutationKey}} - : {mutation: { mutationKey, }, request: undefined}; - - - - - const mutationFn: MutationFunction>, {chapterId: number;params: DownloadChapterArchiveParams}> = (props) => { - const {chapterId,params} = props ?? {}; - - 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 - > => { - - const mutationOptions = getDownloadChapterArchiveMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - -/** - * Accepts multiple files via multipart/form-data and processes them. - * @summary Upload multiple files - */ -export const importMultipleFiles = ( - importMultipleFilesBody: ImportMultipleFilesBody, - options?: SecondParameter,signal?: AbortSignal -) => { - - const formData = new FormData(); -formData.append(`malId`, importMultipleFilesBody.malId) -importMultipleFilesBody.files.forEach(value => formData.append(`files`, value)); - - return customInstance( - {url: `/manga/import/upload`, method: 'POST', - headers: {'Content-Type': 'multipart/form-data', }, - data: formData, signal - }, - options); - } - - - -export const getImportMultipleFilesMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: ImportMultipleFilesBody}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{data: ImportMultipleFilesBody}, TContext> => { - -const mutationKey = ['importMultipleFiles']; -const {mutation: mutationOptions, request: requestOptions} = options ? - options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? - options - : {...options, mutation: {...options.mutation, mutationKey}} - : {mutation: { mutationKey, }, request: undefined}; - - - - - const mutationFn: MutationFunction>, {data: ImportMultipleFilesBody}> = (props) => { - const {data} = props ?? {}; - - return importMultipleFiles(data,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type ImportMultipleFilesMutationResult = NonNullable>> - export type ImportMultipleFilesMutationBody = ImportMultipleFilesBody - export type ImportMultipleFilesMutationError = unknown - - /** - * @summary Upload multiple files - */ -export const useImportMultipleFiles = (options?: { mutation?:UseMutationOptions>, TError,{data: ImportMultipleFilesBody}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {data: ImportMultipleFilesBody}, - TContext - > => { - - const mutationOptions = getImportMultipleFilesMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - -/** - * Get list of pending import reviews. - * @summary Get list of pending import reviews - */ -export const getImportReviews = ( - - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/manga/import/review`, method: 'GET', signal - }, - options); - } - - - - -export const getGetImportReviewsQueryKey = () => { - return [ - `/manga/import/review` - ] as const; - } - - -export const getGetImportReviewsQueryOptions = >, TError = unknown>( options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} -) => { - -const {query: queryOptions, request: requestOptions} = options ?? {}; - - const queryKey = queryOptions?.queryKey ?? getGetImportReviewsQueryKey(); - - - - const queryFn: QueryFunction>> = ({ signal }) => getImportReviews(requestOptions, signal); - - - - - - return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: DataTag } -} - -export type GetImportReviewsQueryResult = NonNullable>> -export type GetImportReviewsQueryError = unknown - - -export function useGetImportReviews>, TError = unknown>( - options: { query:Partial>, TError, TData>> & Pick< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): DefinedUseQueryResult & { queryKey: DataTag } -export function useGetImportReviews>, TError = unknown>( - options?: { query?:Partial>, TError, TData>> & Pick< - UndefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } -export function useGetImportReviews>, TError = unknown>( - options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } -/** - * @summary Get list of pending import reviews - */ - -export function useGetImportReviews>, TError = unknown>( - options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } { - - const queryOptions = getGetImportReviewsQueryOptions(options) - - const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; - - query.queryKey = queryOptions.queryKey ; - - return query; -} - - - - -/** - * Resolve import review by ID. - * @summary Resolve import review - */ -export const resolveImportReview = ( - params: ResolveImportReviewParams, - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/manga/import/review`, method: 'POST', - params, signal - }, - options); - } - - - -export const getResolveImportReviewMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{params: ResolveImportReviewParams}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{params: ResolveImportReviewParams}, TContext> => { - -const mutationKey = ['resolveImportReview']; -const {mutation: mutationOptions, request: requestOptions} = options ? - options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? - options - : {...options, mutation: {...options.mutation, mutationKey}} - : {mutation: { mutationKey, }, request: undefined}; - - - - - const mutationFn: MutationFunction>, {params: ResolveImportReviewParams}> = (props) => { - const {params} = props ?? {}; - - return resolveImportReview(params,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type ResolveImportReviewMutationResult = NonNullable>> - - export type ResolveImportReviewMutationError = unknown - - /** - * @summary Resolve import review - */ -export const useResolveImportReview = (options?: { mutation?:UseMutationOptions>, TError,{params: ResolveImportReviewParams}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {params: ResolveImportReviewParams}, - TContext - > => { - - const mutationOptions = getResolveImportReviewMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - -/** - * Imports manga data from MangaDex into the local database. - * @summary Import manga from MangaDex - */ -export const importFromMangaDex = ( - importMangaDexRequestDTO: ImportMangaDexRequestDTO, - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/manga/import/manga-dex`, method: 'POST', - headers: {'Content-Type': 'application/json', }, - data: importMangaDexRequestDTO, signal - }, - options); - } - - - -export const getImportFromMangaDexMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: ImportMangaDexRequestDTO}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{data: ImportMangaDexRequestDTO}, TContext> => { - -const mutationKey = ['importFromMangaDex']; -const {mutation: mutationOptions, request: requestOptions} = options ? - options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? - options - : {...options, mutation: {...options.mutation, mutationKey}} - : {mutation: { mutationKey, }, request: undefined}; - - - - - const mutationFn: MutationFunction>, {data: ImportMangaDexRequestDTO}> = (props) => { - const {data} = props ?? {}; - - return importFromMangaDex(data,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type ImportFromMangaDexMutationResult = NonNullable>> - export type ImportFromMangaDexMutationBody = ImportMangaDexRequestDTO - export type ImportFromMangaDexMutationError = unknown - - /** - * @summary Import manga from MangaDex - */ -export const useImportFromMangaDex = (options?: { mutation?:UseMutationOptions>, TError,{data: ImportMangaDexRequestDTO}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {data: ImportMangaDexRequestDTO}, - TContext - > => { - - const mutationOptions = getImportFromMangaDexMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - -/** - * Register a new user. - * @summary Register user - */ -export const registerUser = ( - registrationRequestDTO: RegistrationRequestDTO, - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/auth/register`, method: 'POST', - headers: {'Content-Type': 'application/json', }, - data: registrationRequestDTO, signal - }, - options); - } - - - -export const getRegisterUserMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: RegistrationRequestDTO}, TContext>, request?: SecondParameter} -): UseMutationOptions>, 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}; - - - - - const mutationFn: MutationFunction>, {data: RegistrationRequestDTO}> = (props) => { - const {data} = props ?? {}; - - return registerUser(data,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type RegisterUserMutationResult = NonNullable>> - export type RegisterUserMutationBody = RegistrationRequestDTO - export type RegisterUserMutationError = unknown - - /** - * @summary Register user - */ -export const useRegisterUser = (options?: { mutation?:UseMutationOptions>, TError,{data: RegistrationRequestDTO}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {data: RegistrationRequestDTO}, - TContext - > => { - - const mutationOptions = getRegisterUserMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - -/** - * Authenticate user with email and password. - * @summary Authenticate user - */ -export const authenticateUser = ( - authenticationRequestDTO: AuthenticationRequestDTO, - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/auth/login`, method: 'POST', - headers: {'Content-Type': 'application/json', }, - data: authenticationRequestDTO, signal - }, - options); - } - - - -export const getAuthenticateUserMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: AuthenticationRequestDTO}, TContext>, request?: SecondParameter} -): UseMutationOptions>, 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}; - - - - - const mutationFn: MutationFunction>, {data: AuthenticationRequestDTO}> = (props) => { - const {data} = props ?? {}; - - return authenticateUser(data,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type AuthenticateUserMutationResult = NonNullable>> - export type AuthenticateUserMutationBody = AuthenticationRequestDTO - export type AuthenticateUserMutationError = unknown - - /** - * @summary Authenticate user - */ -export const useAuthenticateUser = (options?: { mutation?:UseMutationOptions>, TError,{data: AuthenticationRequestDTO}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {data: AuthenticationRequestDTO}, - TContext - > => { - - const mutationOptions = getAuthenticateUserMutationOptions(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 -) => { - - - return customInstance( - {url: `/mangas`, method: 'GET', - params, signal - }, - options); - } - - - - -export const getGetMangasQueryKey = (params?: GetMangasParams,) => { - return [ - `/mangas`, ...(params ? [params]: []) - ] as const; - } - - -export const getGetMangasQueryOptions = >, TError = unknown>(params?: GetMangasParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} -) => { - -const {query: queryOptions, request: requestOptions} = options ?? {}; - - const queryKey = queryOptions?.queryKey ?? getGetMangasQueryKey(params); - - - - const queryFn: QueryFunction>> = ({ signal }) => getMangas(params, requestOptions, signal); - - - - - - 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< - 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< - 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 } -/** - * @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 } { - - const queryOptions = getGetMangasQueryOptions(params,options) - - const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; - - 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 -) => { - - - return customInstance( - {url: `/mangas/${encodeURIComponent(String(mangaProviderId))}/chapters`, method: 'GET', signal - }, - options); - } - - - - -export const getGetMangaChaptersQueryKey = (mangaProviderId?: number,) => { - return [ - `/mangas/${mangaProviderId}/chapters` - ] as const; - } - - -export const getGetMangaChaptersQueryOptions = >, TError = unknown>(mangaProviderId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} -) => { - -const {query: queryOptions, request: requestOptions} = options ?? {}; - - const queryKey = queryOptions?.queryKey ?? getGetMangaChaptersQueryKey(mangaProviderId); - - - - const queryFn: QueryFunction>> = ({ signal }) => getMangaChapters(mangaProviderId, requestOptions, signal); - - - - - - 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< - 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< - 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 } -/** - * @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 } { - - const queryOptions = getGetMangaChaptersQueryOptions(mangaProviderId,options) - - const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; - - query.queryKey = queryOptions.queryKey ; - - return query; -} - - - - -/** - * Get the details of a manga by its ID - * @summary Get the details of a manga - */ -export const getManga = ( - mangaId: number, - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/mangas/${encodeURIComponent(String(mangaId))}`, method: 'GET', signal - }, - options); - } - - - - -export const getGetMangaQueryKey = (mangaId?: number,) => { - return [ - `/mangas/${mangaId}` - ] as const; - } - - -export const getGetMangaQueryOptions = >, TError = unknown>(mangaId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} -) => { - -const {query: queryOptions, request: requestOptions} = options ?? {}; - - 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 } -} - -export type GetMangaQueryResult = NonNullable>> -export type GetMangaQueryError = unknown - - -export function useGetManga>, TError = unknown>( - mangaId: number, options: { query:Partial>, 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< - 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 } -/** - * @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 } { - - const queryOptions = getGetMangaQueryOptions(mangaId,options) - - const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; - - query.queryKey = queryOptions.queryKey ; - - return query; -} - - - - -/** - * Retrieve a list of manga chapter images for a specific manga/provider combination. - * @summary Get the images for a specific manga/provider combination - */ -export const getMangaChapterImages = ( - chapterId: number, - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/mangas/chapters/${encodeURIComponent(String(chapterId))}/images`, method: 'GET', signal - }, - options); - } - - - - -export const getGetMangaChapterImagesQueryKey = (chapterId?: number,) => { - return [ - `/mangas/chapters/${chapterId}/images` - ] as const; - } - - -export const getGetMangaChapterImagesQueryOptions = >, TError = unknown>(chapterId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} -) => { - -const {query: queryOptions, request: requestOptions} = options ?? {}; - - const queryKey = queryOptions?.queryKey ?? getGetMangaChapterImagesQueryKey(chapterId); - - - - const queryFn: QueryFunction>> = ({ signal }) => getMangaChapterImages(chapterId, requestOptions, signal); - - - - - - 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< - 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< - 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 } -/** - * @summary Get the images 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 } { - - const queryOptions = getGetMangaChapterImagesQueryOptions(chapterId,options) - - const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; - - query.queryKey = queryOptions.queryKey ; - - return query; -} - - - - -/** - * Retrieve a list of genres. - * @summary Get a list of genres - */ -export const getGenres = ( - - options?: SecondParameter,signal?: AbortSignal -) => { - - - return customInstance( - {url: `/genres`, method: 'GET', signal - }, - options); - } - - - - -export const getGetGenresQueryKey = () => { - return [ - `/genres` - ] as const; - } - - -export const getGetGenresQueryOptions = >, TError = unknown>( options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} -) => { - -const {query: queryOptions, request: requestOptions} = options ?? {}; - - const queryKey = queryOptions?.queryKey ?? getGetGenresQueryKey(); - - - - const queryFn: QueryFunction>> = ({ signal }) => getGenres(requestOptions, signal); - - - - - - 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< - DefinedInitialDataOptions< - Awaited>, - TError, - Awaited> - > , 'initialData' - >, request?: SecondParameter} - , queryClient?: QueryClient - ): DefinedUseQueryResult & { queryKey: DataTag } -export function useGetGenres>, TError = unknown>( - options?: { query?:Partial>, 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 } -/** - * @summary Get a list of genres - */ - -export function useGetGenres>, TError = unknown>( - options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} - , queryClient?: QueryClient - ): UseQueryResult & { queryKey: DataTag } { - - const queryOptions = getGetGenresQueryOptions(options) - - const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; - - query.queryKey = queryOptions.queryKey ; - - return query; -} - - - - -/** - * Delete pending import review by ID. - * @summary Delete pending import review - */ -export const deleteImportReview = ( - id: number, - options?: SecondParameter,) => { - - - return customInstance( - {url: `/manga/import/review/${encodeURIComponent(String(id))}`, method: 'DELETE' - }, - options); - } - - - -export const getDeleteImportReviewMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{id: number}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{id: number}, TContext> => { - -const mutationKey = ['deleteImportReview']; -const {mutation: mutationOptions, request: requestOptions} = options ? - options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? - options - : {...options, mutation: {...options.mutation, mutationKey}} - : {mutation: { mutationKey, }, request: undefined}; - - - - - const mutationFn: MutationFunction>, {id: number}> = (props) => { - const {id} = props ?? {}; - - return deleteImportReview(id,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type DeleteImportReviewMutationResult = NonNullable>> - - export type DeleteImportReviewMutationError = unknown - - /** - * @summary Delete pending import review - */ -export const useDeleteImportReview = (options?: { mutation?:UseMutationOptions>, TError,{id: number}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {id: number}, - TContext - > => { - - const mutationOptions = getDeleteImportReviewMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } diff --git a/app/globals.css b/app/globals.css deleted file mode 100644 index 08aa787..0000000 --- a/app/globals.css +++ /dev/null @@ -1,122 +0,0 @@ -@import "tailwindcss"; -@import "tw-animate-css"; - -@custom-variant dark (&:is(.dark *)); - -:root { - /* New elegant dark theme inspired by Vercel's design system */ - --background: oklch(0.11 0.005 264); - --foreground: oklch(0.95 0.005 264); - --card: oklch(0.14 0.005 264); - --card-foreground: oklch(0.95 0.005 264); - --popover: oklch(0.14 0.005 264); - --popover-foreground: oklch(0.95 0.005 264); - --primary: oklch(0.65 0.25 264); - --primary-foreground: oklch(0.98 0.005 264); - --secondary: oklch(0.18 0.005 264); - --secondary-foreground: oklch(0.95 0.005 264); - --muted: oklch(0.18 0.005 264); - --muted-foreground: oklch(0.55 0.005 264); - --accent: oklch(0.65 0.25 264); - --accent-foreground: oklch(0.98 0.005 264); - --destructive: oklch(0.55 0.22 25); - --destructive-foreground: oklch(0.98 0.005 264); - --border: oklch(0.22 0.005 264); - --input: oklch(0.22 0.005 264); - --ring: oklch(0.65 0.25 264); - --radius: 0.5rem; - --chart-1: oklch(0.65 0.25 264); - --chart-2: oklch(0.7 0.2 180); - --chart-3: oklch(0.75 0.18 85); - --chart-4: oklch(0.62 0.26 305); - --chart-5: oklch(0.64 0.24 20); - --sidebar: oklch(0.11 0.005 264); - --sidebar-foreground: oklch(0.95 0.005 264); - --sidebar-primary: oklch(0.65 0.25 264); - --sidebar-primary-foreground: oklch(0.98 0.005 264); - --sidebar-accent: oklch(0.18 0.005 264); - --sidebar-accent-foreground: oklch(0.95 0.005 264); - --sidebar-border: oklch(0.22 0.005 264); - --sidebar-ring: oklch(0.65 0.25 264); -} - -@theme inline { - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-destructive-foreground: var(--destructive-foreground); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); - --color-sidebar: var(--sidebar); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); -} - -@layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } -} - -.dark { - /* Light theme colors */ - --background: oklch(0.98 0.005 264); - --foreground: oklch(0.15 0.005 264); - --card: oklch(1 0 0); - --card-foreground: oklch(0.15 0.005 264); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.15 0.005 264); - --primary: oklch(0.55 0.25 264); - --primary-foreground: oklch(0.98 0.005 264); - --secondary: oklch(0.95 0.005 264); - --secondary-foreground: oklch(0.15 0.005 264); - --muted: oklch(0.95 0.005 264); - --muted-foreground: oklch(0.45 0.005 264); - --accent: oklch(0.55 0.25 264); - --accent-foreground: oklch(0.98 0.005 264); - --destructive: oklch(0.55 0.22 25); - --destructive-foreground: oklch(0.98 0.005 264); - --border: oklch(0.88 0.005 264); - --input: oklch(0.88 0.005 264); - --ring: oklch(0.55 0.25 264); - --sidebar: oklch(0.98 0.005 264); - --sidebar-foreground: oklch(0.15 0.005 264); - --sidebar-primary: oklch(0.55 0.25 264); - --sidebar-primary-foreground: oklch(0.98 0.005 264); - --sidebar-accent: oklch(0.95 0.005 264); - --sidebar-accent-foreground: oklch(0.15 0.005 264); - --sidebar-border: oklch(0.88 0.005 264); - --sidebar-ring: oklch(0.55 0.25 264); -} diff --git a/app/import-review/page.tsx b/app/import-review/page.tsx deleted file mode 100644 index 7d5d2ad..0000000 --- a/app/import-review/page.tsx +++ /dev/null @@ -1,70 +0,0 @@ -"use client"; - -import { useAuth } from "@/contexts/auth-context"; -import { useRouter } from "next/navigation"; -import { useEffect } from "react"; -import { FailedImportCard } from "@/components/failed-import-card"; -import { Card } from "@/components/ui/card"; -import { AlertCircle } from "lucide-react"; -import { useGetImportReviews } from "@/api/mangamochi"; - -export default function ImportReviewPage() { - const { user, isAuthenticated } = useAuth(); - const router = useRouter(); - - const { data: failedImportsData, queryKey } = useGetImportReviews(); - - useEffect(() => { - if (!user) { - return; - } - - if (!isAuthenticated) { - router.push("/login"); - } - }, [isAuthenticated, router, user]); - - if (!user) { - return null; - } - - return ( -
-
-
-

Import Review

-

- Review and resolve manga imports by manually matching them with - MyAnimeList entries. -

-
- - {!failedImportsData?.data || failedImportsData.data.length === 0 ? ( - - -

- No Imports to Review -

-

- All your imports have been processed successfully! -

-
- ) : ( -
-
- {failedImportsData.data.length} import - {failedImportsData.data.length !== 1 ? "s" : ""} to review -
- {failedImportsData.data.map((failedImport) => ( - - ))} -
- )} -
-
- ); -} diff --git a/app/layout.tsx b/app/layout.tsx deleted file mode 100644 index 9a5da29..0000000 --- a/app/layout.tsx +++ /dev/null @@ -1,36 +0,0 @@ -"use client"; - -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; -}>) { - const queryClient = new QueryClient(); - - return ( - - - Loading...}> - - - - {children} - - - - - - - ); -} diff --git a/app/loading.tsx b/app/loading.tsx deleted file mode 100644 index 4349ac3..0000000 --- a/app/loading.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Loading() { - return null; -} diff --git a/app/login/page.tsx b/app/login/page.tsx deleted file mode 100644 index 77374f0..0000000 --- a/app/login/page.tsx +++ /dev/null @@ -1,97 +0,0 @@ -"use client"; - -import type React from "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 { login, isLoading } = useAuth(); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(""); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(""); - - if (!email || !password) { - setError("Email and password are required"); - return; - } - - await login(email, password); - }; - - return ( -
- - - Welcome Back - Sign in to your MangaMochi account - - -
- {error && ( - - - {error} - - )} - -
- - setEmail(e.target.value)} - disabled={isLoading} - /> -
- -
- - setPassword(e.target.value)} - disabled={isLoading} - /> -
- - - -

- 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 deleted file mode 100644 index 79e4285..0000000 --- a/app/manga/[id]/chapter/[chapterNumber]/page.tsx +++ /dev/null @@ -1,211 +0,0 @@ -"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 { useReadingTracker } from "@/hooks/use-reading-tracker"; - -export default function ChapterReaderPage() { - const { setCurrentChapterPage, getCurrentChapterPage } = useReadingTracker(); - - const params = useParams(); - const router = useRouter(); - const mangaId = Number(params.id); - const chapterNumber = Number(params.chapterNumber); - - const [currentPage, setCurrentPage] = useState( - getCurrentChapterPage(chapterNumber) ?? 1, - ); - - const { data, isLoading } = useGetMangaChapterImages(chapterNumber); - - const { mutate } = useMarkAsRead(); - - useEffect(() => { - if (!data || isLoading) { - return; - } - - if (currentPage === data.data?.chapterImageKeys.length) { - mutate({ chapterId: chapterNumber }); - } - }, [data, mutate, currentPage]); - - useEffect(() => { - setCurrentChapterPage(chapterNumber, currentPage); - }, [chapterNumber, currentPage]); - - useEffect(() => { - if (!isLoading && !data?.data) { - return; - } - - const storedChapterPage = getCurrentChapterPage(chapterNumber); - if (storedChapterPage) { - setCurrentPage(storedChapterPage); - } - }, [getCurrentChapterPage, isLoading, data?.data]); - - if (!data?.data) { - return ( -
-
-

- Manga not found -

- -
-
- ); - } - - const goToNextPage = () => { - if (!data?.data) { - return; - } - - if (currentPage < data.data.chapterImageKeys.length) { - setCurrentPage(currentPage + 1); - window.scrollTo({ top: 0, behavior: "smooth" }); - } - }; - - const goToPreviousPage = () => { - if (currentPage > 1) { - setCurrentPage(currentPage - 1); - window.scrollTo({ top: 0, behavior: "smooth" }); - } - }; - - return ( -
- {/* Header */} -
-
-
-
- - -
-

- {data.data.mangaTitle} -

-

- Chapter {chapterNumber} -

-
-
-
- - Page {currentPage} / {data.data.chapterImageKeys.length} - - -
-
-
-
- - {/* Reader Content */} -
- {/* Mobile title */} -
-

- {data.data.mangaTitle} -

-

- Chapter {chapterNumber} -

-
- - {/* Manga Page */} -
- {`Page -
- - {/* Navigation Controls */} -
- {/* Page Navigation */} -
- - - {currentPage} / {data.data.chapterImageKeys.length} - - -
- - {/*/!* Chapter Navigation *!/*/} - {/*{(currentPage === data.chapterImageKeys.length || currentPage === 1) && (*/} - {/*
*/} - {/* */} - {/* */} - {/* Previous Chapter*/} - {/* */} - {/* */} - {/* Next Chapter*/} - {/* */} - {/* */} - {/*
*/} - {/*)}*/} -
-
-
- ); -} diff --git a/app/manga/[id]/page.tsx b/app/manga/[id]/page.tsx deleted file mode 100644 index 333704c..0000000 --- a/app/manga/[id]/page.tsx +++ /dev/null @@ -1,319 +0,0 @@ -"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 {useFetchAllChapters, useFetchMangaChapters, useGetManga} from "@/api/mangamochi"; -import { MangaChapter } from "@/components/manga-chapter"; -import { useQueryClient } from "@tanstack/react-query"; -import {toast} from "sonner"; - -export default function MangaDetailPage() { - const params = useParams(); - const router = useRouter(); - const mangaId = Number(params.id); - - const queryClient = useQueryClient(); - - const { data: mangaData, queryKey } = useGetManga(mangaId); - - const { mutate, isPending: fetchPending } = useFetchMangaChapters({ - mutation: { - onSuccess: () => queryClient.invalidateQueries({ queryKey }), - }, - }); - - const { mutate: fetchAllMutate, isPending: fetchAllPending } = useFetchAllChapters({ - mutation: { - onSuccess: () => toast.success("Chapter import queued successfully.") - } - }) - - const isPending = fetchPending || fetchAllPending; - - const [openProviders, setOpenProviders] = useState>(new Set()); - - if (!mangaData) { - return ( -
-
-

- Manga not found -

- -
-
- ); - } - - const toggleProvider = (providerId: number) => { - setOpenProviders((prev) => { - const next = new Set(prev); - if (next.has(providerId)) { - next.delete(providerId); - } else { - next.add(providerId); - } - return next; - }); - }; - - const author = mangaData.data?.authors.join(", "); - - const formatter = new Intl.DateTimeFormat("en-US", { - month: "2-digit", - day: "2-digit", - year: "numeric", - }); - - const publishedTo = mangaData.data?.publishedTo - ? formatter.format(new Date(mangaData.data.publishedTo)) - : null; - const publishedFrom = mangaData.data?.publishedFrom - ? formatter.format(new Date(mangaData.data.publishedFrom)) - : null; - - const dateRange = publishedTo - ? `${publishedFrom} - ${publishedTo}` - : `${publishedFrom} - Present`; - - return ( -
- {/* Header */} -
-
-
-
- -

MangaMochi

-
- -
-
-
- - {/* Content */} -
-
- {/* Manga Info Section */} -
- {/* Cover */} -
- {mangaData.data?.title -
- - {/* Details */} -
-
-
-

- {mangaData.data?.title} -

- - {mangaData.data?.status} - -
-

{author}

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

- Alternative Titles -

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

- {mangaData.data?.synopsis} -

- -
-
- -
-

Rating

-

- {mangaData.data?.score && mangaData.data?.score > 0 - ? mangaData.data?.score - : "-"} -

-
-
- -
- -
-

Chapters

-

- {mangaData.data?.chapterCount && - mangaData.data?.chapterCount > 0 - ? mangaData.data?.chapterCount - : "-"} -

-
-
- -
- -
-

Published

-

- {dateRange} -

-
-
- -
- -
-

Providers

-

- {mangaData.data?.providerCount} -

-
-
-
- -
- {mangaData.data?.genres.map((genre) => ( - - {genre} - - ))} -
-
-
- -
-

- Chapters by Provider -

- -
- {mangaData.data?.providers.map((provider) => ( - - toggleProvider(provider.id)} - > - - -
-
- -
-

- {provider.providerName} -

-

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

-
-
- {provider.supportsChapterFetch && ( -
- - -
)} -
- -
-
- - - -
-
- ))} -
-
-
-
-
- ); -} diff --git a/app/page.tsx b/app/page.tsx deleted file mode 100644 index 85d788d..0000000 --- a/app/page.tsx +++ /dev/null @@ -1,145 +0,0 @@ -"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 { AuthHeader } from "@/components/auth-header"; -import { ImportDropdown } from "@/components/import-dropdown"; -import { SortOption, SortOptions } from "@/components/sort-options"; - -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 [userFavorites, setUserFavorites] = useState(false); - const [showAdultContent, setShowAdultContent] = useState(false); - const [sortOption, setSortOption] = useState("title-asc"); - - const { data: mangas, queryKey } = useGetMangas({ - page: currentPage - 1, - size: ITEMS_PER_PAGE, - sort: ["id"], - searchQuery: searchQuery, - statuses: selectedStatus, - genreIds: selectedGenres, - userFavorites, - score: minRating, - }); - - const totalPages = mangas?.data?.totalPages; - - return ( -
- - - {/* Main Content */} -
- {/* Header */} -
-
-
-
-
- -
-

- MangaMochi -

-

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

-
-
-
- - - -
-
- - {/* Search */} -
- - { - setSearchQuery(e.target.value); - setCurrentPage(1); - }} - className="pl-10 bg-card border-border" - /> -
-
-
-
- -
- {mangas?.data?.content && mangas.data.content.length > 0 ? ( - <> - {mangas?.data?.totalElements && ( -
-

- Showing {(currentPage - 1) * ITEMS_PER_PAGE + 1} to{" "} - {Math.min( - currentPage * ITEMS_PER_PAGE, - mangas.data.totalElements, - )}{" "} - of {mangas.data.totalElements} -

- -
- )} - - - {totalPages && totalPages > 1 && ( -
- -
- )} - - ) : ( -
-
-

- 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 deleted file mode 100644 index 7abfabd..0000000 --- a/app/profile/page.tsx +++ /dev/null @@ -1,206 +0,0 @@ -"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"; - -export default function ProfilePage() { - const router = useRouter(); - const { user, logout } = useAuth(); - const [itemsPerPage, setItemsPerPage] = useState(12); - - if (!user) { - return ( -
- - - 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}*/} -

-
-
- - {/*{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}*/} - {/*

*/} - {/* ),*/} - {/* )}*/} - {/*
*/} - {/*
*/} - {/*)}*/} - - {/*{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 deleted file mode 100644 index 7fa40f1..0000000 --- a/app/register/page.tsx +++ /dev/null @@ -1,136 +0,0 @@ -"use client"; - -import type React from "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 { 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(""); - - if (!email || !name || !password || !confirmPassword) { - setError("All fields are required"); - return; - } - - if (password !== confirmPassword) { - setError("Passwords do not match"); - return; - } - - console.log(email); - - await register(email, password, name); - }; - - return ( -
- - - Create Account - - Join MangaMochi to track your favorite manga - - - -
- {error && ( - - - {error} - - )} - -
- - setEmail(e.target.value)} - disabled={isLoading} - /> -
- -
- - setName(e.target.value)} - disabled={isLoading} - /> -
- -
- - setPassword(e.target.value)} - disabled={isLoading} - /> -
- -
- - setConfirmPassword(e.target.value)} - disabled={isLoading} - /> -
- - - -

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

-
-
-
-
- ); -} diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..e867bf4 --- /dev/null +++ b/biome.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.2/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "includes": ["**", "!!**/dist", "!**/src/api/generated"] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "noNonNullAssertion": "off" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + }, + "css": { + "parser": { + "tailwindDirectives": true + } + } +} diff --git a/components.json b/components.json index 4ee62ee..6555943 100644 --- a/components.json +++ b/components.json @@ -1,21 +1,22 @@ { - "$schema": "https://ui.shadcn.com/schema.json", - "style": "new-york", - "rsc": true, - "tsx": true, - "tailwind": { - "config": "", - "css": "app/globals.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - }, - "iconLibrary": "lucide" + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} } diff --git a/components/auth-header.tsx b/components/auth-header.tsx deleted file mode 100644 index 0634796..0000000 --- a/components/auth-header.tsx +++ /dev/null @@ -1,85 +0,0 @@ -"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 { - 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 handleLogout = () => { - logout(); - router.push("/"); - }; - - if (!user) { - return ( - - ); - } - - return ( - - - - - -
-
-

{user.name}

-

{user.email}

-
-
- - - - - Profile - - - - - - Preferences - - - - - - Logout - -
-
- ); -} diff --git a/components/failed-import-card.tsx b/components/failed-import-card.tsx deleted file mode 100644 index d76f598..0000000 --- a/components/failed-import-card.tsx +++ /dev/null @@ -1,134 +0,0 @@ -"use client"; - -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Card } from "@/components/ui/card"; -import { useState } from "react"; -import { ExternalLink, Trash2 } from "lucide-react"; -import { - ImportReviewDTO, - useDeleteImportReview, - useResolveImportReview, -} from "@/api/mangamochi"; -import { toast } from "sonner"; -import { useQueryClient } from "@tanstack/react-query"; - -interface FailedImportCardProps { - failedImport: ImportReviewDTO; - queryKey: any; -} - -export function FailedImportCard({ - failedImport, - queryKey, -}: FailedImportCardProps) { - const queryClient = useQueryClient(); - - const [malId, setMalId] = useState(""); - - const { - mutate: mutateDeleteImportReview, - isPending: isPendingDeleteImportReview, - } = useDeleteImportReview({ - mutation: { - onSuccess: () => { - queryClient.invalidateQueries({ queryKey }); - - toast.success("Import review removed successfully"); - }, - }, - }); - - const { - mutate: mutateResolveImportReview, - isPending: isPendingResolveImportReview, - } = useResolveImportReview({ - mutation: { - onSuccess: () => { - queryClient.invalidateQueries({ queryKey }); - toast.success("Import review resolved successfully"); - }, - }, - }); - - const handleResolve = () => { - if (!malId.trim()) { - alert("Please enter a MyAnimeList ID"); - return; - } - - mutateResolveImportReview({ - params: { importReviewId: failedImport.id, malId }, - }); - }; - - const handleRemove = () => { - mutateDeleteImportReview({ id: failedImport.id }); - }; - - const importDate = new Date(failedImport.createdAt).toLocaleDateString(); - - return ( - -
-
-
-

- {failedImport.title} -

-

- Provider:{" "} - {failedImport.providerName} •{" "} - {importDate} -

-
- {failedImport.externalUrl && ( - - - - )} -
- -
-

{failedImport.reason}

-
- -
-
- - setMalId(e.target.value)} - className="mt-2" - type="number" - /> -
- -
- - -
-
-
-
- ); -} diff --git a/components/filter-sidebar.tsx b/components/filter-sidebar.tsx deleted file mode 100644 index 7687e90..0000000 --- a/components/filter-sidebar.tsx +++ /dev/null @@ -1,224 +0,0 @@ -"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 { useAuth } from "@/contexts/auth-context"; -import { Switch } from "@/components/ui/switch"; -import { Skeleton } from "@/components/ui/skeleton"; - -interface FilterSidebarProps { - selectedGenres: number[]; - selectedStatus: string[]; - minRating: number; - userFavorites: boolean; - showAdultContent: boolean; - onGenresChange: (genres: number[]) => void; - onStatusChange: (status: string[]) => void; - onRatingChange: (rating: number) => void; - onUserFavoritesChange: (favorites: boolean) => void; - onShowAdultContentChange: (showAdult: boolean) => void; -} - -const STATUSES = ["Ongoing", "Completed", "Hiatus"]; - -const RATINGS = [ - { label: "8.5+ Stars", value: 8.5 }, - { label: "7.0+ Stars", value: 7.0 }, - { label: "5.0+ Stars", value: 5.0 }, - { label: "All Ratings", value: 0 }, -]; - -export function FilterSidebar({ - selectedGenres, - selectedStatus, - minRating, - userFavorites, - showAdultContent, - onGenresChange, - onStatusChange, - onRatingChange, - onUserFavoritesChange, - onShowAdultContentChange, -}: FilterSidebarProps) { - const { data: genresData, isPending: isPendingGenres } = useGetGenres(); - const { isAuthenticated } = useAuth(); - - const toggleGenre = (genre: number) => { - if (selectedGenres.includes(genre)) { - onGenresChange(selectedGenres.filter((g) => g !== genre)); - } else { - onGenresChange([...selectedGenres, genre]); - } - }; - - const toggleStatus = (status: string) => { - if (selectedStatus.includes(status)) { - onStatusChange(selectedStatus.filter((s) => s !== status)); - } else { - onStatusChange([...selectedStatus, status]); - } - }; - - const clearAllFilters = () => { - onGenresChange([]); - onStatusChange([]); - onRatingChange(0); - onUserFavoritesChange(false); - onShowAdultContentChange(false); - }; - - const hasActiveFilters = - selectedGenres.length > 0 || - selectedStatus.length > 0 || - minRating > 0 || - userFavorites; - - return ( - - ); -} diff --git a/components/import-dropdown.tsx b/components/import-dropdown.tsx deleted file mode 100644 index f717ceb..0000000 --- a/components/import-dropdown.tsx +++ /dev/null @@ -1,73 +0,0 @@ -"use client"; - -import type React from "react"; - -import { useState } from "react"; -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { AlertCircle, Download, FileUp } from "lucide-react"; -import { useAuth } from "@/contexts/auth-context"; -import { MangaDexImportDialog } from "@/components/manga-dex-import-dialog"; -import { MangaManualImportDialog } from "@/components/manga-manual-import-dialog"; -import Link from "next/link"; - -export function ImportDropdown() { - const { isAuthenticated } = useAuth(); - - const [mangaDexDialogOpen, setMangaDexDialogOpen] = useState(false); - const [fileImportDialogOpen, setFileImportDialogOpen] = useState(false); - - if (!isAuthenticated) { - return null; - } - - return ( - <> - - - - - - setMangaDexDialogOpen(true)}> - - Import from MangaDex - - setFileImportDialogOpen(true)}> - - Import from File - - - - - - - Import Review - - - - - - - - - - ); -} diff --git a/components/manga-card.tsx b/components/manga-card.tsx deleted file mode 100644 index 5b80470..0000000 --- a/components/manga-card.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import Link from "next/link"; -import Image from "next/image"; -import { Star, Calendar, Database, Heart } from "lucide-react"; -import { Card, CardContent } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { useCallback } from "react"; -import { - PageMangaListDTO, - useSetFavorite, - useSetUnfavorite, -} from "@/api/mangamochi"; -import { useQueryClient } from "@tanstack/react-query"; -import { useAuth } from "@/contexts/auth-context"; - -interface Manga { - id: number; - title: string; - coverImageKey?: string; - status?: string; - publishedFrom?: string; - publishedTo?: string; - providerCount?: number; - authors: string[]; - genres: string[]; - score: number; - favorite: boolean; -} - -interface MangaCardProps { - manga: Manga; - queryKey: any; -} - -export function MangaCard({ manga, queryKey }: MangaCardProps) { - const queryClient = useQueryClient(); - const { isAuthenticated } = useAuth(); - - 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 dateRange = publishedTo - ? `${publishedFrom} - ${publishedTo}` - : `${publishedFrom} - Present`; - - const author = manga.authors.join(", "); - - const { mutate: mutateFavorite, isPending: isPendingFavorite } = - useSetFavorite({ - mutation: { - onSuccess: () => - queryClient.setQueryData( - queryKey, - (oldData: PageMangaListDTO | undefined) => { - return { - ...oldData, - content: - oldData?.content?.map((m) => - m.id === manga.id ? { ...m, favorite: true } : m, - ) || [], - }; - }, - ), - }, - }); - - const { mutate: mutateUnfavorite, isPending: isPendingUnfavorite } = - useSetUnfavorite({ - mutation: { - onSuccess: () => - queryClient.setQueryData( - queryKey, - (oldData: PageMangaListDTO | undefined) => { - return { - ...oldData, - content: - oldData?.content?.map((m) => - m.id === manga.id ? { ...m, favorite: false } : m, - ) || [], - }; - }, - ), - }, - }); - - const handleFavoriteClick = useCallback( - (isFavorite: boolean) => { - isFavorite - ? mutateUnfavorite({ id: manga.id }) - : mutateFavorite({ id: manga.id }); - }, - [mutateUnfavorite, manga.id, mutateFavorite], - ); - - return ( - - -
- - {manga.title} -
- - {manga.status} - -
- - - {isAuthenticated && ( - - )} -
- - {/* Info */} -
- -

- {manga.title} -

- - -

{author}

- - {publishedFrom && ( -
- - {dateRange} -
- )} - -
-
- - - {manga.score && manga.score > 0 ? manga.score : "-"} - -
-
- -
- - - {manga.providerCount}{" "} - {manga.providerCount === 1 ? "provider" : "providers"} - -
- -
- {manga.genres.map((genre) => ( - - {genre} - - ))} -
-
-
-
- ); -} diff --git a/components/manga-chapter.tsx b/components/manga-chapter.tsx deleted file mode 100644 index 9146e02..0000000 --- a/components/manga-chapter.tsx +++ /dev/null @@ -1,155 +0,0 @@ -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; -} - -export const MangaChapter: FC = ({ - mangaId, - mangaProviderId, -}) => { - const router = useRouter(); - - const { isPending, data, queryKey } = useGetMangaChapters(mangaProviderId); - - 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, isPending: isPendingFetchChapter } = useFetchChapter({ - mutation: { - onSuccess: () => queryClient.invalidateQueries({ queryKey }), - onSettled: () => setFetchingId(null), - }, - }); - - const [fetchingId, setFetchingId] = useState(null); - - const fetchChapter = useCallback( - (mangaChapterId: number) => { - setFetchingId(mangaChapterId); - mutate({ chapterId: mangaChapterId }); - }, - [mutate], - ); - - const handleReadChapter = (chapterNumber: number) => { - router.push(`/manga/${mangaId}/chapter/${chapterNumber}`); - }; - - return ( -
- {isPending ? ( -
- - - Loading chapters... - -
- ) : data ? ( -
- {data.data?.map((chapter) => { - return ( -
-
-
- {chapter.isRead ? ( - - ) : ( - - {/*{chapter}*/} - - )} -
-
-

- {chapter.title} -

- {chapter.downloaded && ( -

- In database -

- )} -
-
- {chapter.downloaded ? ( -
- - -
- ) : ( - - )} -
- ); - })} -
- ) : null} -
- ); -}; diff --git a/components/manga-dex-import-dialog.tsx b/components/manga-dex-import-dialog.tsx deleted file mode 100644 index 1245023..0000000 --- a/components/manga-dex-import-dialog.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React, { useState } from "react"; -import { useImportFromMangaDex } from "@/api/mangamochi"; -import { toast } from "sonner"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; - -interface MangaDexImportDialogProps { - mangaDexDialogOpen: boolean; - onMangaDexDialogOpenChange: (open: boolean) => void; -} - -export const MangaDexImportDialog = ({ - mangaDexDialogOpen, - onMangaDexDialogOpenChange, -}: MangaDexImportDialogProps) => { - const [mangaDexId, setMangaDexId] = useState(""); - - const { mutate: importMangaDex, isPending: isPendingImportMangaDex } = - useImportFromMangaDex({ - mutation: { - onSuccess: () => { - setMangaDexId(""); - onMangaDexDialogOpenChange(false); - toast.success("Manga imported successfully!"); - }, - }, - }); - - const handleMangaDexImport = () => { - if (!mangaDexId.trim()) { - alert("Please enter a MangaDex URL or ID"); - return; - } - - let id = mangaDexId; - if (mangaDexId.length > 36) { - const match = mangaDexId.match(/title\/([0-9a-fA-F-]{36})/); - if (match) { - id = match[1]; - } else { - alert("Invalid MangaDex URL or ID"); - return; - } - } - - if (id.length !== 36) { - alert("Invalid MangaDex ID"); - return; - } - - importMangaDex({ data: { id } }); - }; - - return ( - - - - Import from MangaDex - - Enter a MangaDex manga URL or ID to import it to your library. - - -
-
- - setMangaDexId(e.target.value)} - className="mt-2" - /> -
-
- - - - -
-
- ); -}; diff --git a/components/manga-grid.tsx b/components/manga-grid.tsx deleted file mode 100644 index 6a54a8a..0000000 --- a/components/manga-grid.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { MangaCard } from "./manga-card"; -import { MangaListDTO } from "@/api/mangamochi"; - -interface MangaGridProps { - manga: MangaListDTO[]; - queryKey: any; -} - -export function MangaGrid({ manga, queryKey }: MangaGridProps) { - return ( -
- {manga.map((item) => ( - - ))} -
- ); -} diff --git a/components/manga-manual-import-dialog.tsx b/components/manga-manual-import-dialog.tsx deleted file mode 100644 index 31b9714..0000000 --- a/components/manga-manual-import-dialog.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { FileUp } from "lucide-react"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import React, { useState } from "react"; -import { Input } from "@/components/ui/input"; -import { useImportMultipleFiles } from "@/api/mangamochi"; -import { toast } from "sonner"; - -interface MangaManualImportDialogProps { - fileImportDialogOpen: boolean; - onFileImportDialogOpenChange: (open: boolean) => void; -} - -export const MangaManualImportDialog = ({ - fileImportDialogOpen, - onFileImportDialogOpenChange, -}: MangaManualImportDialogProps) => { - const [malId, setMalId] = useState(""); - const [dragActive, setDragActive] = useState(false); - const [files, setFiles] = useState(null); - - const { mutate, isPending } = useImportMultipleFiles({ - mutation: { - onSuccess: () => { - setFiles(null); - setMalId(""); - onFileImportDialogOpenChange(false); - toast.success("Manga imported successfully!"); - }, - onError: () => toast.error("Failed to import manga."), - }, - }); - - const handleFileImport = () => { - if (!files) { - alert("Please select one or more files to upload"); - return; - } - - if (!malId.trim()) { - alert("Please enter a MyAnimeList manga ID"); - return; - } - - let id = malId; - - if (!/^\d+$/.test(malId)) { - const regex = - /https?:\/\/(?:www\.)?myanimelist\.net\/(manga)\/(\d+)(?:\/|$)/i; - const match = malId.match(regex); - - if (match) { - id = match[2]; - } else { - alert("Invalid MyAnimeList URL or ID"); - return; - } - } - - mutate({ data: { malId: id, files } }); - }; - - const handleDrag = (e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - if (e.type === "dragenter" || e.type === "dragover") { - setDragActive(true); - } else if (e.type === "dragleave") { - setDragActive(false); - } - }; - - const handleDrop = (e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setDragActive(false); - - if (e.dataTransfer.files) { - setFiles(Array.from(e.dataTransfer.files)); - } - }; - - const handleFileSelect = (e: React.ChangeEvent) => { - if (e.target.files) { - setFiles(Array.from(e.target.files)); - } - }; - - return ( - - - - Import from File - - Upload one or more files and provide the MyAnimeList manga URL (or - ID) to import manga data. - - -
-
- - setMalId(e.target.value)} - className="mt-2" - /> -
-
- -
- - -
-
-
- - - - -
-
- ); -}; diff --git a/components/manga-pagination.tsx b/components/manga-pagination.tsx deleted file mode 100644 index ef0d7a2..0000000 --- a/components/manga-pagination.tsx +++ /dev/null @@ -1,90 +0,0 @@ -"use client"; - -import { Button } from "@/components/ui/button"; -import { ChevronLeft, ChevronRight } from "lucide-react"; - -interface MangaPaginationProps { - currentPage: number; - totalPages: number; - onPageChange: (page: number) => void; -} - -export function MangaPagination({ - currentPage, - totalPages, - onPageChange, -}: MangaPaginationProps) { - const getPageNumbers = () => { - const pages: (number | string)[] = []; - const showEllipsis = totalPages > 7; - - if (!showEllipsis) { - return Array.from({ length: totalPages }, (_, i) => i + 1); - } - - // Always show first page - pages.push(1); - - if (currentPage > 3) { - 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); - } - - if (currentPage < totalPages - 2) { - pages.push("..."); - } - - // Always show last page - if (totalPages > 1) { - pages.push(totalPages); - } - - return pages; - }; - - return ( -
- - - {getPageNumbers().map((page, index) => ( -
- {page === "..." ? ( - ... - ) : ( - - )} -
- ))} - - -
- ); -} diff --git a/components/sort-options.tsx b/components/sort-options.tsx deleted file mode 100644 index 01e7120..0000000 --- a/components/sort-options.tsx +++ /dev/null @@ -1,132 +0,0 @@ -"use client"; - -import { ArrowUpDown, ArrowUp, ArrowDown } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; - -export type SortOption = - | "title-asc" - | "title-desc" - | "rating-desc" - | "rating-asc" - | "date-newest" - | "date-oldest" - | "status"; - -interface SortOptionsProps { - currentSort: SortOption; - onSortChange: (sort: SortOption) => void; -} - -export function SortOptions({ currentSort, onSortChange }: SortOptionsProps) { - const getSortLabel = () => { - switch (currentSort) { - case "title-asc": - return "Title (A-Z)"; - case "title-desc": - return "Title (Z-A)"; - case "rating-desc": - return "Rating (High to Low)"; - case "rating-asc": - return "Rating (Low to High)"; - case "date-newest": - return "Newest First"; - case "date-oldest": - return "Oldest First"; - case "status": - return "Status"; - default: - return "Sort by"; - } - }; - - return ( - - - - - - Sort by - - - - Title - - onSortChange("title-asc")} - className="cursor-pointer" - > - - Title (A-Z) - - onSortChange("title-desc")} - className="cursor-pointer" - > - - Title (Z-A) - - - - - Rating - - onSortChange("rating-desc")} - className="cursor-pointer" - > - - Rating (High to Low) - - onSortChange("rating-asc")} - className="cursor-pointer" - > - - Rating (Low to High) - - - - - Publication Date - - onSortChange("date-newest")} - className="cursor-pointer" - > - - Newest First - - onSortChange("date-oldest")} - className="cursor-pointer" - > - - Oldest First - - - - - Status - - onSortChange("status")} - className="cursor-pointer" - > - - Ongoing First - - - - ); -} diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx deleted file mode 100644 index d14279f..0000000 --- a/components/theme-provider.tsx +++ /dev/null @@ -1,57 +0,0 @@ -"use client"; - -import type React from "react"; - -import { createContext, useContext, useEffect, useState } from "react"; - -type Theme = "light" | "dark"; - -type ThemeContextType = { - theme: Theme; - toggleTheme: () => void; -}; - -const ThemeContext = createContext(undefined); - -export function ThemeProvider({ children }: { children: React.ReactNode }) { - const [theme, setTheme] = useState("dark"); - const [mounted, setMounted] = useState(false); - - useEffect(() => { - setMounted(true); - const savedTheme = localStorage.getItem("theme") as Theme | null; - if (savedTheme) { - setTheme(savedTheme); - } - }, []); - - useEffect(() => { - if (mounted) { - const root = document.documentElement; - if (theme === "dark") { - root.classList.add("dark"); - } else { - root.classList.remove("dark"); - } - localStorage.setItem("theme", theme); - } - }, [theme, mounted]); - - const toggleTheme = () => { - setTheme((prev) => (prev === "dark" ? "light" : "dark")); - }; - - return ( - - {children} - - ); -} - -export function useTheme() { - const context = useContext(ThemeContext); - if (context === undefined) { - throw new Error("useTheme must be used within a ThemeProvider"); - } - return context; -} diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx deleted file mode 100644 index 13615a9..0000000 --- a/components/theme-toggle.tsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; - -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(); - - return ( - - ); -} diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx deleted file mode 100644 index 6d60d31..0000000 --- a/components/ui/accordion.tsx +++ /dev/null @@ -1,66 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as AccordionPrimitive from "@radix-ui/react-accordion"; -import { ChevronDownIcon } from "lucide-react"; - -import { cn } from "@/lib/utils"; - -function Accordion({ - ...props -}: React.ComponentProps) { - return ; -} - -function AccordionItem({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function AccordionTrigger({ - className, - children, - ...props -}: React.ComponentProps) { - return ( - - svg]:rotate-180", - className, - )} - {...props} - > - {children} - - - - ); -} - -function AccordionContent({ - className, - children, - ...props -}: React.ComponentProps) { - return ( - -
{children}
-
- ); -} - -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx deleted file mode 100644 index c37e0cf..0000000 --- a/components/ui/alert-dialog.tsx +++ /dev/null @@ -1,157 +0,0 @@ -"use client"; - -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"; - -function AlertDialog({ - ...props -}: React.ComponentProps) { - return ; -} - -function AlertDialogTrigger({ - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function AlertDialogPortal({ - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function AlertDialogOverlay({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function AlertDialogContent({ - className, - ...props -}: React.ComponentProps) { - return ( - - - - - ); -} - -function AlertDialogHeader({ - className, - ...props -}: React.ComponentProps<"div">) { - return ( -
- ); -} - -function AlertDialogFooter({ - className, - ...props -}: React.ComponentProps<"div">) { - return ( -
- ); -} - -function AlertDialogTitle({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function AlertDialogDescription({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function AlertDialogAction({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function AlertDialogCancel({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -export { - AlertDialog, - AlertDialogPortal, - AlertDialogOverlay, - AlertDialogTrigger, - AlertDialogContent, - AlertDialogHeader, - AlertDialogFooter, - AlertDialogTitle, - AlertDialogDescription, - AlertDialogAction, - AlertDialogCancel, -}; diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx deleted file mode 100644 index aa7de24..0000000 --- a/components/ui/alert.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from "react"; -import { cva, type VariantProps } from "class-variance-authority"; - -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", - { - variants: { - variant: { - default: "bg-card text-card-foreground", - destructive: - "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", - }, - }, - defaultVariants: { - variant: "default", - }, - }, -); - -function Alert({ - className, - variant, - ...props -}: React.ComponentProps<"div"> & VariantProps) { - return ( -
- ); -} - -function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); -} - -function AlertDescription({ - className, - ...props -}: React.ComponentProps<"div">) { - return ( -
- ); -} - -export { Alert, AlertTitle, AlertDescription }; diff --git a/components/ui/aspect-ratio.tsx b/components/ui/aspect-ratio.tsx deleted file mode 100644 index c16d6bc..0000000 --- a/components/ui/aspect-ratio.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use client"; - -import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; - -function AspectRatio({ - ...props -}: React.ComponentProps) { - return ; -} - -export { AspectRatio }; diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx deleted file mode 100644 index c4475c2..0000000 --- a/components/ui/avatar.tsx +++ /dev/null @@ -1,53 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as AvatarPrimitive from "@radix-ui/react-avatar"; - -import { cn } from "@/lib/utils"; - -function Avatar({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function AvatarImage({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function AvatarFallback({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -export { Avatar, AvatarImage, AvatarFallback }; diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx deleted file mode 100644 index 46f988c..0000000 --- a/components/ui/badge.tsx +++ /dev/null @@ -1,46 +0,0 @@ -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"; - -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", - { - variants: { - variant: { - default: - "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", - 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", - outline: - "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - }, -); - -function Badge({ - className, - variant, - asChild = false, - ...props -}: React.ComponentProps<"span"> & - VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot : "span"; - - return ( - - ); -} - -export { Badge, badgeVariants }; diff --git a/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx deleted file mode 100644 index f63ae19..0000000 --- a/components/ui/breadcrumb.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; -import { ChevronRight, MoreHorizontal } from "lucide-react"; - -import { cn } from "@/lib/utils"; - -function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { - return