diff --git a/.env b/.env index 1409b98..e69de29 100644 --- a/.env +++ b/.env @@ -1,2 +0,0 @@ -NEXTAUTH_SECRET=Pn9CJbdUk6C9J8+lY6SlmFHkw4NItMpoHJ6ylIwEqrk= -NEXT_PUBLIC_API_URL=https://mangahub.azurewebsites.net \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index 66ce2d9..6afe64c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,7 +5,6 @@ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended", - "next/core-web-vitals", "plugin:prettier/recommended" ], "ignorePatterns": ["dist", ".eslintrc.cjs"], @@ -18,6 +17,7 @@ "prettier" ], "rules": { + "no-undef": "warn", "react/react-in-jsx-scope": 0, "react/display-name": 0, "@typescript-eslint/no-var-requires": "off", @@ -50,7 +50,7 @@ ], "react/function-component-definition": [ 2, - { "namedComponents": "function-declaration" } + { "namedComponents": "arrow-function" } ], "react/jsx-one-expression-per-line": "off" } diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 0a0031a..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Simple workflow for deploying static content to GitHub Pages -name: Deploy static content to Pages - -on: - # Runs on pushes targeting the default branch - push: - branches: ['main'] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow one concurrent deployment -concurrency: - group: 'pages' - cancel-in-progress: true - -jobs: - # Single deploy job since we're just deploying - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Set up Node - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: 'npm' - - name: Install dependencies - run: npm install - - name: Build - run: npm run build - - name: Setup Pages - uses: actions/configure-pages@v3 - - name: Upload artifact - uses: actions/upload-pages-artifact@v1 - with: - # Upload dist repository - path: './dist' - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3a8a177..d8db839 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* -.next node_modules dist dist-ssr diff --git a/.prettierrc b/.prettierrc index bb62995..2db266a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,6 +4,5 @@ "semi": true, "singleQuote": true, "printWidth": 80, - "endOfLine": "lf", - "plugins": ["prettier-plugin-tailwindcss"] + "endOfLine": "lf" } diff --git a/components.json b/components.json deleted file mode 100644 index 94d1ff9..0000000 --- a/components.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", - "rsc": true, - "tsx": true, - "tailwind": { - "config": "tailwind.config.js", - "css": "./src/app/globals.css", - "baseColor": "slate", - "cssVariables": true - }, - "aliases": { - "components": "@/shared/components", - "utils": "@/shared/utils/cn" - } -} diff --git a/index.html b/index.html new file mode 100644 index 0000000..7a09b03 --- /dev/null +++ b/index.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + Manga Hub + + +
+ + + diff --git a/lint-staged.config.js b/lint-staged.config.js index 3b6359d..2591315 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -1,20 +1,14 @@ -module.exports = { +export default { // Type check TypeScript files '**/*.(ts|tsx)': () => 'yarn tsc --noEmit', // Lint then format TypeScript and JavaScript files '**/*.(ts|tsx|js)': (filenames) => [ - `yarn eslint --fix ${filenames - .filter((filePath) => !filePath.includes('[', '(')) - .join(' ')}`, - `yarn prettier --write ${filenames - .filter((filePath) => !filePath.includes('[', '(')) - .join(' ')}`, + `yarn eslint --fix ${filenames.join(' ')}`, + `yarn prettier --write ${filenames.join(' ')}`, ], // Format MarkDown and JSON '**/*.(md|json)': (filenames) => - `yarn prettier --write ${filenames - .filter((filePath) => !filePath.includes('[...nextauth]')) - .join(' ')}`, + `yarn prettier --write ${filenames.join(' ')}`, }; diff --git a/next-env.d.ts b/next-env.d.ts deleted file mode 100644 index 4f11a03..0000000 --- a/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next.config.js b/next.config.js deleted file mode 100644 index b58adfa..0000000 --- a/next.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/** @type {import('next').NextConfig} */ -module.exports = { - reactStrictMode: true, - swcMinify: true, - compiler: { - styledComponents: true, - }, - webpack: (config) => { - config.resolve.alias.canvas = false; - - return config; - }, -}; diff --git a/package.json b/package.json index 205207c..54ddc6e 100644 --- a/package.json +++ b/package.json @@ -1,40 +1,26 @@ { "name": "mangahub", "private": true, - "version": "0.0.0", - "type": "commonjs", + "version": "0.0.1", + "type": "module", "scripts": { - "dev": "clear && next dev", - "build": "next build", - "start": "next start", - "lint": "next lint --fix" + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint --ext .js,.jsx,.ts,.tsx", + "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx --fix", + "format": "prettier \"**/*.{js,jsx,ts,tsx,css,scss}\" --write" }, "dependencies": { "@hookform/resolvers": "^3.3.2", - "@radix-ui/react-checkbox": "^1.0.4", - "@radix-ui/react-dropdown-menu": "^2.0.6", - "@radix-ui/react-label": "^2.0.2", - "@radix-ui/react-select": "^2.0.0", - "@radix-ui/react-separator": "^1.0.3", - "@radix-ui/react-slot": "^1.0.2", - "@radix-ui/react-toast": "^1.1.5", - "@tanstack/react-table": "^8.10.7", "axios": "^1.5.1", - "class-variance-authority": "^0.7.0", - "clsx": "^2.0.0", - "eslint-config-prettier": "^9.0.0", - "jwt-decode": "^4.0.0", "lucide-react": "^0.289.0", - "next": "^13.5.6", - "next-auth": "^4.24.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-helmet": "^6.1.0", "react-hook-form": "^7.47.0", - "react-pdf": "^7.5.1", "react-router-dom": "^6.16.0", - "styled-components": "^6.1.1", - "tailwind-merge": "^1.14.0", - "tailwindcss-animate": "^1.0.7", + "styled-components": "^6.1.5", "uuid": "^9.0.1", "yup": "^1.3.2" }, @@ -44,21 +30,19 @@ "@types/react-dom": "^18.2.7", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", - "autoprefixer": "^10.4.16", + "@vitejs/plugin-react": "^4.2.1", "eslint": "^8.45.0", - "eslint-config-next": "^13.5.6", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.28.1", "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.3", + "eslint-plugin-react-refresh": "^0.4.5", "husky": "^8.0.3", "lint-staged": "^15.0.2", - "postcss": "^8.4.31", "prettier": "3.0.3", - "prettier-plugin-tailwindcss": "^0.5.6", - "shadcn-ui": "^0.4.1", - "tailwindcss": "^3.3.5", - "typescript": "^5.2.2" + "typescript": "^5.2.2", + "vite": "^5.0.10", + "vite-tsconfig-paths": "^4.2.2" } } diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index 12a703d..0000000 --- a/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/public/bg.jpg b/public/bg.jpg deleted file mode 100644 index 2bfdb17..0000000 Binary files a/public/bg.jpg and /dev/null differ diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..d525b47 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,42 @@ +import { BrowserRouter, Route, Routes } from 'react-router-dom'; + +import { ROUTE } from './constants'; +import { + ChangePasswordContainer, + ForgotPasswordContainer, + SignInContainer, + SignUpContainer, +} from './modules/auth'; +import { HomeContainer } from './modules/home'; +import { LayoutContainer } from './modules/layout'; +import { NotFoundContainer } from './modules/notFound'; + +import GlobalStyles from '@/globals'; + +const App = () => { + return ( + <> + + + + }> + } /> + } /> + } /> + } + /> + } + /> + } /> + + + + + ); +}; + +export default App; diff --git a/src/app/(auth)/change-password/page.tsx b/src/app/(auth)/change-password/page.tsx deleted file mode 100644 index 8d243bf..0000000 --- a/src/app/(auth)/change-password/page.tsx +++ /dev/null @@ -1,127 +0,0 @@ -'use client'; - -import { yupResolver } from '@hookform/resolvers/yup'; -import { useRouter, useSearchParams } from 'next/navigation'; -import { useEffect } from 'react'; -import { Resolver, SubmitHandler, useForm } from 'react-hook-form'; -import * as yup from 'yup'; - -import { ChangePasswordDTO, ChangePasswordForm } from './types'; - -import { Input } from '@/shared/components/FormComponents'; -import { Button } from '@/shared/components/ui/button'; -import { useToast } from '@/shared/components/ui/use-toast'; -import { ROUTE } from '@/shared/constants/routes'; -import { PasswordRegex } from '@/shared/constants/validationConstants'; -import { axios } from '@/shared/utils/axios'; - -const validationSchema = yup - .object({ - password: yup - .string() - .required('No password provided.') - .min(8, 'Password minimal length is 8') - .matches( - PasswordRegex, - 'Password may contain only latin characters, numbers and special characters.', - ), - confirmPassword: yup - .string() - .required('No password provided.') - .min(8, 'Password minimal length is 8') - .matches( - PasswordRegex, - 'Password may contain only latin characters, numbers and special characters.', - ), - }) - .required(); - -function ChangePasswordPage() { - const { toast } = useToast(); - const router = useRouter(); - const searchParams = useSearchParams(); - - const token = searchParams.get('token'); - - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - resolver: yupResolver(validationSchema) as Resolver< - ChangePasswordForm, - unknown - >, - }); - - useEffect(() => { - (async () => { - try { - await axios.get('User/request-reset-password', { - params: { token }, - }); - } catch (e) { - console.error(e); - - toast({ - title: 'Error occurred!', - variant: 'destructive', - description: 'Your token is invalid, please try again from the start', - }); - - router.push(ROUTE.HOME); - } - })(); - }, [token, router, toast]); - - const onSubmit: SubmitHandler = async (data) => { - try { - await axios.post( - 'User/reset-password', - { - ...data, - token, - }, - ); - - toast({ - title: 'Success', - description: 'Your password has been changed successfully', - }); - - router.push(ROUTE.HOME); - } catch (error) { - toast({ - title: 'Error occurred!', - variant: 'destructive', - // description: error.errors, TODO doesn't work - }); - } - }; - return ( -
-

Change Password Form

-
- - - - -
-
- ); -} - -export default ChangePasswordPage; diff --git a/src/app/(auth)/change-password/types.ts b/src/app/(auth)/change-password/types.ts deleted file mode 100644 index 7016240..0000000 --- a/src/app/(auth)/change-password/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -export type ChangePasswordDTO = { - token: string; - password: string; - confirmPassword: string; -}; - -export type ChangePasswordForm = { - password: string; - confirmPassword: string; -}; diff --git a/src/app/(auth)/forgot-password/page.tsx b/src/app/(auth)/forgot-password/page.tsx deleted file mode 100644 index 5860d08..0000000 --- a/src/app/(auth)/forgot-password/page.tsx +++ /dev/null @@ -1,78 +0,0 @@ -'use client'; - -import { yupResolver } from '@hookform/resolvers/yup'; -import { useRouter } from 'next/navigation'; -import { Resolver, SubmitHandler, useForm } from 'react-hook-form'; -import * as yup from 'yup'; - -import { ForgotPasswordDTO } from './types'; - -import { Input } from '@/shared/components/FormComponents'; -import { Button } from '@/shared/components/ui/button'; -import { useToast } from '@/shared/components/ui/use-toast'; -import { ROUTE } from '@/shared/constants/routes'; -import { axios } from '@/shared/utils/axios'; - -const validationSchema = yup - .object({ - email: yup - .string() - .required('Email required') - .email('Incorrect email') - .min(5, 'Email minimum length is 5'), - }) - .required(); - -function ForgotPasswordPage() { - const { toast } = useToast(); - const router = useRouter(); - - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - resolver: yupResolver(validationSchema) as Resolver< - ForgotPasswordDTO, - unknown - >, - }); - - const onSubmit: SubmitHandler = async (data) => { - try { - await axios.post('User/forgot-password', data); - - toast({ - title: 'Success', - description: 'Check your email for changing password', - }); - - router.push(ROUTE.HOME); - } catch (error) { - toast({ - title: 'Error occurred!', - variant: 'destructive', - // description: error.errors, TODO doesn't work - }); - } - }; - return ( -
-

Change Password Form

-
- - - -
-
- ); -} - -export default ForgotPasswordPage; diff --git a/src/app/(auth)/forgot-password/types.ts b/src/app/(auth)/forgot-password/types.ts deleted file mode 100644 index c4883ee..0000000 --- a/src/app/(auth)/forgot-password/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type ForgotPasswordDTO = { - email: string; -}; diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx deleted file mode 100644 index 70a1dda..0000000 --- a/src/app/(auth)/layout.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import '../globals.css'; - -process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; - -import { ReactNode } from 'react'; - -import { Providers } from '@/shared/components'; -import { Toaster } from '@/shared/components/ui/toaster'; - -interface RootLayoutProps { - children: ReactNode; -} - -export default function RootLayout({ children }: RootLayoutProps) { - return ( - - - - -
{children}
-
- - - ); -} diff --git a/src/app/(auth)/loading.tsx b/src/app/(auth)/loading.tsx deleted file mode 100644 index 8964cdb..0000000 --- a/src/app/(auth)/loading.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { FullPageLoader } from '@/shared/components/lib'; - -export default function Loading() { - return ; -} diff --git a/src/app/(auth)/sign-in/page.tsx b/src/app/(auth)/sign-in/page.tsx deleted file mode 100644 index 0543f77..0000000 --- a/src/app/(auth)/sign-in/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import dynamic from 'next/dynamic'; - -const SignInContainer = dynamic(() => import('@/containers/sign-in'), { - ssr: false, -}); - -export default function SignInPage() { - return ; -} diff --git a/src/app/(auth)/sign-up/page.tsx b/src/app/(auth)/sign-up/page.tsx deleted file mode 100644 index a8aa4d9..0000000 --- a/src/app/(auth)/sign-up/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import dynamic from 'next/dynamic'; - -const SignUpContainer = dynamic(() => import('@/containers/sign-up'), { - ssr: false, -}); - -export default function SignUpPage() { - return ; -} diff --git a/src/app/(main)/admin/page.tsx b/src/app/(main)/admin/page.tsx deleted file mode 100644 index d69aabd..0000000 --- a/src/app/(main)/admin/page.tsx +++ /dev/null @@ -1,140 +0,0 @@ -'use client'; - -import { useRouter } from 'next/navigation'; -import { useSession } from 'next-auth/react'; -import { useCallback, useEffect, useState } from 'react'; - -import { columns } from './types/columns'; - -import { DataTable as _DataTable } from '@/shared/components'; -import { Button } from '@/shared/components/ui/button'; -import { useToast } from '@/shared/components/ui/use-toast'; -import { ROUTE } from '@/shared/constants/routes'; -import useAxiosAuth from '@/shared/hooks/useAxiosAuth'; -import { User } from '@/shared/models/user'; - -function AdminPage() { - const router = useRouter(); - const session = useSession(); - const axiosAuth = useAxiosAuth(); - const { toast } = useToast(); - - const [users, setUsers] = useState([]); - const [page, setPage] = useState(1); - - const fetchUsers = useCallback( - async (page: number) => { - try { - const res = await axiosAuth.get('User/get-all', { - params: { PageSize: 5, PageCount: page }, - }); - - const users = res.data.map((user) => ({ - ...user, - // birthDate: new Date(user.birthDate), - // registrationDate: new Date(user.registrationDate), - })); - - setUsers(users); - - toast({ - title: 'Success', - description: `Result length - ${res.data.length}`, - }); - } catch (e) { - console.error(e); - - toast({ - title: 'Error occurred!', - variant: 'destructive', - description: 'Some error occurred', - }); - } - }, - [axiosAuth, toast], - ); - - useEffect(() => { - if (session.data) { - fetchUsers(page); - } - }, [session, page, fetchUsers]); - - const onClickNextPage = () => { - if (users.length != 0) { - setPage((prevPage) => prevPage + 1); - } - }; - - const onClickPreviousPage = () => { - if (page != 1) { - setPage((prevPage) => prevPage - 1); - } - }; - - const DataTable = ( -
- <_DataTable columns={columns} data={users} /> -
- - -
-

Current page {page}

-
- ); - - const fetchWeather = async () => { - try { - const res = await axiosAuth.get('/WeatherForecast/'); - - toast({ - title: 'Success', - description: `Result length - ${res.data.length}`, - }); - } catch (e) { - console.error(e); - - toast({ - title: 'Error occurred!', - variant: 'destructive', - description: 'Some error occurred', - }); - } - }; - - return ( -
-

Admin page

- - - -
{users && DataTable}
-
- ); -} - -export default AdminPage; diff --git a/src/app/(main)/admin/set-admin/page.tsx b/src/app/(main)/admin/set-admin/page.tsx deleted file mode 100644 index 76786e5..0000000 --- a/src/app/(main)/admin/set-admin/page.tsx +++ /dev/null @@ -1,78 +0,0 @@ -'use client'; - -import { yupResolver } from '@hookform/resolvers/yup'; -import { Resolver, SubmitHandler, useForm } from 'react-hook-form'; -import * as yup from 'yup'; - -import { SetAdminDTO } from './types'; - -import { Checkbox, Input } from '@/shared/components/FormComponents'; -import { Button } from '@/shared/components/ui/button'; -import { useToast } from '@/shared/components/ui/use-toast'; -import useAxiosAuth from '@/shared/hooks/useAxiosAuth'; - -const validationSchema = yup - .object({ - userId: yup - .number() - .required('Id required') - .min(0, `Id can't be less then 0`), - isAdmin: yup.boolean().required('Show Confidential Information required'), - }) - .required(); - -function SetAdminPage() { - const { toast } = useToast(); - const axiosAuth = useAxiosAuth(); - - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - resolver: yupResolver(validationSchema) as Resolver, - }); - - const onSubmit: SubmitHandler = async (data) => { - try { - await axiosAuth.post('User/set-isadmin-value', data); - - toast({ - title: 'Success', - description: `User's admin option of user with id ${data.userId} has been set to ${data.isAdmin}`, - }); - } catch (error) { - toast({ - title: 'Error occurred!', - variant: 'destructive', - }); - } - }; - return ( -
-

Change Password Form

-
- - - - - - -
- ); -} - -export default SetAdminPage; diff --git a/src/app/(main)/admin/set-admin/types.ts b/src/app/(main)/admin/set-admin/types.ts deleted file mode 100644 index 48b00dc..0000000 --- a/src/app/(main)/admin/set-admin/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type SetAdminDTO = { userId: number; isAdmin: boolean }; diff --git a/src/app/(main)/admin/types/columns.tsx b/src/app/(main)/admin/types/columns.tsx deleted file mode 100644 index 49c6e4b..0000000 --- a/src/app/(main)/admin/types/columns.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { ColumnDef } from '@tanstack/react-table'; -import { MoreHorizontal } from 'lucide-react'; - -import { Button } from '@/shared/components/ui/button'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/shared/components/ui/dropdown-menu'; -import { User } from '@/shared/models/user'; - -export const columns: ColumnDef[] = [ - { - accessorKey: 'userId', - header: 'Id', - }, - { - accessorKey: 'login', - header: 'Login', - }, - { - accessorKey: 'showConfidentialInformation', - header: 'Show Confidential Information', - }, - { - accessorKey: 'birthDate', - header: 'Birth Date', - }, - { - accessorKey: 'email', - header: 'Email', - }, - { - accessorKey: 'registrationDate', - header: 'Registration date', - }, - { - accessorKey: 'isAdmin', - header: 'Is admin', - }, - { - id: 'actions', - cell: ({ row }) => { - const user = row.original; - - return ( - - - - - - - navigator.clipboard.writeText(user.userId.toString()) - } - > - Copy User ID - - - - ); - }, - }, -]; diff --git a/src/app/(main)/chapter/add/page.tsx b/src/app/(main)/chapter/add/page.tsx deleted file mode 100644 index 9160fc4..0000000 --- a/src/app/(main)/chapter/add/page.tsx +++ /dev/null @@ -1,95 +0,0 @@ -'use client'; - -import { yupResolver } from '@hookform/resolvers/yup'; -import { useRouter, useSearchParams } from 'next/navigation'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import * as yup from 'yup'; - -import { FormValues } from './types'; - -import { Input } from '@/shared/components/FormComponents'; -import { Button } from '@/shared/components/ui/button'; -import { useToast } from '@/shared/components/ui/use-toast'; -import useAxiosAuth from '@/shared/hooks/useAxiosAuth'; - -const validationSchema: yup.ObjectSchema = yup.object({ - title: yup - .string() - .required('No title provided.') - .min(10, 'Title too short.') - .max(124, 'Title too long.'), - chapterNumber: yup.number().required('No chapter number provided.'), - createdOn: yup.date().required('No release date provided.'), -}); - -function Page() { - const { toast } = useToast(); - const axiosAuth = useAxiosAuth(); - const router = useRouter(); - const searchParams = useSearchParams(); - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - resolver: yupResolver(validationSchema), - }); - - const mangaId = searchParams.get('mangaId'); - - const onSubmit: SubmitHandler = async (data) => { - try { - await axiosAuth.post('Chapters', { - ...data, - mangaId, - scans: '', - }); - - toast({ - title: 'Success', - description: `Chapter "${data.title}" was successfully created!`, - }); - - router.push(`/manga/${mangaId}`); - } catch (error) { - toast({ - title: 'Error occurred!', - variant: 'destructive', - }); - } - }; - - return ( -
-

Add new chapter

-
- - - - -
-
- ); -} - -export default Page; diff --git a/src/app/(main)/chapter/add/types.ts b/src/app/(main)/chapter/add/types.ts deleted file mode 100644 index 9457d10..0000000 --- a/src/app/(main)/chapter/add/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type FormValues = { - title: string; - chapterNumber: number; - createdOn: Date; -}; - -export type CreateChapterDto = { - title: string; - chapterNumber: number; - createdOn: Date; -}; diff --git a/src/app/(main)/chapter/edit/page.tsx b/src/app/(main)/chapter/edit/page.tsx deleted file mode 100644 index 74cbe3c..0000000 --- a/src/app/(main)/chapter/edit/page.tsx +++ /dev/null @@ -1,125 +0,0 @@ -'use client'; - -import { yupResolver } from '@hookform/resolvers/yup'; -import { useRouter, useSearchParams } from 'next/navigation'; -import { useEffect, useState } from 'react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import * as yup from 'yup'; - -import { FormValues } from './types'; - -import { Input } from '@/shared/components/FormComponents'; -import { Button } from '@/shared/components/ui/button'; -import { useToast } from '@/shared/components/ui/use-toast'; -import useAxiosAuth from '@/shared/hooks/useAxiosAuth'; -import Chapter from '@/shared/models/chapter'; - -const validationSchema: yup.ObjectSchema = yup.object({ - title: yup - .string() - .required('No title provided.') - .min(10, 'Title too short.') - .max(124, 'Title too long.'), - chapterNumber: yup.number().required('No chapter number provided.'), - createdOn: yup.string().required('No release date provided.'), -}); - -function Page() { - const [chapter, setChapter] = useState(null); - - const router = useRouter(); - const queryParams = useSearchParams(); - - const axiosAuth = useAxiosAuth(); - const { toast } = useToast(); - const { - register, - handleSubmit, - setValue, - formState: { errors }, - } = useForm({ - resolver: yupResolver(validationSchema), - }); - - const mangaId = queryParams.get('mangaId'); - const chapterId = queryParams.get('chapterId'); - - const fetchChapter = async () => { - const res = await axiosAuth.get(`Chapters`, { - params: { chapterId }, - }); - - setChapter(res.data); - setValue('title', res.data.title); - setValue('chapterNumber', res.data.chapterNumber); - setValue( - 'createdOn', - new Date(res.data.createdOn).toISOString().slice(0, 10), - ); - }; - - useEffect(() => { - fetchChapter(); - }, []); - - const onSubmit: SubmitHandler = async (data) => { - try { - await axiosAuth.post('Chapters', { - ...chapter, - ...data, - chapterId, - mangaId, - }); - - toast({ - title: 'Success', - description: `Chapter "${data.title}" was successfully created!`, - }); - - router.push(`/manga/${mangaId}`); - } catch (error) { - toast({ - title: 'Error occurred!', - variant: 'destructive', - }); - } - }; - - if (!chapter) { - return
Loading...
; - } - - return ( -
-

Edit chapter

-
- - - - -
-
- ); -} - -export default Page; diff --git a/src/app/(main)/chapter/edit/types.ts b/src/app/(main)/chapter/edit/types.ts deleted file mode 100644 index 2bc54ff..0000000 --- a/src/app/(main)/chapter/edit/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type FormValues = { - title: string; - chapterNumber: number; - createdOn: string; -}; - -export type CreateChapterDto = { - title: string; - chapterNumber: number; - createdOn: Date; -}; diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx deleted file mode 100644 index f18c691..0000000 --- a/src/app/(main)/layout.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import '../globals.css'; - -import dynamic from 'next/dynamic'; -import { ReactNode } from 'react'; - -const Sidebar = dynamic( - () => import('@/shared/components/lib/Sidebar/Sidebar'), - { - ssr: false, - }, -); -import { Header, Providers } from '@/shared/components'; -import { Toaster } from '@/shared/components/ui/toaster'; - -process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; - -interface RootLayoutProps { - children: ReactNode; -} - -export default function RootLayout({ children }: RootLayoutProps) { - return ( - - - -
- - -
{children}
- - - - ); -} diff --git a/src/app/(main)/loading.tsx b/src/app/(main)/loading.tsx deleted file mode 100644 index 8964cdb..0000000 --- a/src/app/(main)/loading.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { FullPageLoader } from '@/shared/components/lib'; - -export default function Loading() { - return ; -} diff --git a/src/app/(main)/manga/[mangaId]/[chapterId]/MangaPDF.tsx b/src/app/(main)/manga/[mangaId]/[chapterId]/MangaPDF.tsx deleted file mode 100644 index 82a9753..0000000 --- a/src/app/(main)/manga/[mangaId]/[chapterId]/MangaPDF.tsx +++ /dev/null @@ -1,40 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { Document, Page, pdfjs } from 'react-pdf'; - -import bytesToFile from '@/shared/utils/bytesToFile'; - -import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; -import 'react-pdf/dist/esm/Page/TextLayer.css'; - -pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`; - -type MangaPDFProps = { - chapter: string; -}; - -function MangaPDF({ chapter }: MangaPDFProps) { - const [numPages, setNumPages] = useState(); - - function onDocumentLoadSuccess({ numPages }: { numPages: number }): void { - setNumPages(numPages); - } - - return ( - - {Array.from(new Array(numPages), (_, index) => ( - - ))} - - ); -} - -export default MangaPDF; diff --git a/src/app/(main)/manga/[mangaId]/[chapterId]/page.tsx b/src/app/(main)/manga/[mangaId]/[chapterId]/page.tsx deleted file mode 100644 index 21f2daa..0000000 --- a/src/app/(main)/manga/[mangaId]/[chapterId]/page.tsx +++ /dev/null @@ -1,50 +0,0 @@ -'use client'; - -import { useEffect, useState } from 'react'; - -import MangaPDF from './MangaPDF'; - -import useAxiosAuth from '@/shared/hooks/useAxiosAuth'; -import Chapter from '@/shared/models/chapter'; - -type PageProps = { - params: { - mangaId: string; - chapterId: string; - }; -}; - -function ChapterPage({ params: { chapterId } }: PageProps) { - const [chapter, setChapter] = useState(null); - - const axiosAuth = useAxiosAuth(); - - const fetchChapter = async () => { - const res = await axiosAuth.get(`Chapters`, { - params: { chapterId }, - }); - - return res.data; - }; - - useEffect(() => { - fetchChapter() - .then((chapter) => { - setChapter(chapter); - }) - .catch((error) => { - console.log(error); - }); - }, [chapterId]); - - return ( -
-

Chapter info

-

Chapter title {chapter?.title}

-

Scans available {chapter?.scans ? 'yes' : 'no'}

- {chapter?.scans && } -
- ); -} - -export default ChapterPage; diff --git a/src/app/(main)/manga/[mangaId]/components/ChapterCard.tsx b/src/app/(main)/manga/[mangaId]/components/ChapterCard.tsx deleted file mode 100644 index 348c743..0000000 --- a/src/app/(main)/manga/[mangaId]/components/ChapterCard.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { LucideUpload, Edit, X } from 'lucide-react'; -import { useRouter } from 'next/navigation'; -import { useSession } from 'next-auth/react'; -import { useRef } from 'react'; - -import { ChapterDTO } from '../types'; - -import { Button } from '@/shared/components/ui/button'; -import { Card } from '@/shared/components/ui/card'; -import { useToast } from '@/shared/components/ui/use-toast'; -import { ROUTE } from '@/shared/constants/routes'; -import useAxiosAuth from '@/shared/hooks/useAxiosAuth'; - -type ChapterCardProps = { - chapter: ChapterDTO; - index: number; - mangaId: string; - refetchData: () => void; -}; - -function ChapterCard({ - chapter, - index, - mangaId, - refetchData, -}: ChapterCardProps) { - const fileInputRef = useRef(null); - const axiosAuth = useAxiosAuth(); - const { toast } = useToast(); - const router = useRouter(); - - const { data: session } = useSession(); - - const handleEdit = async () => { - router.push( - `${ROUTE.EDIT_CHAPTER}?chapterId=${chapter.chapterId}&mangaId=${mangaId}`, - ); - }; - - const handleDelete = async () => { - try { - await axiosAuth.delete('/Chapters', { - params: { chapterId: chapter.chapterId }, - }); - - toast({ - title: 'Success', - description: 'Chapter has been deleted', - }); - - refetchData(); - } catch (error) { - toast({ - title: 'Error', - variant: 'destructive', - description: 'Something went wrong.', - }); - } - }; - - const handleImageChange = async ( - event: React.ChangeEvent, - ) => { - const file = event.target.files?.[0]; - - if (!file) return; - - const formData = new FormData(); - formData.append('ChapterId', chapter.chapterId); - formData.append('ChapterFile', file); - - try { - await axiosAuth.post('/Chapters/upload', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - toast({ - title: 'Success', - description: 'Chapter scans has been uploaded', - }); - } catch (error) { - toast({ - title: 'Error', - variant: 'destructive', - description: 'Something went wrong.', - }); - } - }; - - return ( -
- { - router.push(`${ROUTE.MANGA}/${mangaId}/${chapter.chapterId}`); - }} - > -

- {index + 1}: {chapter.title} -

-
- {session?.user?.accessToken ? ( - <> - - - - - - ) : null} -
- ); -} - -export default ChapterCard; diff --git a/src/app/(main)/manga/[mangaId]/components/Chapters.tsx b/src/app/(main)/manga/[mangaId]/components/Chapters.tsx deleted file mode 100644 index 1c286f4..0000000 --- a/src/app/(main)/manga/[mangaId]/components/Chapters.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { BookPlusIcon } from 'lucide-react'; -import { useRouter } from 'next/navigation'; -import { useSession } from 'next-auth/react'; -import { useEffect, useState } from 'react'; - -import ChapterCard from './ChapterCard'; -import { ChapterDTO } from '../types'; - -import { BeatLoader } from '@/shared/components/lib'; -import { Button } from '@/shared/components/ui/button'; -import { axios } from '@/shared/utils/axios'; - -type ChaptersProps = { - mangaId: string; -}; - -function Chapters({ mangaId }: ChaptersProps) { - const [chapters, setChapters] = useState([]); - - const router = useRouter(); - const { data: session } = useSession(); - - const fetchChapters = async () => { - const res = await axios.get(`Chapters/get-manga-chapters`, { - params: { mangaId }, - }); - - setChapters(res.data); - }; - - useEffect(() => { - fetchChapters(); - }, []); - - return ( -
-

Chapters

-
- {session?.user?.accessToken ? ( - - ) : null} -
-
- {chapters?.length !== 0 ? ( - chapters?.map((chapter, index) => ( - - )) - ) : ( -
- -
- )} -
-
- ); -} - -export default Chapters; diff --git a/src/app/(main)/manga/[mangaId]/components/CommentCard.tsx b/src/app/(main)/manga/[mangaId]/components/CommentCard.tsx deleted file mode 100644 index 47d6ce7..0000000 --- a/src/app/(main)/manga/[mangaId]/components/CommentCard.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Edit, X } from 'lucide-react'; -import { useSession } from 'next-auth/react'; - -import { Button } from '@/shared/components/ui/button'; -import { Card } from '@/shared/components/ui/card'; -import { useToast } from '@/shared/components/ui/use-toast'; -import useAxiosAuth from '@/shared/hooks/useAxiosAuth'; -import Comment from '@/shared/models/comment'; - -type CommentCardProps = { - comment: Comment; - onDelete?: () => void; - onUpdate?: (comment: Comment) => void; -}; - -function CommentCard({ comment, onDelete, onUpdate }: CommentCardProps) { - const axiosAuth = useAxiosAuth(); - const { toast } = useToast(); - const { data: session } = useSession(); - - const handleEdit = async () => { - onUpdate(comment); - }; - - const handleDelete = async () => { - try { - await axiosAuth.delete(`/Comments/${comment.id}`); - - toast({ - title: 'Success', - description: 'Comment has been deleted', - }); - - onDelete(); - } catch (error) { - toast({ - title: 'Error', - variant: 'destructive', - description: 'Something went wrong.', - }); - } - }; - - return ( -
- -
-

- {comment.login}{' '} - {comment.userId == session?.user?.id ? ( - (You) - ) : null} -

-
{comment.body}
-

- {new Date(comment.createdDate).toLocaleString()} -

-
- {comment.userId == session?.user?.id ? ( -
- - -
- ) : null} -
-
- ); -} - -export default CommentCard; diff --git a/src/app/(main)/manga/[mangaId]/components/CommentForm.tsx b/src/app/(main)/manga/[mangaId]/components/CommentForm.tsx deleted file mode 100644 index 0bf8c1a..0000000 --- a/src/app/(main)/manga/[mangaId]/components/CommentForm.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { yupResolver } from '@hookform/resolvers/yup'; -import React from 'react'; -import { useForm } from 'react-hook-form'; -import * as yup from 'yup'; - -import { Button } from '@/shared/components/ui/button'; -import { Textarea } from '@/shared/components/ui/textarea'; -import { useToast } from '@/shared/components/ui/use-toast'; -import useAxiosAuth from '@/shared/hooks/useAxiosAuth'; - -const schema = yup.object().shape({ - comment: yup.string().required('Comment is required'), -}); - -type CommentFormProps = { - mangaId: string; - refetchData?: () => void; -}; - -function CommentForm({ mangaId, refetchData }: CommentFormProps) { - const axiosAuth = useAxiosAuth(); - const { toast } = useToast(); - - const { - register, - handleSubmit, - formState: { errors }, - setValue, - } = useForm({ - resolver: yupResolver(schema), - }); - - const onSubmit = async (data: { comment: string }) => { - try { - await axiosAuth.post('/Comments', { - body: data.comment, - mangaId, - }); - - toast({ - title: 'Success', - description: 'Comment has been added', - }); - - setValue('comment', ''); - refetchData(); - } catch (error) { - console.error(error); - - toast({ - title: 'Error', - variant: 'destructive', - description: 'Something went wrong.', - }); - } - }; - - return ( -
-