refactor: create types instead of using models

This commit is contained in:
Sonny
2024-05-26 23:43:48 +02:00
committed by Sonny
parent 55cd973b1a
commit e03952de1c
28 changed files with 582 additions and 176 deletions

View File

@@ -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,
});
}
}

View File

@@ -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.
*

View File

@@ -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.
*

View File

@@ -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

View File

@@ -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,

View File

@@ -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<HTMLDivElement>(null);
const { activeCollection } = useActiveCollection();

View File

@@ -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,

View File

@@ -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',

View File

@@ -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%',

View File

@@ -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;

View File

@@ -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<HTMLAnchorElement>;
}) {
const { t } = useTranslation('common');
const ActionIcon = getIconFromAction(action);
return (
<QuickResourceActionStyle
href={appendCollectionId(
appendResourceId(`/${resource}/${action}`, resourceId),
collectionId
)}
title={t(`${resource}.${action}`)}
onClick={onClick ? (event) => onClick(event) : undefined}
>
<ActionIcon />
</QuickResourceActionStyle>
);
}

View File

@@ -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<HTMLDivElement>;
}) => (
<QuickLinkFavoriteStyle
onClick={onClick ? (event) => onClick(event) : undefined}
>
{isFavorite ? <StartIcon /> : <UnstaredIcon />}
</QuickLinkFavoriteStyle>
);
export default QuickLinkFavorite;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 =

View File

@@ -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<CollectionsContextType>(

View File

@@ -1,5 +1,5 @@
import type Link from '#models/link';
import { createContext } from 'react';
import { Link } from '~/types/app';
type FavoritesContextType = {
favorites: Link[];

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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,

View File

@@ -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<DashboardPageProps>) {
function DashboardProviders(
props: Readonly<{
children: ReactNode;
collections: Collection[];
activeCollection: Collection;
collections: CollectionWithLinks[];
activeCollection: CollectionWithLinks;
}>
) {
const [globalHotkeysEnabled, setGlobalHotkeysEnabled] =
useState<boolean>(true);
const [collections, setCollections] = useState<Collection[]>(
const [collections, setCollections] = useState<CollectionWithLinks[]>(
props.collections
);
const [activeCollection, setActiveCollection] = useState<Collection | null>(
props.activeCollection || collections?.[0]
);
const [activeCollection, setActiveCollection] =
useState<CollectionWithLinks | null>(
props.activeCollection || collections?.[0]
);
const handleChangeCollection = (collection: Collection) => {
const handleChangeCollection = (collection: CollectionWithLinks) => {
setActiveCollection(collection);
router.visit(appendCollectionId(route('dashboard').url, collection.id));
};

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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[];
};

445
package-lock.json generated
View File

@@ -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",

View File

@@ -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",