diff --git a/package.json b/package.json index d22580a..4e92355 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "prisma:generate": "prisma generate" }, "dependencies": { - "@aulasoftwarelibre/next-auth-firewall": "^1.0.1", + "@aulasoftwarelibre/next-auth-firewall": "^1.0.2", "@auth/core": "^0.0.0-manual.e9863699", "@auth/prisma-adapter": "^1.0.8", "@heroicons/react": "^2.0.18", @@ -76,6 +76,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-sort": "^2.11.0", "eslint-plugin-storybook": "^0.6.15", + "eslint-plugin-unicorn": "^49.0.0", "eslint-plugin-unused-imports": "^3.0.0", "husky": "^8.0.3", "jest": "^29.7.0", @@ -108,6 +109,7 @@ }, "extends": [ "next", + "plugin:unicorn/recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", "plugin:storybook/recommended" @@ -146,14 +148,15 @@ "import/first": "error", "import/newline-after-import": "error", "import/no-duplicates": "error", + "neverthrow/must-use-result": "error", "no-shadow": "off", "simple-import-sort/exports": "error", "simple-import-sort/imports": "error", "sort/destructuring-properties": "error", "sort/object-properties": "error", "sort/type-properties": "error", - "unused-imports/no-unused-imports": "error", - "neverthrow/must-use-result": "error" + "unicorn/prefer-node-protocol": "off", + "unused-imports/no-unused-imports": "error" } } } diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx index 2ea54ac..fd068d5 100644 --- a/src/app/(auth)/layout.tsx +++ b/src/app/(auth)/layout.tsx @@ -1,6 +1,6 @@ import React from 'react' -import AuthModal from '@/components/AuthModal/AuthModal' +import AuthModal from '@/components/auth-modal' export default function RootLayout({ children, diff --git a/src/app/(auth)/signin/page.tsx b/src/app/(auth)/signin/page.tsx index c8cc347..6b0bb96 100644 --- a/src/app/(auth)/signin/page.tsx +++ b/src/app/(auth)/signin/page.tsx @@ -3,14 +3,15 @@ import { Metadata } from 'next' import { redirect } from 'next/navigation' import React from 'react' -import AuthModalFooter from '@/components/AuthModal/AuthModalFooter' -import AuthModalHeader from '@/components/AuthModal/AuthModalHeader' -import SignInEmailForm from '@/components/SignInEmailForm/SignInEmailForm' +import AuthModalFooter from '@/components/auth-modal/auth-modal-footer' +import AuthModalHeader from '@/components/auth-modal/auth-modal-header' import { auth } from '@/lib/auth/auth' +import SignInEmailForm from '../../../components/sign-in-email-form' + export const metadata: Metadata = { description: 'Biblioteca del Aula de Software Libre', - title: 'Codex | Login', + title: 'Iniciar sesión | Codex', } export default async function Page() { diff --git a/src/app/(auth)/signout/page.tsx b/src/app/(auth)/signout/page.tsx index 64a4a39..1b195ba 100644 --- a/src/app/(auth)/signout/page.tsx +++ b/src/app/(auth)/signout/page.tsx @@ -2,13 +2,13 @@ import { Metadata } from 'next' import { redirect } from 'next/navigation' import React from 'react' -import AuthModalHeader from '@/components/AuthModal/AuthModalHeader' -import SignOutForm from '@/components/SignOutForm/SignOutForm' +import AuthModalHeader from '@/components/auth-modal/auth-modal-header' +import Index from '@/components/sign-out-form' import { auth } from '@/lib/auth/auth' export const metadata: Metadata = { description: 'Biblioteca del Aula de Software Libre', - title: 'Codex | Cerrar sesión', + title: 'Cerrar sesión | Codex', } export default async function Page() { @@ -21,7 +21,7 @@ export default async function Page() { return ( <> - + ) diff --git a/src/app/(auth)/verify/page.tsx b/src/app/(auth)/verify/page.tsx index 4ae9e6e..b4dba55 100644 --- a/src/app/(auth)/verify/page.tsx +++ b/src/app/(auth)/verify/page.tsx @@ -4,13 +4,13 @@ import Link from 'next/link' import { redirect } from 'next/navigation' import React from 'react' -import AuthModalFooter from '@/components/AuthModal/AuthModalFooter' -import AuthModalHeader from '@/components/AuthModal/AuthModalHeader' +import AuthModalFooter from '@/components/auth-modal/auth-modal-footer' +import AuthModalHeader from '@/components/auth-modal/auth-modal-header' import { auth } from '@/lib/auth/auth' export const metadata: Metadata = { description: 'Biblioteca del Aula de Software Libre', - title: 'Codex | Verifica tu correo', + title: 'Verifica tu correo | Codex', } const WEBMAIL_URL = process.env.WEBMAIL_URL as string diff --git a/src/app/api/mailer/route.ts b/src/app/api/mailer/route.ts index 66e1c39..66c99d5 100644 --- a/src/app/api/mailer/route.ts +++ b/src/app/api/mailer/route.ts @@ -21,11 +21,11 @@ export async function POST(request: Request) { ) } - const params: SendVerificationRequestParams = await request.json() + const parameters: SendVerificationRequestParams = await request.json() await new SmtpClient( process.env.MAILER_DSN as string, - ).sendVerificationRequest(params) + ).sendVerificationRequest(parameters) return Response.json({ success: true }) } diff --git a/src/app/books/new/page.tsx b/src/app/books/new/page.tsx index 0d16573..3e401e3 100644 --- a/src/app/books/new/page.tsx +++ b/src/app/books/new/page.tsx @@ -1,13 +1,24 @@ -import BookForm from '@/components/BookForm/BookForm' -import { createBook } from '@/core/book/infrastructure/actions' +import { Card, CardBody, CardHeader } from '@nextui-org/react' +import { Metadata } from 'next' + +import BookForm from '@/components/book-form' + +export const metadata: Metadata = { + description: 'Biblioteca del Aula de Software Libre', + title: 'Añadir un nuevo libro | Codex', +} export default async function Page() { return (
-

- Crear Nuevo Libro -

- + + +

Añadir un nuevo libro

+
+ + + +
) } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f5c05cc..9a22964 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,8 +3,8 @@ import './globals.css' import type { Metadata } from 'next' import { Nunito } from 'next/font/google' -import Header from '@/components/Header/Header' -import { Providers } from '@/components/Providers/Providers' +import Header from '@/components/header' +import { Providers } from '@/components/providers' import { findUser } from '@/core/user/infrastructure/actions' import { auth } from '@/lib/auth/auth' @@ -30,7 +30,7 @@ export default async function RootLayout({ >
-
{children}
+
{children}
diff --git a/src/app/page.tsx b/src/app/page.tsx index ca21627..e4c52c1 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,5 @@ -import BookGrid from '@/components/BookGrid/BookGrid' -import { findBooks } from '@/core/book/infrastructure/actions' +import BookGrid from '@/components/book-grid' +import { findBooks } from '@/core/book/infrastructure/actions/find-books' export default async function Home() { const books = await findBooks() diff --git a/src/app/settings/profile/page.tsx b/src/app/settings/profile/page.tsx index 88e2002..76ad49b 100644 --- a/src/app/settings/profile/page.tsx +++ b/src/app/settings/profile/page.tsx @@ -1,8 +1,8 @@ import { redirect } from 'next/navigation' -import EditProfileForm from '@/components/EditProfileForm/EditProfileForm' +import EditProfileForm from '@/components/edit-profile-form' import { FindUserResponse } from '@/core/user/application/types' -import { findUser, updateUser } from '@/core/user/infrastructure/actions' +import { findUser } from '@/core/user/infrastructure/actions' import { auth } from '@/lib/auth/auth' export default async function Page() { @@ -16,7 +16,7 @@ export default async function Page() { return ( <> - + ) } diff --git a/src/components/AuthModal/AuthModalFooter.tsx b/src/components/AuthModal/AuthModalFooter.tsx deleted file mode 100644 index 457bd29..0000000 --- a/src/components/AuthModal/AuthModalFooter.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { CardFooter } from '@nextui-org/react' -import React, { ReactNode } from 'react' - -interface AuthModalFooterProps { - children: ReactNode -} - -function useController(props: AuthModalFooterProps) { - return { ...props } -} - -export default function AuthModalFooter(props: AuthModalFooterProps) { - const { children } = useController(props) - return ( - -

{children}

-
- ) -} diff --git a/src/components/BookForm/BookForm.tsx b/src/components/BookForm/BookForm.tsx deleted file mode 100644 index ae6d0ff..0000000 --- a/src/components/BookForm/BookForm.tsx +++ /dev/null @@ -1,80 +0,0 @@ -'use client' - -import { Divider, Input } from '@nextui-org/react' -import { useEffect } from 'react' -import { useFormState } from 'react-dom' -import toast from 'react-hot-toast' - -import SubmitButton from '@/components/SubmitButton/SubmitButton' -import Toast from '@/components/Toast/Toast' -import { - CreateBookResponse, - FindBookResponse, -} from '@/core/book/application/types' - -interface BookFormProps { - book?: FindBookResponse - create: ( - prevState: unknown, - formData: FormData, - ) => Promise -} - -export default function BookForm(props: BookFormProps) { - //const { create, book } = props - const { create } = props - const [state, action] = useFormState(create, undefined) - - useEffect(() => { - if (state?.success) { - toast((t) => ( - toast.dismiss(t.id)} - /> - )) - } - }, [state]) - - return ( - <> -
-
- - - - -
-
- -
-
- - ) -} diff --git a/src/components/BookGrid/BookGrid.tsx b/src/components/BookGrid/BookGrid.tsx deleted file mode 100644 index 183bc75..0000000 --- a/src/components/BookGrid/BookGrid.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { FindBookResponse } from '@/core/book/application/types' - -import Book from '../Book/Book' - -export interface BookGridProps { - books: FindBookResponse[] -} - -export default function BookGrid(props: BookGridProps) { - return ( - <> -
- {props.books.map((book) => ( - ({})} /> - ))} -
- - ) -} diff --git a/src/components/auth-modal/auth-modal-footer.tsx b/src/components/auth-modal/auth-modal-footer.tsx new file mode 100644 index 0000000..af7c623 --- /dev/null +++ b/src/components/auth-modal/auth-modal-footer.tsx @@ -0,0 +1,19 @@ +import { CardFooter } from '@nextui-org/react' +import React, { ReactNode } from 'react' + +interface AuthModalFooterProperties { + children: ReactNode +} + +function useController(properties: AuthModalFooterProperties) { + return { ...properties } +} + +export default function AuthModalFooter(properties: AuthModalFooterProperties) { + const { children } = useController(properties) + return ( + +

{children}

+
+ ) +} diff --git a/src/components/AuthModal/AuthModalHeader.tsx b/src/components/auth-modal/auth-modal-header.tsx similarity index 73% rename from src/components/AuthModal/AuthModalHeader.tsx rename to src/components/auth-modal/auth-modal-header.tsx index d332fa9..fb2540f 100644 --- a/src/components/AuthModal/AuthModalHeader.tsx +++ b/src/components/auth-modal/auth-modal-header.tsx @@ -1,13 +1,13 @@ import { CardBody } from '@nextui-org/react' import React, { ReactNode } from 'react' -interface AuthModalHeaderProps { +interface AuthModalHeaderProperties { children: ReactNode title: string } -export default function AuthModalHeader(props: AuthModalHeaderProps) { - const { children, title } = { ...props } +export default function AuthModalHeader(properties: AuthModalHeaderProperties) { + const { children, title } = { ...properties } return (

diff --git a/src/components/AuthModal/AuthModal.tsx b/src/components/auth-modal/index.tsx similarity index 75% rename from src/components/AuthModal/AuthModal.tsx rename to src/components/auth-modal/index.tsx index 0712435..4f5e4fb 100644 --- a/src/components/AuthModal/AuthModal.tsx +++ b/src/components/auth-modal/index.tsx @@ -1,20 +1,20 @@ import { Card, CardHeader } from '@nextui-org/react' import React, { ReactNode } from 'react' -import ThemeImage from '@/components/ThemeImage/ThemeImage' +import Index from '@/components/theme-image' -interface AuthModalProps { +interface AuthModalProperties { children: ReactNode } -export default function AuthModal(props: AuthModalProps) { - const { children } = { ...props } +export default function AuthModal(properties: AuthModalProperties) { + const { children } = { ...properties } return ( <>
- { + if (state?.success) { + toast((t) => ( + toast.dismiss(t.id)} + /> + )) + } + }, [state]) + + return ( + <> +
+ + + + +
+ +
+ + + ) +} diff --git a/src/components/book-grid/index.tsx b/src/components/book-grid/index.tsx new file mode 100644 index 0000000..a23edd9 --- /dev/null +++ b/src/components/book-grid/index.tsx @@ -0,0 +1,18 @@ +import Book from '@/components/book' +import { BookDTO } from '@/core/book/application/types' + +export interface BookGridProperties { + books: BookDTO[] +} + +export default function BookGrid(properties: BookGridProperties) { + return ( + <> +
+ {properties.books.map((book) => ( + ({})} /> + ))} +
+ + ) +} diff --git a/src/components/Book/Book.tsx b/src/components/book/index.tsx similarity index 87% rename from src/components/Book/Book.tsx rename to src/components/book/index.tsx index 4f1d7d7..4dee2a8 100644 --- a/src/components/Book/Book.tsx +++ b/src/components/book/index.tsx @@ -7,20 +7,20 @@ import { Image, } from '@nextui-org/react' -import { FindBookResponse } from '@/core/book/application/types' +import { BookDTO } from '@/core/book/application/types' import { findBorrow } from '@/core/borrow/infrastructure/actions' import gravatar from '@/lib/utils/gravatar' -export interface BookProps { - book: FindBookResponse +export interface BookProperties { + book: BookDTO onBorrow: (id: string) => void } -export default async function Book(props: BookProps) { +export default async function Book(properties: BookProperties) { const { book: { authors, id, image, title }, onBorrow, - } = props + } = properties const { reader } = await findBorrow(id) const footer = reader ? ( diff --git a/src/components/EditProfileForm/EditProfileForm.tsx b/src/components/edit-profile-form/index.tsx similarity index 53% rename from src/components/EditProfileForm/EditProfileForm.tsx rename to src/components/edit-profile-form/index.tsx index 6210471..912584a 100644 --- a/src/components/EditProfileForm/EditProfileForm.tsx +++ b/src/components/edit-profile-form/index.tsx @@ -1,32 +1,30 @@ 'use client' -import { Divider, Input } from '@nextui-org/react' +import { Divider } from '@nextui-org/react' import { useEffect } from 'react' import { useFormState } from 'react-dom' import toast from 'react-hot-toast' -import SubmitButton from '@/components/SubmitButton/SubmitButton' -import Toast from '@/components/Toast/Toast' +import InputForm from '@/components/input-form' +import Index from '@/components/submit-button' +import Toast from '@/components/toast' import { FindUserResponse } from '@/core/user/application/types' -import { UpdateUserResponse } from '@/core/user/infrastructure/actions' +import { updateUser } from '@/core/user/infrastructure/actions' +import FormResponse from '@/lib/zod/form-response' -interface EditProfileFormProps { - update: ( - prevState: unknown, - formData: FormData, - ) => Promise +interface EditProfileFormProperties { user: FindUserResponse } -export default function EditProfileForm(props: EditProfileFormProps) { - const { update, user } = props - const [state, action] = useFormState(update, undefined) +export default function EditProfileForm(properties: EditProfileFormProperties) { + const { user } = properties + const [state, action] = useFormState(updateUser, FormResponse.initialState()) useEffect(() => { if (state?.success) { toast((t) => ( toast.dismiss(t.id)} /> )) @@ -37,30 +35,25 @@ export default function EditProfileForm(props: EditProfileFormProps) { <>
- -
- +
diff --git a/src/components/Header/HeaderAuthenticatedMenu.tsx b/src/components/header/header-authenticated-menu.tsx similarity index 83% rename from src/components/Header/HeaderAuthenticatedMenu.tsx rename to src/components/header/header-authenticated-menu.tsx index d88a17c..b317adc 100644 --- a/src/components/Header/HeaderAuthenticatedMenu.tsx +++ b/src/components/header/header-authenticated-menu.tsx @@ -1,6 +1,7 @@ import { ArrowLeftOnRectangleIcon, BookOpenIcon, + PlusIcon, UserIcon, } from '@heroicons/react/24/outline' import { @@ -16,17 +17,17 @@ import { useRouter } from 'next/navigation' import { FindUserResponse } from '@/core/user/application/types' import gravatar from '@/lib/utils/gravatar' -interface HeaderAuthenticatedMenuProps { +interface HeaderAuthenticatedMenuProperties { user: FindUserResponse } export default function HeaderAuthenticatedMenu( - props: HeaderAuthenticatedMenuProps, + properties: HeaderAuthenticatedMenuProperties, ) { const router = useRouter() const { user: { email, image, name }, - } = props + } = properties return ( <> @@ -54,6 +55,13 @@ export default function HeaderAuthenticatedMenu( }} /> + } + onClick={() => router.push('/books/new')} + > + Añadir libro + } diff --git a/src/components/Header/HeaderUnauthenticatedMenu.tsx b/src/components/header/header-unauthenticated-menu.tsx similarity index 100% rename from src/components/Header/HeaderUnauthenticatedMenu.tsx rename to src/components/header/header-unauthenticated-menu.tsx diff --git a/src/components/Header/Header.tsx b/src/components/header/index.tsx similarity index 63% rename from src/components/Header/Header.tsx rename to src/components/header/index.tsx index b26e71f..b92e4ac 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/header/index.tsx @@ -8,17 +8,17 @@ import { } from '@nextui-org/react' import Link from 'next/link' -import HeaderAuthenticatedMenu from '@/components/Header/HeaderAuthenticatedMenu' -import HeaderUnauthenticatedMenu from '@/components/Header/HeaderUnauthenticatedMenu' -import ThemeSwitcher from '@/components/ThemeSwitcher/ThemeSwitcher' +import HeaderAuthenticatedMenu from '@/components/header/header-authenticated-menu' +import HeaderUnauthenticatedMenu from '@/components/header/header-unauthenticated-menu' +import Index from '@/components/theme-switcher' import { FindUserResponse } from '@/core/user/application/types' -interface HeaderProps { - user: FindUserResponse | null +interface HeaderProperties { + user?: FindUserResponse } -export default function Header(props: HeaderProps) { - const { user } = props +export default function Header(properties: HeaderProperties) { + const { user } = properties const AuthNavbarItem = user ? ( @@ -36,7 +36,7 @@ export default function Header(props: HeaderProps) { - + {AuthNavbarItem} diff --git a/src/components/input-form/index.tsx b/src/components/input-form/index.tsx new file mode 100644 index 0000000..77b04f6 --- /dev/null +++ b/src/components/input-form/index.tsx @@ -0,0 +1,30 @@ +import { Input, InputProps } from '@nextui-org/react' +import { ZodIssue } from 'zod' + +type FormInputProperties = Omit & { + errors?: ZodIssue[] + label: string +} + +export default function InputForm(properties: FormInputProperties) { + const { errors = [], label, name, ...rest } = properties + const errorMessage = errors + .filter((error) => error.path[0] === name) + .map((error) => error.message) + .join(', ') + + return ( + <> + + + ) +} diff --git a/src/components/Providers/Providers.tsx b/src/components/providers/index.tsx similarity index 100% rename from src/components/Providers/Providers.tsx rename to src/components/providers/index.tsx diff --git a/src/components/SignInEmailForm/actions.ts b/src/components/sign-in-email-form/actions.ts similarity index 94% rename from src/components/SignInEmailForm/actions.ts rename to src/components/sign-in-email-form/actions.ts index 98d4ca8..ad71369 100644 --- a/src/components/SignInEmailForm/actions.ts +++ b/src/components/sign-in-email-form/actions.ts @@ -12,7 +12,7 @@ const FormSchema = z.object({ }) export async function form( - prevState: ZodIssue[] | undefined, + previousState: ZodIssue[] | undefined, formData: FormData, ) { try { diff --git a/src/components/SignInEmailForm/SignInEmailForm.tsx b/src/components/sign-in-email-form/index.tsx similarity index 92% rename from src/components/SignInEmailForm/SignInEmailForm.tsx rename to src/components/sign-in-email-form/index.tsx index 61e1ec1..a3a1846 100644 --- a/src/components/SignInEmailForm/SignInEmailForm.tsx +++ b/src/components/sign-in-email-form/index.tsx @@ -6,11 +6,12 @@ import React from 'react' import { useFormState } from 'react-dom' import { ZodIssue } from 'zod' -import { form } from '@/components/SignInEmailForm/actions' +import { form } from '@/components/sign-in-email-form/actions' export default function SignInEmailForm() { const [code, action] = useFormState(form, []) + // eslint-disable-next-line unicorn/no-array-reduce const errors = code.reduce( (map, error) => { map[error.path.toString()] = error diff --git a/src/components/SignOutForm/actions.ts b/src/components/sign-out-form/actions.ts similarity index 100% rename from src/components/SignOutForm/actions.ts rename to src/components/sign-out-form/actions.ts diff --git a/src/components/SignOutForm/SignOutForm.tsx b/src/components/sign-out-form/index.tsx similarity index 64% rename from src/components/SignOutForm/SignOutForm.tsx rename to src/components/sign-out-form/index.tsx index 4f049a2..ff2fbdd 100644 --- a/src/components/SignOutForm/SignOutForm.tsx +++ b/src/components/sign-out-form/index.tsx @@ -3,14 +3,12 @@ import { ArrowRightOnRectangleIcon } from '@heroicons/react/24/outline' import { Button } from '@nextui-org/react' import React from 'react' -import { useFormState } from 'react-dom' -import { form } from '@/components/SignOutForm/actions' +import { form } from '@/components/sign-out-form/actions' -export default function SignOutForm() { - const [, action] = useFormState(form, undefined) +export default function Index() { return ( -
+ diff --git a/src/components/ThemeImage/hooks.ts b/src/components/theme-image/hooks.ts similarity index 100% rename from src/components/ThemeImage/hooks.ts rename to src/components/theme-image/hooks.ts diff --git a/src/components/ThemeImage/ThemeImage.tsx b/src/components/theme-image/index.tsx similarity index 71% rename from src/components/ThemeImage/ThemeImage.tsx rename to src/components/theme-image/index.tsx index 4ca8c0c..ec9cad2 100644 --- a/src/components/ThemeImage/ThemeImage.tsx +++ b/src/components/theme-image/index.tsx @@ -2,16 +2,16 @@ import Image, { ImageProps } from 'next/image' -import { useController } from '@/components/ThemeSwitcher/hooks' +import { useController } from '@/components/theme-switcher/hooks' -type ThemeImageProps = Omit & { +type ThemeImageProperties = Omit & { srcDark: string srcLight: string } -export default function ThemeImage(props: ThemeImageProps) { +export default function Index(properties: ThemeImageProperties) { const { mounted, theme } = useController() - const { alt, srcDark, srcLight, ...rest } = props + const { alt, srcDark, srcLight, ...rest } = properties if (!mounted) return ( diff --git a/src/components/ThemeSwitcher/hooks.ts b/src/components/theme-switcher/hooks.ts similarity index 100% rename from src/components/ThemeSwitcher/hooks.ts rename to src/components/theme-switcher/hooks.ts diff --git a/src/components/ThemeSwitcher/ThemeSwitcher.tsx b/src/components/theme-switcher/index.tsx similarity index 77% rename from src/components/ThemeSwitcher/ThemeSwitcher.tsx rename to src/components/theme-switcher/index.tsx index 5b8b750..7dcb942 100644 --- a/src/components/ThemeSwitcher/ThemeSwitcher.tsx +++ b/src/components/theme-switcher/index.tsx @@ -3,12 +3,12 @@ import { MoonIcon, SunIcon } from '@heroicons/react/24/solid' import { Switch } from '@nextui-org/react' -import { useController } from '@/components/ThemeSwitcher/hooks' +import { useController } from '@/components/theme-switcher/hooks' -export default function ThemeSwitcher() { +export default function Index() { const { mounted, setTheme, theme } = useController() - if (!mounted) return null + if (!mounted) return return ( } -export default function Toast(props: ToastProps) { - const { message, onCloseHandler } = props +export default function Toast(properties: ToastProperties) { + const { message, onCloseHandler } = properties return ( <> diff --git a/src/core/book/application/find-books.use-case.ts b/src/core/book/application/find-books.use-case.ts index 73a796a..c3071c0 100644 --- a/src/core/book/application/find-books.use-case.ts +++ b/src/core/book/application/find-books.use-case.ts @@ -1,11 +1,11 @@ import Books from '@/core/book/domain/services/books.repository' -import { FindBookResponse } from './types' +import { BookDTO } from './types' export default class FindBooksUseCase { constructor(private readonly booksRepository: Books) {} - async with(): Promise { + async with(): Promise { const books = await this.booksRepository.findAll() return books.map((book) => ({ diff --git a/src/core/book/application/types.ts b/src/core/book/application/types.ts index ecf07e6..c7a8807 100644 --- a/src/core/book/application/types.ts +++ b/src/core/book/application/types.ts @@ -1,17 +1,5 @@ import BookId from '@/core/book/domain/model/id.value-object' -export interface CreateBookResponse { - message: string - success: boolean -} - -export interface FindBookResponse { - authors: string[] - id: string - image: string - title: string -} - export class CreateBookCommand { constructor( public readonly id: string, @@ -21,8 +9,25 @@ export class CreateBookCommand { ) {} } +export interface BookDTO { + authors: string[] + id: string + image: string + title: string +} + export class BookError extends Error { + constructor( + message: string, + public readonly type: string, + ) { + super(message) + } + static becauseAlreadyExists(id: BookId) { - return new BookError(`Book with id ${id.value} already exists`) + return new BookError( + `Book with id ${id.value} already exists`, + 'DUPLICATE_NAME', + ) } } diff --git a/src/core/book/domain/model/author.value-object.ts b/src/core/book/domain/model/author.value-object.ts index 1da206d..df0c529 100644 --- a/src/core/book/domain/model/author.value-object.ts +++ b/src/core/book/domain/model/author.value-object.ts @@ -3,10 +3,10 @@ export class BookAuthor { public static create(author: string): BookAuthor { if (author === undefined) { - throw Error('Author can not be undefined') + throw new Error('Author can not be undefined') } if (author.length < 3) { - throw Error('Author is too short') + throw new Error('Author is too short') } return new BookAuthor(author) diff --git a/src/core/book/domain/model/book.entity.ts b/src/core/book/domain/model/book.entity.ts index 30211dd..1c7d17a 100644 --- a/src/core/book/domain/model/book.entity.ts +++ b/src/core/book/domain/model/book.entity.ts @@ -18,12 +18,12 @@ export default class Book { title: string, image: string, ): Book { - const idObj = BookId.create(id) - const titleObj = BookTitle.create(title) - const imageObj = BookImage.create(image) - const authorObj = authors.map((item) => BookAuthor.create(item)) + const idObject = BookId.create(id) + const titleObject = BookTitle.create(title) + const imageObject = BookImage.create(image) + const authorObject = authors.map((item) => BookAuthor.create(item)) - return new Book(idObj, titleObj, authorObj, imageObj) + return new Book(idObject, titleObject, authorObject, imageObject) } get id(): string { diff --git a/src/core/book/domain/model/image.value-object.ts b/src/core/book/domain/model/image.value-object.ts index d48a41d..d10f1ea 100644 --- a/src/core/book/domain/model/image.value-object.ts +++ b/src/core/book/domain/model/image.value-object.ts @@ -8,7 +8,7 @@ export default class BookImage { try { new URL(name) - } catch (error) { + } catch { throw new Error('Invalid URL') } diff --git a/src/core/book/domain/model/title.value-object.ts b/src/core/book/domain/model/title.value-object.ts index e598003..cd0b0fe 100644 --- a/src/core/book/domain/model/title.value-object.ts +++ b/src/core/book/domain/model/title.value-object.ts @@ -3,10 +3,10 @@ export class BookTitle { public static create(title: string): BookTitle { if (title === undefined) { - throw Error('Title can not be undefined') + throw new Error('Title can not be undefined') } if (title.length < 3) { - throw Error('Title is too short') + throw new Error('Title is too short') } return new BookTitle(title) diff --git a/src/core/book/domain/services/books.repository.ts b/src/core/book/domain/services/books.repository.ts index d54313e..dee6ba4 100644 --- a/src/core/book/domain/services/books.repository.ts +++ b/src/core/book/domain/services/books.repository.ts @@ -5,6 +5,6 @@ import BookId from '../model/id.value-object' export default interface Books { // Finds a user by email findAll(): Promise - findById(id: BookId): Promise + findById(id: BookId): Promise save(book: Book): Promise } diff --git a/src/core/book/infrastructure/actions.ts b/src/core/book/infrastructure/actions.ts deleted file mode 100644 index 813910e..0000000 --- a/src/core/book/infrastructure/actions.ts +++ /dev/null @@ -1,43 +0,0 @@ -'use server' - -import { z } from 'zod' - -import container from '@/lib/container' - -import { - CreateBookCommand, - CreateBookResponse, - FindBookResponse, -} from '../application/types' -import BookId from '../domain/model/id.value-object' - -const CreateFormSchema = z.object({ - authors: z.string().min(3), - image: z.string().min(3), - title: z.string().min(3), -}) - -export async function findBooks(): Promise { - return container.findBooks.with() -} - -export async function createBook( - prevState: unknown, - formData: FormData, -): Promise { - const id = BookId.generate() - const { authors, image, title } = CreateFormSchema.parse({ - authors: formData.get('authors'), - image: formData.get('image'), - title: formData.get('title'), - }) - - await container.createBook.with( - new CreateBookCommand(id.value, title, authors.split(', '), image), - ) - - return { - message: 'good', - success: true, - } -} diff --git a/src/core/book/infrastructure/actions/create-book.ts b/src/core/book/infrastructure/actions/create-book.ts new file mode 100644 index 0000000..d2dfc06 --- /dev/null +++ b/src/core/book/infrastructure/actions/create-book.ts @@ -0,0 +1,41 @@ +'use server' + +import { z } from 'zod' + +import { CreateBookCommand } from '@/core/book/application/types' +import BookId from '@/core/book/domain/model/id.value-object' +import container from '@/lib/container' +import FormResponse from '@/lib/zod/form-response' + +const CreateFormSchema = z.object({ + authors: z.string().min(3, 'Introduzca un nombre de autor válido.'), + image: z.string().url('Se debe indicar una URL válida.'), + title: z.string().min(3, 'Introduzca un título válido.'), +}) + +export async function createBook( + previousState: unknown, + formData: FormData, +): Promise { + const id = BookId.generate() + const result = CreateFormSchema.safeParse({ + authors: formData.get('authors'), + image: formData.get('image'), + title: formData.get('title'), + }) + + if (!result.success) { + return { + errors: result.error.issues, + success: false, + } + } + + const { authors, image, title } = result.data + + await container.createBook.with( + new CreateBookCommand(id.value, title, authors.split(', '), image), + ) + + return FormResponse.success() +} diff --git a/src/core/book/infrastructure/actions/find-books.ts b/src/core/book/infrastructure/actions/find-books.ts new file mode 100644 index 0000000..a94dc20 --- /dev/null +++ b/src/core/book/infrastructure/actions/find-books.ts @@ -0,0 +1,8 @@ +'use server' + +import { BookDTO } from '@/core/book/application/types' +import container from '@/lib/container' + +export async function findBooks(): Promise { + return container.findBooks.with() +} diff --git a/src/core/book/infrastructure/services/books-in-memory.retository.ts b/src/core/book/infrastructure/services/books-in-memory.retository.ts index ff4e10a..0aff989 100644 --- a/src/core/book/infrastructure/services/books-in-memory.retository.ts +++ b/src/core/book/infrastructure/services/books-in-memory.retository.ts @@ -87,13 +87,12 @@ export default class BooksInMemory implements Books { ) } - async findById(id: BookId): Promise { - const book = this.books.get(id.value) - return book ? book : null + async findById(id: BookId): Promise { + return this.books.get(id.value) } async findAll(): Promise { - return Array.from(this.books.values()) + return [...this.books.values()] } async save(book: Book): Promise { diff --git a/src/core/book/infrastructure/services/books-prisma.repository.ts b/src/core/book/infrastructure/services/books-prisma.repository.ts index edcaec6..e51a098 100644 --- a/src/core/book/infrastructure/services/books-prisma.repository.ts +++ b/src/core/book/infrastructure/services/books-prisma.repository.ts @@ -13,10 +13,10 @@ export default class BooksPrisma implements Books { async findAll(): Promise { const books = await this.prisma.book.findMany() - return books.map(this.mapFromPrismaBook) + return books.map((book) => this.mapFromPrismaBook(book)) } - async findById(id: BookId): Promise { + async findById(id: BookId): Promise { const book = await this.prisma.book.findUnique({ where: { id: id.value, @@ -24,7 +24,7 @@ export default class BooksPrisma implements Books { }) if (!book) { - return null + return undefined } return this.mapFromPrismaBook(book) diff --git a/src/core/borrow/application/services/find-readers.service.ts b/src/core/borrow/application/services/find-readers.service.ts index 82fd3c2..7b1d5fc 100644 --- a/src/core/borrow/application/services/find-readers.service.ts +++ b/src/core/borrow/application/services/find-readers.service.ts @@ -1,5 +1,5 @@ export interface FindBorrowsService { - withReader(bookId: string): Promise + withReader(bookId: string): Promise } export interface BorrowWithReaderResponse { diff --git a/src/core/borrow/infrastructure/services/prisma-find-readers.service.ts b/src/core/borrow/infrastructure/services/prisma-find-readers.service.ts index 7678fc2..64d7d83 100644 --- a/src/core/borrow/infrastructure/services/prisma-find-readers.service.ts +++ b/src/core/borrow/infrastructure/services/prisma-find-readers.service.ts @@ -7,7 +7,9 @@ import { export class PrismaFindReader implements FindBorrowsService { constructor(private readonly prisma: PrismaClient) {} - async withReader(bookId: string): Promise { + async withReader( + bookId: string, + ): Promise { const borrow = await this.prisma.borrow.findUnique({ select: { id: true, @@ -19,7 +21,7 @@ export class PrismaFindReader implements FindBorrowsService { }) if (!borrow) { - return null + return undefined } const { diff --git a/src/core/user/domain/model/image.value-object.ts b/src/core/user/domain/model/image.value-object.ts index 68303c6..b320072 100644 --- a/src/core/user/domain/model/image.value-object.ts +++ b/src/core/user/domain/model/image.value-object.ts @@ -8,7 +8,7 @@ export default class Image { try { new URL(name) - } catch (error) { + } catch { throw new Error('Invalid URL') } diff --git a/src/core/user/domain/model/user.entity.ts b/src/core/user/domain/model/user.entity.ts index cf70414..bd2f4e2 100644 --- a/src/core/user/domain/model/user.entity.ts +++ b/src/core/user/domain/model/user.entity.ts @@ -18,12 +18,12 @@ export default class User { email: string, image: string, ): User { - const nameObj = Name.create(name) - const roleObjs = roles.map(Role.create) - const emailObj = Email.create(email) - const imageObj = Image.create(image || gravatar(email)) + const nameObject = Name.create(name) + const roleObjs = roles.map((role) => Role.create(role)) + const emailObject = Email.create(email) + const imageObject = Image.create(image || gravatar(email)) - return new User(nameObj, roleObjs, emailObj, imageObj) + return new User(nameObject, roleObjs, emailObject, imageObject) } get name(): string { diff --git a/src/core/user/domain/services/users.repository.ts b/src/core/user/domain/services/users.repository.ts index 36eacd3..b6d9596 100644 --- a/src/core/user/domain/services/users.repository.ts +++ b/src/core/user/domain/services/users.repository.ts @@ -2,7 +2,7 @@ import User from '@/core/user/domain/model/user.entity' export default interface Users { // Finds a user by email - findByEmail(email: string): Promise + findByEmail(email: string): Promise // Saves a user save(user: User): Promise diff --git a/src/core/user/infrastructure/actions.ts b/src/core/user/infrastructure/actions.ts index 9dda64b..2d02279 100644 --- a/src/core/user/infrastructure/actions.ts +++ b/src/core/user/infrastructure/actions.ts @@ -11,28 +11,21 @@ import { import { auth } from '@/lib/auth/auth' import container from '@/lib/container' import gravatar from '@/lib/utils/gravatar' +import FormResponse from '@/lib/zod/form-response' const UpdateFormSchema = z.object({ name: z.string().min(3), }) -export interface UpdateUserResponse { - message: string - success: boolean -} - export async function updateUser( - prevState: unknown, + previousState: unknown, formData: FormData, -): Promise { +): Promise { const session = await auth() const email = session?.user?.email as string if (!email) { - return { - message: 'Error: Sesión no encontrada', - success: false, - } + return FormResponse.custom(['email'], 'Error en la sesión del usuario') } const { name } = UpdateFormSchema.parse({ @@ -45,18 +38,17 @@ export async function updateUser( revalidateTag(`role-for-${email}`) return { - message: 'Perfil actualizado', success: true, } } export async function findUser( email: string, -): Promise { +): Promise { const result = await container.findUser.with(new FindUserCommand(email)) if (result.isErr()) { - return null + return undefined } return result.value diff --git a/src/core/user/infrastructure/services/users-in-memory.repository.ts b/src/core/user/infrastructure/services/users-in-memory.repository.ts index 4e2c775..9f78673 100644 --- a/src/core/user/infrastructure/services/users-in-memory.repository.ts +++ b/src/core/user/infrastructure/services/users-in-memory.repository.ts @@ -4,8 +4,8 @@ import Users from '@/core/user/domain/services/users.repository' export default class UsersInMemory implements Users { private users: Map = new Map() - async findByEmail(email: string): Promise { - return this.users.get(email) || null + async findByEmail(email: string): Promise { + return this.users.get(email) } async save(user: User): Promise { diff --git a/src/core/user/infrastructure/services/users-prisma.repository.ts b/src/core/user/infrastructure/services/users-prisma.repository.ts index d272633..afec3fa 100644 --- a/src/core/user/infrastructure/services/users-prisma.repository.ts +++ b/src/core/user/infrastructure/services/users-prisma.repository.ts @@ -10,7 +10,7 @@ import Users from '@/core/user/domain/services/users.repository' export default class UsersPrisma implements Users { constructor(private readonly prisma: PrismaClient) {} - async findByEmail(email: string): Promise { + async findByEmail(email: string): Promise { const user = await this.prisma.user.findUnique({ select: { email: true, @@ -24,7 +24,7 @@ export default class UsersPrisma implements Users { }) if (!user) { - return null + return undefined } return this.mapFromPrismaUser(user) diff --git a/src/lib/auth/providers/email.ts b/src/lib/auth/providers/email.ts index 7c3cf22..db5b9b6 100644 --- a/src/lib/auth/providers/email.ts +++ b/src/lib/auth/providers/email.ts @@ -13,9 +13,9 @@ export default function Email( maxAge: 24 * 60 * 60, name: 'Email', options, - async sendVerificationRequest(params: SendVerificationRequestParams) { + async sendVerificationRequest(parameters: SendVerificationRequestParams) { const response = await fetch(process.env.MAILER_URL as string, { - body: JSON.stringify(params), + body: JSON.stringify(parameters), headers: { Authorization: `Bearer ${process.env.MAILER_SECRET}`, 'Content-Type': 'application/json', diff --git a/src/lib/mailer/formaters.ts b/src/lib/mailer/formaters.ts index 5b33a7e..3068748 100644 --- a/src/lib/mailer/formaters.ts +++ b/src/lib/mailer/formaters.ts @@ -8,10 +8,10 @@ import { Theme } from '@auth/core/types' * * @note We don't add the email address to avoid needing to escape it, if you do, remember to sanitize it! */ -export function html(params: { host: string; theme: Theme; url: string }) { - const { host, theme, url } = params +export function html(parameters: { host: string; theme: Theme; url: string }) { + const { host, theme, url } = parameters - const escapedHost = host.replace(/\./g, '​.') + const escapedHost = host.replaceAll('.', '​.') // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const brandColor = theme.brandColor || '#346df1' diff --git a/src/lib/mailer/smtp-client.ts b/src/lib/mailer/smtp-client.ts index fd0dba0..4467ccb 100644 --- a/src/lib/mailer/smtp-client.ts +++ b/src/lib/mailer/smtp-client.ts @@ -30,8 +30,8 @@ export class SmtpClient { text: text({ host, url }), to: identifier, }) - const failed = result.rejected.concat(result.pending).filter(Boolean) - if (failed.length) { + const failed = [...result.rejected, ...result.pending].filter(Boolean) + if (failed.length > 0) { throw new Error(`El correo (${failed.join(', ')}) no pudo ser enviado.`) } } catch (error) { diff --git a/src/lib/zod/form-response.ts b/src/lib/zod/form-response.ts new file mode 100644 index 0000000..4d8315f --- /dev/null +++ b/src/lib/zod/form-response.ts @@ -0,0 +1,28 @@ +import { ZodIssue } from 'zod' + +interface FormResponse { + errors?: ZodIssue[] + success: boolean +} + +const FormResponse = { + custom: (path: string[], message: string): FormResponse => ({ + errors: [ + { + code: 'custom', + message, + path, + }, + ], + success: false, + }), + initialState: (): FormResponse => ({ + errors: [], + success: false, + }), + success: (): FormResponse => ({ + success: true, + }), +} + +export default FormResponse diff --git a/stories/components/BookForm.stories.ts b/stories/components/book-form.stories.ts similarity index 85% rename from stories/components/BookForm.stories.ts rename to stories/components/book-form.stories.ts index e060fe4..10ee889 100644 --- a/stories/components/BookForm.stories.ts +++ b/stories/components/book-form.stories.ts @@ -1,6 +1,6 @@ import { Meta, StoryObj } from '@storybook/react' -import BookForm from '@/components/BookForm/BookForm' +import BookForm from '@/components/book-form' const meta = { component: BookForm, diff --git a/stories/components/BookGrid.stories.ts b/stories/components/book-grid.stories.ts similarity index 92% rename from stories/components/BookGrid.stories.ts rename to stories/components/book-grid.stories.ts index 3bde983..5911506 100644 --- a/stories/components/BookGrid.stories.ts +++ b/stories/components/book-grid.stories.ts @@ -1,6 +1,6 @@ import { Meta, StoryObj } from '@storybook/react' -import BookGrid from '@/components/BookGrid/BookGrid' +import BookGrid from '@/components/book-grid' const meta = { component: BookGrid, diff --git a/stories/components/Book.stories.ts b/stories/components/book.stories.ts similarity index 94% rename from stories/components/Book.stories.ts rename to stories/components/book.stories.ts index 4f88c5a..08182f4 100644 --- a/stories/components/Book.stories.ts +++ b/stories/components/book.stories.ts @@ -1,6 +1,6 @@ import { Meta, StoryObj } from '@storybook/react' -import Book from '@/components/Book/Book' +import Book from '@/components/book' const meta = { component: Book, diff --git a/stories/components/ThemeSwitcher.stories.ts b/stories/components/theme-switcher.stories.ts similarity index 58% rename from stories/components/ThemeSwitcher.stories.ts rename to stories/components/theme-switcher.stories.ts index 717a745..0ea76f2 100644 --- a/stories/components/ThemeSwitcher.stories.ts +++ b/stories/components/theme-switcher.stories.ts @@ -1,11 +1,11 @@ import { Meta, StoryObj } from '@storybook/react' -import ThemeSwitcher from '@/components/ThemeSwitcher/ThemeSwitcher' +import Index from '@/components/theme-switcher' const meta = { - component: ThemeSwitcher, + component: Index, title: 'Components/ThemeSwitcher', -} satisfies Meta +} satisfies Meta export default meta type Story = StoryObj diff --git a/yarn.lock b/yarn.lock index c6ce08d..596e1a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25,10 +25,10 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@aulasoftwarelibre/next-auth-firewall@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@aulasoftwarelibre/next-auth-firewall/-/next-auth-firewall-1.0.1.tgz" - integrity sha512-sQ5/fns5n4pfed5WJ2j4T2ewXJVLbPqQyg5oeCPusyqKyGOiPsUwJQSKtzLTWiBL052HCapswHdXofVabtX3yw== +"@aulasoftwarelibre/next-auth-firewall@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@aulasoftwarelibre/next-auth-firewall/-/next-auth-firewall-1.0.2.tgz#841b4242aedee93419c3a815ab28136f5e6da7bb" + integrity sha512-Za6A7Vn6JIdL64NvFm+suByIEjlD5pvP34DTiA4nk8VS13DHvKFwV4lb2mPieL2AQDE//gs9MjIMqxOlHBsy8Q== dependencies: "@auth/core" experimental next "^14.0.0" @@ -6398,6 +6398,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz" @@ -6566,7 +6571,7 @@ chrome-trace-event@^1.0.2: resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -ci-info@^3.2.0: +ci-info@^3.2.0, ci-info@^3.8.0: version "3.9.0" resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== @@ -6591,6 +6596,13 @@ clean-css@^5.2.2: dependencies: source-map "~0.6.0" +clean-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" + integrity sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw== + dependencies: + escape-string-regexp "^1.0.5" + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" @@ -7916,6 +7928,26 @@ eslint-plugin-storybook@^0.6.15: requireindex "^1.1.0" ts-dedent "^2.2.0" +eslint-plugin-unicorn@^49.0.0: + version "49.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-49.0.0.tgz#4449ea954d7e1455eec8518f9417d7021b245fa8" + integrity sha512-0fHEa/8Pih5cmzFW5L7xMEfUTvI9WKeQtjmKpTUmY+BiFCDxkxrTdnURJOHKykhtwIeyYsxnecbGvDCml++z4Q== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + "@eslint-community/eslint-utils" "^4.4.0" + ci-info "^3.8.0" + clean-regexp "^1.0.0" + esquery "^1.5.0" + indent-string "^4.0.0" + is-builtin-module "^3.2.1" + jsesc "^3.0.2" + pluralize "^8.0.0" + read-pkg-up "^7.0.1" + regexp-tree "^0.1.27" + regjsparser "^0.10.0" + semver "^7.5.4" + strip-indent "^3.0.0" + eslint-plugin-unused-imports@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.0.0.tgz" @@ -8019,7 +8051,7 @@ esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.2: +esquery@^1.4.2, esquery@^1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz" integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== @@ -9187,6 +9219,13 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" @@ -10030,6 +10069,11 @@ jsesc@^2.5.1: resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + jsesc@~0.5.0: version "0.5.0" resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" @@ -11362,6 +11406,11 @@ playwright@1.40.0: optionalDependencies: fsevents "2.3.2" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + pnp-webpack-plugin@^1.7.0: version "1.7.0" resolved "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz" @@ -12048,6 +12097,11 @@ regex-parser@^2.2.11: resolved "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz" integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== +regexp-tree@^0.1.27: + version "0.1.27" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== + regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz" @@ -12069,6 +12123,13 @@ regexpu-core@^5.3.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.1.0" +regjsparser@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.10.0.tgz#b1ed26051736b436f22fdec1c8f72635f9f44892" + integrity sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA== + dependencies: + jsesc "~0.5.0" + regjsparser@^0.9.1: version "0.9.1" resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz"