diff --git a/app/controllers/admin_controller.ts b/app/controllers/admin_controller.ts index 6d89e12..6f5c90d 100644 --- a/app/controllers/admin_controller.ts +++ b/app/controllers/admin_controller.ts @@ -1,42 +1,46 @@ +import CollectionsController from '#controllers/collections_controller'; +import LinksController from '#controllers/links_controller'; import UsersController from '#controllers/users_controller'; import User from '#models/user'; import { inject } from '@adonisjs/core'; import type { HttpContext } from '@adonisjs/core/http'; -import db from '@adonisjs/lucid/services/db'; class UserWithRelationCountDto { constructor(private user: User) {} - public toJson() { - return { - id: this.user.id, - email: this.user.email, - fullname: this.user.name, - avatarUrl: this.user.avatarUrl, - isAdmin: this.user.isAdmin, - createdAt: this.user.createdAt, - updatedAt: this.user.updatedAt, - count: { - link: Number(this.user.$extras.totalLinks), - collection: Number(this.user.$extras.totalCollections), - }, - }; - } + toJson = () => ({ + id: this.user.id, + email: this.user.email, + fullname: this.user.name, + avatarUrl: this.user.avatarUrl, + isAdmin: this.user.isAdmin, + createdAt: this.user.createdAt, + updatedAt: this.user.updatedAt, + count: { + link: Number(this.user.$extras.totalLinks), + collection: Number(this.user.$extras.totalCollections), + }, + }); } @inject() export default class AdminController { - constructor(protected usersController: UsersController) {} + constructor( + protected usersController: UsersController, + protected linksController: LinksController, + protected collectionsController: CollectionsController + ) {} async index({ inertia }: HttpContext) { const users = await this.usersController.getAllUsersWithTotalRelations(); - const links = await db.from('links').count('* as total'); - const collections = await db.from('collections').count('* as total'); + const linksCount = await this.linksController.getTotalLinksCount(); + const collectionsCount = + await this.collectionsController.getTotalCollectionsCount(); return inertia.render('admin/dashboard', { users: users.map((user) => new UserWithRelationCountDto(user).toJson()), - totalLinks: Number(links[0].total), - totalCollections: Number(collections[0].total), + totalLinks: linksCount, + totalCollections: collectionsCount, }); } } diff --git a/app/controllers/collections_controller.ts b/app/controllers/collections_controller.ts index c0fcd19..4e5b99b 100644 --- a/app/controllers/collections_controller.ts +++ b/app/controllers/collections_controller.ts @@ -6,6 +6,7 @@ import { updateCollectionValidator, } from '#validators/collection'; import type { HttpContext } from '@adonisjs/core/http'; +import db from '@adonisjs/lucid/services/db'; export default class CollectionsController { // Dashboard @@ -105,6 +106,11 @@ export default class CollectionsController { return response.redirectToNamedRoute('dashboard'); } + async getTotalCollectionsCount() { + const totalCount = await db.from('collections').count('* as total'); + return Number(totalCount[0].total); + } + /** * Get collection by id. * diff --git a/app/controllers/links_controller.ts b/app/controllers/links_controller.ts index 50dddaf..a51e064 100644 --- a/app/controllers/links_controller.ts +++ b/app/controllers/links_controller.ts @@ -8,6 +8,7 @@ import { } from '#validators/link'; import { inject } from '@adonisjs/core'; import type { HttpContext } from '@adonisjs/core/http'; +import db from '@adonisjs/lucid/services/db'; @inject() export default class LinksController { @@ -111,6 +112,11 @@ export default class LinksController { }); } + async getTotalLinksCount() { + const totalCount = await db.from('links').count('* as total'); + return Number(totalCount[0].total); + } + /** * Get link by id. * diff --git a/docker-compose.yml b/docker-compose.yml index 7d8efc0..f66667b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,22 @@ services: test: ['CMD-SHELL', 'pg_isready'] volumes: - postgres-volume:/var/lib/postgresql/data + ports: + - '${DB_PORT}:5432' + + pgadmin: + container_name: pgadmin + image: dpage/pgadmin4:8.6 + restart: always + environment: + - PGADMIN_DEFAULT_EMAIL=myemail@gmail.com + - PGADMIN_DEFAULT_PASSWORD=a12345678 + depends_on: + - postgres + volumes: + - ./docker-config/servers_pgadmin.json:/pgadmin4/servers.json + ports: + - '5050:80' my-links: container_name: my-links diff --git a/inertia/components/dashboard/collection/header/collection_controls.tsx b/inertia/components/dashboard/collection/header/collection_controls.tsx index 3abed74..f365c4c 100644 --- a/inertia/components/dashboard/collection/header/collection_controls.tsx +++ b/inertia/components/dashboard/collection/header/collection_controls.tsx @@ -1,4 +1,3 @@ -import type Collection from '#models/collection'; import { route } from '@izzyjs/route/client'; import { useTranslation } from 'react-i18next'; import { BsThreeDotsVertical } from 'react-icons/bs'; @@ -8,6 +7,7 @@ import { IoTrashOutline } from 'react-icons/io5'; import Dropdown from '~/components/common/dropdown/dropdown'; import { DropdownItemLink } from '~/components/common/dropdown/dropdown_item'; import { appendCollectionId } from '~/lib/navigation'; +import { Collection } from '~/types/app'; export default function CollectionControls({ collectionId, diff --git a/inertia/components/dashboard/collection/list/collection_item.tsx b/inertia/components/dashboard/collection/list/collection_item.tsx index 05b9e6d..d34396a 100644 --- a/inertia/components/dashboard/collection/list/collection_item.tsx +++ b/inertia/components/dashboard/collection/list/collection_item.tsx @@ -1,4 +1,3 @@ -import type Collection from '#models/collection'; import styled from '@emotion/styled'; import { Link } from '@inertiajs/react'; import { route } from '@izzyjs/route/client'; @@ -8,6 +7,7 @@ import TextEllipsis from '~/components/common/text_ellipsis'; import { Item } from '~/components/dashboard/side_nav/nav_item'; import useActiveCollection from '~/hooks/use_active_collection'; import { appendCollectionId } from '~/lib/navigation'; +import { CollectionWithLinks } from '~/types/app'; const CollectionItemStyle = styled(Item, { shouldForwardProp: (propName) => propName !== 'isActive', @@ -28,7 +28,7 @@ const LinksCount = styled.div(({ theme }) => ({ export default function CollectionItem({ collection, }: { - collection: Collection; + collection: CollectionWithLinks; }) { const itemRef = useRef(null); const { activeCollection } = useActiveCollection(); diff --git a/inertia/components/dashboard/link/link_controls.tsx b/inertia/components/dashboard/link/link_controls.tsx index c097720..c76c5f3 100644 --- a/inertia/components/dashboard/link/link_controls.tsx +++ b/inertia/components/dashboard/link/link_controls.tsx @@ -1,4 +1,3 @@ -import type Link from '#models/link'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { route } from '@izzyjs/route/client'; @@ -17,6 +16,7 @@ import useActiveCollection from '~/hooks/use_active_collection'; import useCollections from '~/hooks/use_collections'; import { appendLinkId } from '~/lib/navigation'; import { makeRequest } from '~/lib/request'; +import { Link } from '~/types/app'; const StartItem = styled(DropdownItemButton)(({ theme }) => ({ color: theme.colors.yellow, @@ -54,7 +54,7 @@ export default function LinkControls({ link }: { link: Link }) { const onFavorite = () => { const { url, method } = route('link.toggle-favorite', { - params: { id: link.id }, + params: { id: link.id.toString() }, }); makeRequest({ url, diff --git a/inertia/components/dashboard/link/link_item.tsx b/inertia/components/dashboard/link/link_item.tsx index 5b6eacc..f7c5b3d 100644 --- a/inertia/components/dashboard/link/link_item.tsx +++ b/inertia/components/dashboard/link/link_item.tsx @@ -1,9 +1,9 @@ -import type Link from '#models/link'; import styled from '@emotion/styled'; import { AiFillStar } from 'react-icons/ai'; import ExternalLink from '~/components/common/external_link'; import LinkControls from '~/components/dashboard/link/link_controls'; import LinkFavicon from '~/components/dashboard/link/link_favicon'; +import { Link } from '~/types/app'; const LinkWrapper = styled.li(({ theme }) => ({ userSelect: 'none', diff --git a/inertia/components/dashboard/link/link_list.tsx b/inertia/components/dashboard/link/link_list.tsx index 3e183cb..e3a7716 100644 --- a/inertia/components/dashboard/link/link_list.tsx +++ b/inertia/components/dashboard/link/link_list.tsx @@ -1,8 +1,8 @@ -import type Link from '#models/link'; import styled from '@emotion/styled'; import LinkItem from '~/components/dashboard/link/link_item'; import { NoLink } from '~/components/dashboard/link/no_item'; import { sortByCreationDate } from '~/lib/array'; +import { Link } from '~/types/app'; const LinkListStyle = styled.ul({ height: '100%', diff --git a/inertia/components/dashboard/quick_action/_quick_action_style.tsx b/inertia/components/dashboard/quick_action/_quick_action_style.tsx deleted file mode 100644 index 412e885..0000000 --- a/inertia/components/dashboard/quick_action/_quick_action_style.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import styled from '@emotion/styled'; - -const ActionStyle = styled.div(({ theme }) => ({ - border: '0 !important', - margin: '0 !important', - padding: '0 !important', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - - '& svg': { - height: '20px', - width: '20px', - transition: theme.transition.delay, - }, - - '&:hover svg': { - transform: 'scale(1.25)', - }, -})); - -export default ActionStyle; diff --git a/inertia/components/dashboard/quick_action/quick_action.tsx b/inertia/components/dashboard/quick_action/quick_action.tsx deleted file mode 100644 index 1f0751a..0000000 --- a/inertia/components/dashboard/quick_action/quick_action.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import type Collection from '#models/collection'; -import type Link from '#models/link'; -import { Link as LinkTag } from '@inertiajs/react'; -import { MouseEventHandler } from 'react'; -import { useTranslation } from 'react-i18next'; -import { AiOutlineEdit } from 'react-icons/ai'; -import { CgTrashEmpty } from 'react-icons/cg'; -import { IoAddOutline } from 'react-icons/io5'; -import ActionStyle from '~/components/dashboard/quick_action/_quick_action_style'; -import { appendCollectionId, appendResourceId } from '~/lib/navigation'; - -type Resource = 'collection' | 'link'; -type Action = 'create' | 'edit' | 'remove'; - -const getIconFromAction = (action: Action) => { - switch (action) { - case 'create': - return IoAddOutline; - case 'edit': - return AiOutlineEdit; - case 'remove': - return CgTrashEmpty; - } -}; - -const QuickResourceActionStyle = ActionStyle.withComponent(LinkTag); -export default function QuickResourceAction({ - resource, - action, - collectionId, - resourceId, - onClick, -}: { - resource: Resource; - action: Action; - collectionId?: Collection['id']; - resourceId?: Collection['id'] | Link['id']; - onClick?: MouseEventHandler; -}) { - const { t } = useTranslation('common'); - const ActionIcon = getIconFromAction(action); - - return ( - onClick(event) : undefined} - > - - - ); -} diff --git a/inertia/components/dashboard/quick_action/quick_favorite_link.tsx b/inertia/components/dashboard/quick_action/quick_favorite_link.tsx deleted file mode 100644 index 2457649..0000000 --- a/inertia/components/dashboard/quick_action/quick_favorite_link.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import styled from '@emotion/styled'; -import { MouseEventHandler } from 'react'; -import { AiFillStar, AiOutlineStar } from 'react-icons/ai'; -import ActionStyle from '~/components/dashboard/quick_action/_quick_action_style'; - -const StartIcon = styled(AiFillStar)(({ theme }) => ({ - color: theme.colors.yellow, -})); -const UnstaredIcon = StartIcon.withComponent(AiOutlineStar); - -const QuickLinkFavoriteStyle = ActionStyle.withComponent('div'); -const QuickLinkFavorite = ({ - isFavorite, - onClick, -}: { - isFavorite?: boolean; - onClick: MouseEventHandler; -}) => ( - onClick(event) : undefined} - > - {isFavorite ? : } - -); - -export default QuickLinkFavorite; diff --git a/inertia/components/form/form_collection.tsx b/inertia/components/form/form_collection.tsx index 082a05e..fbd973b 100644 --- a/inertia/components/form/form_collection.tsx +++ b/inertia/components/form/form_collection.tsx @@ -1,11 +1,11 @@ import { Visibility } from '#enums/visibility'; -import type Collection from '#models/collection'; import { FormEvent } from 'react'; import { useTranslation } from 'react-i18next'; import Checkbox from '~/components/common/form/checkbox'; import TextBox from '~/components/common/form/textbox'; import BackToDashboard from '~/components/common/navigation/back_to_dashboard'; import FormLayout from '~/components/layouts/form_layout'; +import { Collection } from '~/types/app'; export type FormCollectionData = { name: string; diff --git a/inertia/components/form/form_link.tsx b/inertia/components/form/form_link.tsx index 3bf53bc..79f4244 100644 --- a/inertia/components/form/form_link.tsx +++ b/inertia/components/form/form_link.tsx @@ -1,4 +1,3 @@ -import type Collection from '#models/collection'; import { FormEvent } from 'react'; import { useTranslation } from 'react-i18next'; import Checkbox from '~/components/common/form/checkbox'; @@ -6,6 +5,7 @@ import TextBox from '~/components/common/form/textbox'; import BackToDashboard from '~/components/common/navigation/back_to_dashboard'; import FormLayout from '~/components/layouts/form_layout'; import useSearchParam from '~/hooks/use_search_param'; +import { Collection } from '~/types/app'; export type FormLinkData = { name: string; diff --git a/inertia/contexts/active_collection_context.tsx b/inertia/contexts/active_collection_context.tsx index d5be8bf..ade506f 100644 --- a/inertia/contexts/active_collection_context.tsx +++ b/inertia/contexts/active_collection_context.tsx @@ -1,14 +1,14 @@ -import type Collection from '#models/collection'; import { createContext } from 'react'; +import { CollectionWithLinks } from '~/types/app'; type ActiveCollectionContextType = { - activeCollection: Collection | null; - setActiveCollection: (collection: Collection) => void; + activeCollection: CollectionWithLinks | null; + setActiveCollection: (collection: CollectionWithLinks) => void; }; const iActiveCollectionContextState: ActiveCollectionContextType = { activeCollection: null, - setActiveCollection: (_: Collection) => {}, + setActiveCollection: (_: CollectionWithLinks) => {}, }; export const ActiveCollectionContext = diff --git a/inertia/contexts/collections_context.ts b/inertia/contexts/collections_context.ts index 28d88ee..df42330 100644 --- a/inertia/contexts/collections_context.ts +++ b/inertia/contexts/collections_context.ts @@ -1,14 +1,16 @@ -import Collection from '#models/collection'; import { createContext } from 'react'; +import { CollectionWithLinks } from '~/types/app'; type CollectionsContextType = { - collections: Collection[]; - setCollections: (collections: Collection[]) => void | Collection[]; + collections: CollectionWithLinks[]; + setCollections: ( + collections: CollectionWithLinks[] + ) => void | CollectionWithLinks[]; }; const iCollectionsContextState: CollectionsContextType = { - collections: [] as Collection[], - setCollections: (_: Collection[]) => {}, + collections: [] as CollectionWithLinks[], + setCollections: (_: CollectionWithLinks[]) => {}, }; const CollectionsContext = createContext( diff --git a/inertia/contexts/favorites_context.ts b/inertia/contexts/favorites_context.ts index 42e41f1..2ec796b 100644 --- a/inertia/contexts/favorites_context.ts +++ b/inertia/contexts/favorites_context.ts @@ -1,5 +1,5 @@ -import type Link from '#models/link'; import { createContext } from 'react'; +import { Link } from '~/types/app'; type FavoritesContextType = { favorites: Link[]; diff --git a/inertia/lib/collection.ts b/inertia/lib/collection.ts index 9ac0075..0faeeb7 100644 --- a/inertia/lib/collection.ts +++ b/inertia/lib/collection.ts @@ -1,11 +1,11 @@ -import type Collection from '#models/collection'; +import { CollectionWithLinks } from '~/types/app'; export default function sortCcollectionsByNextId( - collections: Collection[] -): Collection[] { - const sortedCollections: Collection[] = []; + collections: CollectionWithLinks[] +): CollectionWithLinks[] { + const sortedCollections: CollectionWithLinks[] = []; - const visit = (collection: Collection) => { + const visit = (collection: CollectionWithLinks) => { // Check if the collection has been visited if (sortedCollections.includes(collection)) { return; diff --git a/inertia/lib/navigation.ts b/inertia/lib/navigation.ts index b56dbee..65c8815 100644 --- a/inertia/lib/navigation.ts +++ b/inertia/lib/navigation.ts @@ -1,5 +1,4 @@ -import type Collection from '#models/collection'; -import type Link from '#models/link'; +import { Collection, Link } from '~/types/app'; export const appendCollectionId = ( url: string, @@ -11,8 +10,10 @@ export const appendLinkId = ( linkId?: Link['id'] | null | undefined ) => `${url}${linkId ? `?linkId=${linkId}` : ''}`; -export const appendResourceId = (url: string, resourceId?: string) => - `${url}${resourceId ? `/${resourceId}` : ''}`; +export const appendResourceId = ( + url: string, + resourceId?: Collection['id'] | Link['id'] +) => `${url}${resourceId ? `/${resourceId}` : ''}`; export function isValidHttpUrl(urlParam: string) { let url; diff --git a/inertia/pages/collections/delete.tsx b/inertia/pages/collections/delete.tsx index 22216ea..afe5642 100644 --- a/inertia/pages/collections/delete.tsx +++ b/inertia/pages/collections/delete.tsx @@ -1,10 +1,10 @@ -import type Collection from '#models/collection'; import { useForm } from '@inertiajs/react'; import { route } from '@izzyjs/route/client'; import { useTranslation } from 'react-i18next'; import FormCollection, { FormCollectionData, } from '~/components/form/form_collection'; +import { Collection } from '~/types/app'; export default function DeleteCollectionPage({ collection, diff --git a/inertia/pages/collections/edit.tsx b/inertia/pages/collections/edit.tsx index 8087bae..ac644d5 100644 --- a/inertia/pages/collections/edit.tsx +++ b/inertia/pages/collections/edit.tsx @@ -1,4 +1,3 @@ -import type Collection from '#models/collection'; import { useForm } from '@inertiajs/react'; import { route } from '@izzyjs/route/client'; import { useMemo } from 'react'; @@ -6,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import FormCollection, { FormCollectionData, } from '~/components/form/form_collection'; +import { Collection } from '~/types/app'; export default function EditCollectionPage({ collection, diff --git a/inertia/pages/dashboard.tsx b/inertia/pages/dashboard.tsx index a3a29ca..353e4d4 100644 --- a/inertia/pages/dashboard.tsx +++ b/inertia/pages/dashboard.tsx @@ -1,5 +1,3 @@ -import type Collection from '#models/collection'; -import Link from '#models/link'; import styled from '@emotion/styled'; import { router } from '@inertiajs/react'; import { route } from '@izzyjs/route/client'; @@ -18,10 +16,11 @@ import { useMediaQuery } from '~/hooks/use_media_query'; import useToggle from '~/hooks/use_modal'; import useShortcut from '~/hooks/use_shortcut'; import { appendCollectionId } from '~/lib/navigation'; +import { CollectionWithLinks, Link } from '~/types/app'; interface DashboardPageProps { - collections: Collection[]; - activeCollection: Collection; + collections: CollectionWithLinks[]; + activeCollection: CollectionWithLinks; } const SideBar = styled.div(({ theme }) => ({ @@ -65,20 +64,21 @@ export default function DashboardPage(props: Readonly) { function DashboardProviders( props: Readonly<{ children: ReactNode; - collections: Collection[]; - activeCollection: Collection; + collections: CollectionWithLinks[]; + activeCollection: CollectionWithLinks; }> ) { const [globalHotkeysEnabled, setGlobalHotkeysEnabled] = useState(true); - const [collections, setCollections] = useState( + const [collections, setCollections] = useState( props.collections ); - const [activeCollection, setActiveCollection] = useState( - props.activeCollection || collections?.[0] - ); + const [activeCollection, setActiveCollection] = + useState( + props.activeCollection || collections?.[0] + ); - const handleChangeCollection = (collection: Collection) => { + const handleChangeCollection = (collection: CollectionWithLinks) => { setActiveCollection(collection); router.visit(appendCollectionId(route('dashboard').url, collection.id)); }; diff --git a/inertia/pages/links/create.tsx b/inertia/pages/links/create.tsx index f584890..d3f75e5 100644 --- a/inertia/pages/links/create.tsx +++ b/inertia/pages/links/create.tsx @@ -1,4 +1,3 @@ -import type Collection from '#models/collection'; import { useForm } from '@inertiajs/react'; import { route } from '@izzyjs/route/client'; import { useMemo } from 'react'; @@ -6,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import FormLink from '~/components/form/form_link'; import useSearchParam from '~/hooks/use_search_param'; import { isValidHttpUrl } from '~/lib/navigation'; +import { Collection } from '~/types/app'; export default function CreateLinkPage({ collections, diff --git a/inertia/pages/links/delete.tsx b/inertia/pages/links/delete.tsx index fc0ee27..06cac46 100644 --- a/inertia/pages/links/delete.tsx +++ b/inertia/pages/links/delete.tsx @@ -1,10 +1,10 @@ -import type Link from '#models/link'; import { useForm } from '@inertiajs/react'; import { route } from '@izzyjs/route/client'; import { useTranslation } from 'react-i18next'; import FormLink from '~/components/form/form_link'; +import { LinkWithCollection } from '~/types/app'; -export default function DeleteLinkPage({ link }: { link: Link }) { +export default function DeleteLinkPage({ link }: { link: LinkWithCollection }) { const { t } = useTranslation('common'); const { data, setData, submit, processing } = useForm({ name: link.name, diff --git a/inertia/pages/links/edit.tsx b/inertia/pages/links/edit.tsx index 3045f21..1e39b55 100644 --- a/inertia/pages/links/edit.tsx +++ b/inertia/pages/links/edit.tsx @@ -1,11 +1,10 @@ -import type Collection from '#models/collection'; -import type Link from '#models/link'; import { useForm } from '@inertiajs/react'; import { route } from '@izzyjs/route/client'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import FormLink from '~/components/form/form_link'; import { isValidHttpUrl } from '~/lib/navigation'; +import { Collection, Link } from '~/types/app'; export default function EditLinkPage({ collections, diff --git a/inertia/types/app.d.ts b/inertia/types/app.d.ts index 2f34400..046242e 100644 --- a/inertia/types/app.d.ts +++ b/inertia/types/app.d.ts @@ -1,26 +1,55 @@ -import type Collection from '#models/collection'; +import { Visibility } from '@/app/enums/visibility'; -type User = { +type CommonBase = { id: number; + createdAt: string; + updatedAt: string; +}; + +type User = CommonBase & { email: string; name: string; nickName: string; fullname: string; avatarUrl: string; isAdmin: boolean; +}; + +type UserWithCollections = User & { collections: Collection[]; }; -type UserWithRelationCount = { - id: number; +type UserWithRelationCount = CommonBase & { email: string; fullname: string; avatarUrl: string; isAdmin: string; - createdAt: string; - updatedAt: string; count: { link: number; collection: number; }; }; + +type Link = CommonBase & { + name: string; + description: string | null; + url: string; + favorite: boolean; + collectionId: number; +}; + +type LinkWithCollection = Link & { + collection: Collection; +}; + +type Collection = CommonBase & { + name: string; + description: string | null; + visibility: Visibility; + nextId: number; + authorId: number; +}; + +type CollectionWithLinks = Collection & { + links: Link[]; +}; diff --git a/package-lock.json b/package-lock.json index df22fd3..3e76a49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/react-toggle": "^4.0.5", + "@typescript-eslint/eslint-plugin": "^7.10.0", "@vitejs/plugin-react": "^4.3.0", "eslint": "^8.57.0", "hot-hook": "^0.2.6", @@ -4274,6 +4275,220 @@ "node_modules/@types/yargs-parser": { "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.10.0.tgz", + "integrity": "sha512-PzCr+a/KAef5ZawX7nbyNwBDtM1HdLIT53aSA2DDlxmxMngZ43O8SIePOeX8H5S+FHXeI6t97mTt/dDdzY4Fyw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.10.0", + "@typescript-eslint/type-utils": "7.10.0", + "@typescript-eslint/utils": "7.10.0", + "@typescript-eslint/visitor-keys": "7.10.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.10.0.tgz", + "integrity": "sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/visitor-keys": "7.10.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.10.0.tgz", + "integrity": "sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.10.0.tgz", + "integrity": "sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.10.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.10.0.tgz", + "integrity": "sha512-2EjZMA0LUW5V5tGQiaa2Gys+nKdfrn2xiTIBLR4fxmPmVSvgPcKNW+AE/ln9k0A4zDUti0J/GZXMDupQoI+e1w==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.10.0", + "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/typescript-estree": "7.10.0", + "@typescript-eslint/visitor-keys": "7.10.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.10.0.tgz", + "integrity": "sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/visitor-keys": "7.10.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.10.0.tgz", + "integrity": "sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==", + "dev": true, + "peer": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.10.0.tgz", + "integrity": "sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/visitor-keys": "7.10.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.10.0.tgz", + "integrity": "sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "7.10.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", @@ -4291,6 +4506,115 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.10.0.tgz", + "integrity": "sha512-D7tS4WDkJWrVkuzgm90qYw9RdgBcrWmbbRkrLA4d7Pg3w0ttVGDsvYGV19SH8gPR5L7OtcN5J1hTtyenO9xE9g==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.10.0", + "@typescript-eslint/utils": "7.10.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.10.0.tgz", + "integrity": "sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.10.0.tgz", + "integrity": "sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/visitor-keys": "7.10.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.10.0.tgz", + "integrity": "sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.10.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", @@ -4356,6 +4680,127 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@typescript-eslint/utils": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.10.0.tgz", + "integrity": "sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.10.0", + "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/typescript-estree": "7.10.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.10.0.tgz", + "integrity": "sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/visitor-keys": "7.10.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.10.0.tgz", + "integrity": "sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.10.0.tgz", + "integrity": "sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/visitor-keys": "7.10.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.10.0.tgz", + "integrity": "sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.10.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", diff --git a/package.json b/package.json index 1c410d3..6a78c74 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/react-toggle": "^4.0.5", + "@typescript-eslint/eslint-plugin": "^7.10.0", "@vitejs/plugin-react": "^4.3.0", "eslint": "^8.57.0", "hot-hook": "^0.2.6",