Merge branch 'dane/fiddle-with-new-tables' into v2

This commit is contained in:
Dane Everitt 2022-02-27 10:51:56 -05:00
commit 341fa45cbd
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
22 changed files with 628 additions and 32 deletions

View File

@ -42,10 +42,12 @@
"@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-regular-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.1.16", "@fortawesome/react-fontawesome": "^0.1.16",
"@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.5", "@heroicons/react": "^1.0.5",
"@hot-loader/react-dom": "^16.14.0", "@hot-loader/react-dom": "^16.14.0",
"axios": "^0.21.4", "axios": "^0.21.4",
"chart.js": "^2.9.4", "chart.js": "^2.9.4",
"classnames": "^2.3.1",
"date-fns": "^2.25.0", "date-fns": "^2.25.0",
"debounce": "^1.2.1", "debounce": "^1.2.1",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",

View File

@ -0,0 +1,144 @@
import React, { useEffect, useState } from 'react';
import http from '@/api/http';
import { User } from '@/api/admin/user';
import { AdminTransformers } from '@/api/admin/transformers';
import { Dropdown } from '@/components/elements/dropdown';
import {
BanIcon,
DotsVerticalIcon,
LockOpenIcon,
PencilIcon,
PlusIcon,
SupportIcon,
TrashIcon,
} from '@heroicons/react/solid';
import { Button } from '@/components/elements/button/index';
import { Dialog } from '@/components/elements/dialog';
import { Checkbox, InputField } from '@/components/elements/inputs';
const UsersContainerV2 = () => {
const [ users, setUsers ] = useState<User[]>([]);
useEffect(() => {
document.title = 'Admin | Users';
}, []);
const [ visible, setVisible ] = useState(false);
useEffect(() => {
http.get('/api/application/users')
.then(({ data }) => {
setUsers(data.data.map(AdminTransformers.toUser));
})
.catch(console.error);
}, []);
return (
<div>
<div className={'flex justify-end mb-4'}>
<Button className={'shadow focus:ring-offset-2 focus:ring-offset-neutral-800'}>
Add User <PlusIcon className={'ml-2 w-5 h-5'}/>
</Button>
</div>
<Dialog title={'Delete account'} visible={visible} onDismissed={() => setVisible(false)}>
<Dialog.Icon type={'danger'}/>
This account will be permanently deleted.
<Dialog.Buttons>
<Button.Text
onClick={() => setVisible(false)}
>
Cancel
</Button.Text>
<Button.Danger>Delete</Button.Danger>
</Dialog.Buttons>
</Dialog>
<div className={'relative flex items-center rounded-t bg-neutral-700 px-4 py-2'}>
<div className={'mr-6'}>
<Checkbox />
</div>
<div className={'flex-1'}>
<InputField type={'text'} name={'filter'} placeholder={'Begin typing to filter...'} className={'w-56 focus:w-96'} />
</div>
<div className={'absolute rounded-t bg-neutral-700 w-full h-full top-0 left-0 flex items-center justify-end space-x-4 px-4'}>
<div className={'flex-1'}>
<Checkbox indeterminate />
</div>
<Button.Text square>
<SupportIcon className={'w-4 h-4'} />
</Button.Text>
<Button.Text square>
<LockOpenIcon className={'w-4 h-4'} />
</Button.Text>
<Button.Text square>
<TrashIcon className={'w-4 h-4'} />
</Button.Text>
</div>
</div>
<table className={'min-w-full rounded bg-neutral-700'}>
<thead className={'bg-neutral-900'}>
<tr>
<th scope={'col'} className={'w-8'}/>
<th scope={'col'} className={'text-left px-6 py-2 w-full'}>Email</th>
<th scope={'col'}/>
<th scope={'col'}/>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.uuid}>
<td className={'whitespace-nowrap'}>
<div className={'flex justify-end items-center w-8'}>
<Checkbox/>
</div>
</td>
<td className={'pl-6 py-4 whitespace-nowrap'}>
<div className={'flex items-center'}>
<div className={'w-10 h-10'}>
<img
src={user.avatarUrl}
className={'w-10 h-10 rounded-full'}
alt={'User avatar'}
/>
</div>
<div className={'ml-4'}>
<p className={'font-medium'}>
{user.email}
</p>
<p className={'text-sm text-neutral-400'}>
{user.uuid}
</p>
</div>
</div>
</td>
<td className={'pl-2 py-4 whitespace-nowrap'}>
{user.isUsingTwoFactor &&
<span className={'bg-green-100 uppercase text-green-600 font-semibold text-xs px-2 py-0.5 rounded'}>
2-FA Enabled
</span>
}
</td>
<td className={'px-6 py-4 whitespace-nowrap'}>
<Dropdown>
<Dropdown.Button className={'px-2'}>
<DotsVerticalIcon/>
</Dropdown.Button>
<Dropdown.Item icon={<PencilIcon/>}>Edit</Dropdown.Item>
<Dropdown.Item icon={<SupportIcon/>}>Reset Password</Dropdown.Item>
<Dropdown.Item icon={<LockOpenIcon/>} disabled={!user.isUsingTwoFactor}>
Disable 2-FA
</Dropdown.Item>
<Dropdown.Item icon={<BanIcon/>}>Suspend</Dropdown.Item>
<Dropdown.Gap/>
<Dropdown.Item icon={<TrashIcon/>} onClick={() => setVisible(true)} danger>Delete
Account
</Dropdown.Item>
</Dropdown>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default UsersContainerV2;

View File

@ -0,0 +1,36 @@
import React, { forwardRef } from 'react';
import classNames from 'classnames';
import styles from './button.module.css';
export type ButtonProps = JSX.IntrinsicElements['button'] & {
square?: boolean;
small?: boolean;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ children, square, small, className, ...rest }, ref) => {
return (
<button
ref={ref}
className={classNames(styles.button, { [styles.square]: square, [styles.small]: small }, className)}
{...rest}
>
{children}
</button>
);
},
);
const TextButton = forwardRef<HTMLButtonElement, ButtonProps>(({ className, ...props }, ref) => (
// @ts-expect-error
<Button ref={ref} className={classNames(styles.text, className)} {...props} />
));
const DangerButton = forwardRef<HTMLButtonElement, ButtonProps>(({ className, ...props }, ref) => (
// @ts-expect-error
<Button ref={ref} className={classNames(styles.danger, className)} {...props} />
));
const _Button = Object.assign(Button, { Text: TextButton, Danger: DangerButton });
export default _Button;

View File

@ -0,0 +1,30 @@
.button {
@apply px-4 py-2 inline-flex items-center justify-center;
@apply bg-blue-600 rounded text-base font-semibold text-blue-50 transition-all duration-100;
@apply hover:bg-blue-500 active:bg-blue-500;
&.square {
@apply p-2;
}
&:focus {
@apply ring-[3px] ring-blue-500 ring-offset-2 ring-offset-neutral-700;
}
/* Sizing Controls */
&.small {
@apply px-3 py-1 font-normal focus:ring-2;
&.square {
@apply p-1;
}
}
}
.text {
@apply bg-transparent focus:ring-neutral-300 focus:ring-opacity-50 hover:bg-neutral-500 active:bg-neutral-500;
}
.danger {
@apply bg-red-600 hover:bg-red-500 active:bg-red-500 focus:ring-red-500 text-red-50;
}

View File

@ -0,0 +1,2 @@
export { default as Button } from './Button';
export { default as styles } from './button.module.css';

View File

@ -0,0 +1,112 @@
import React, { Fragment } from 'react';
import { Dialog as HeadlessDialog, Transition } from '@headlessui/react';
import { Button } from '@/components/elements/button/index';
import styles from './dialog.module.css';
import { XIcon } from '@heroicons/react/solid';
import { CheckIcon, ExclamationIcon, InformationCircleIcon, ShieldExclamationIcon } from '@heroicons/react/outline';
import classNames from 'classnames';
interface Props {
visible: boolean;
onDismissed: () => void;
title?: string;
children?: React.ReactNode;
}
interface DialogIconProps {
type: 'danger' | 'info' | 'success' | 'warning';
className?: string;
}
const DialogIcon = ({ type, className }: DialogIconProps) => {
const [ Component, styles ] = (function (): [(props: React.ComponentProps<'svg'>) => JSX.Element, string] {
switch (type) {
case 'danger':
return [ ShieldExclamationIcon, 'bg-red-500 text-red-50' ];
case 'warning':
return [ ExclamationIcon, 'bg-yellow-600 text-yellow-50' ];
case 'success':
return [ CheckIcon, 'bg-green-600 text-green-50' ];
case 'info':
return [ InformationCircleIcon, 'bg-primary-500 text-primary-50' ];
}
})();
return (
<div className={classNames('flex items-center justify-center w-10 h-10 rounded-full', styles, className)}>
<Component className={'w-6 h-6'} />
</div>
);
};
const DialogButtons = ({ children }: { children: React.ReactNode }) => (
<>{children}</>
);
const Dialog = ({ visible, title, onDismissed, children }: Props) => {
const items = React.Children.toArray(children || []);
const [ buttons, icon, content ] = [
// @ts-expect-error
items.find(child => child.type === DialogButtons),
// @ts-expect-error
items.find(child => child.type === DialogIcon),
// @ts-expect-error
items.filter(child => ![ DialogIcon, DialogButtons ].includes(child.type)),
];
return (
<Transition show={visible} as={Fragment}>
<HeadlessDialog onClose={() => onDismissed()} className={styles.wrapper}>
<div className={'flex items-center justify-center min-h-screen'}>
<Transition.Child
as={Fragment}
enter={'ease-out duration-200'}
enterFrom={'opacity-0'}
enterTo={'opacity-100'}
leave={'ease-in duration-100'}
leaveFrom={'opacity-100'}
leaveTo={'opacity-0'}
>
<HeadlessDialog.Overlay className={styles.overlay}/>
</Transition.Child>
<Transition.Child
as={Fragment}
enter={'ease-out duration-200'}
enterFrom={'opacity-0 scale-95'}
enterTo={'opacity-100 scale-100'}
leave={'ease-in duration-100'}
leaveFrom={'opacity-100 scale-100'}
leaveTo={'opacity-0 scale-95'}
>
<div className={styles.container}>
<div className={'flex p-6'}>
{icon && <div className={'mr-4'}>{icon}</div>}
<div className={'flex-1'}>
{title &&
<HeadlessDialog.Title className={styles.title}>
{title}
</HeadlessDialog.Title>
}
<HeadlessDialog.Description className={'pr-4'}>
{content}
</HeadlessDialog.Description>
</div>
</div>
{buttons && <div className={styles.button_bar}>{buttons}</div>}
{/* Keep this below the other buttons so that it isn't the default focus if they're present. */}
<div className={'absolute right-0 top-0 m-4'}>
<Button.Text square small onClick={() => onDismissed()} className={'hover:rotate-90'}>
<XIcon className={'w-5 h-5'}/>
</Button.Text>
</div>
</div>
</Transition.Child>
</div>
</HeadlessDialog>
</Transition>
);
};
const _Dialog = Object.assign(Dialog, { Buttons: DialogButtons, Icon: DialogIcon });
export default _Dialog;

View File

@ -0,0 +1,20 @@
.wrapper {
@apply fixed z-10 inset-0 overflow-y-auto;
}
.overlay {
@apply fixed inset-0 bg-gray-900 opacity-50;
}
.container {
@apply relative bg-gray-600 rounded max-w-xl w-full mx-auto shadow-lg;
@apply ring-4 ring-gray-800 ring-opacity-80;
& .title {
@apply font-header text-xl font-medium mb-2 text-white pr-4;
}
& > .button_bar {
@apply px-6 py-3 bg-gray-700 flex items-center justify-end space-x-3 rounded-b;
}
}

View File

@ -0,0 +1,2 @@
export { default as Dialog } from './Dialog';
export { default as styles } from './dialog.module.css';

View File

@ -0,0 +1,62 @@
import React, { ElementType, forwardRef, useMemo } from 'react';
import { Menu, Transition } from '@headlessui/react';
import styles from './dropdown.module.css';
import classNames from 'classnames';
import DropdownItem from '@/components/elements/dropdown/DropdownItem';
import DropdownButton from '@/components/elements/dropdown/DropdownButton';
interface Props {
as?: ElementType;
children: React.ReactNode;
}
const DropdownGap = ({ invisible }: { invisible?: boolean }) => (
<div className={classNames('border m-2', { 'border-neutral-700': !invisible, 'border-transparent': invisible })}/>
);
type TypedChild = (React.ReactChild | React.ReactFragment | React.ReactPortal) & {
type?: JSX.Element;
}
const Dropdown = forwardRef<typeof Menu, Props>(({ as, children }, ref) => {
const [ Button, items ] = useMemo(() => {
const list = React.Children.toArray(children) as unknown as TypedChild[];
return [
list.filter(child => child.type === DropdownButton),
list.filter(child => child.type !== DropdownButton),
];
}, [ children ]);
if (!Button) {
throw new Error('Cannot mount <Dropdown /> component without a child <Dropdown.Button />.');
}
return (
<Menu as={as || 'div'} className={styles.menu} ref={ref}>
{Button}
<Transition
enter={'transition duration-100 ease-out'}
enterFrom={'transition scale-95 opacity-0'}
enterTo={'transform scale-100 opacity-100'}
leave={'transition duration-75 ease-out'}
leaveFrom={'transform scale-100 opacity-100'}
leaveTo={'transform scale-95 opacity-0'}
>
<Menu.Items className={classNames(styles.items_container, 'w-56')}>
<div className={'px-1 py-1'}>
{items}
</div>
</Menu.Items>
</Transition>
</Menu>
);
});
const _Dropdown = Object.assign(Dropdown, {
Button: DropdownButton,
Item: DropdownItem,
Gap: DropdownGap,
});
export { _Dropdown as default };

View File

@ -0,0 +1,24 @@
import classNames from 'classnames';
import styles from '@/components/elements/dropdown/dropdown.module.css';
import { ChevronDownIcon } from '@heroicons/react/solid';
import { Menu } from '@headlessui/react';
import React from 'react';
interface Props {
className?: string;
animate?: boolean;
children: React.ReactNode;
}
export default ({ className, animate = true, children }: Props) => (
<Menu.Button className={classNames(styles.button, className || 'px-4')}>
{typeof children === 'string' ?
<>
<span className={'mr-2'}>{children}</span>
<ChevronDownIcon aria-hidden={'true'} data-animated={animate.toString()}/>
</>
:
children
}
</Menu.Button>
);

View File

@ -0,0 +1,43 @@
import React, { forwardRef } from 'react';
import { Menu } from '@headlessui/react';
import styles from './dropdown.module.css';
import classNames from 'classnames';
interface Props {
children: React.ReactNode | ((opts: { active: boolean; disabled: boolean }) => JSX.Element);
danger?: boolean;
disabled?: boolean;
className?: string;
icon?: JSX.Element;
onClick?: (e: React.MouseEvent) => void;
}
const DropdownItem = forwardRef<HTMLAnchorElement, Props>(({
disabled,
danger,
className,
onClick,
children,
icon: IconComponent,
}, ref) => {
return (
<Menu.Item disabled={disabled}>
{({ disabled, active }) => (
<a
ref={ref}
href={'#'}
className={classNames(styles.menu_item, {
[styles.danger]: danger,
[styles.disabled]: disabled,
}, className)}
onClick={onClick}
>
{IconComponent}
{typeof children === 'function' ? children({ disabled, active }) : children}
</a>
)}
</Menu.Item>
);
});
export default DropdownItem;

View File

@ -0,0 +1,58 @@
.menu {
@apply relative inline-block text-left;
& .button {
@apply inline-flex justify-center items-center w-full py-2 text-neutral-100 rounded-md;
@apply transition-all duration-100;
&:hover, &[aria-expanded="true"] {
@apply bg-neutral-600 text-white;
}
&:focus, &:focus-within, &:active {
@apply ring-2 ring-opacity-50 ring-neutral-300 text-white;
}
& svg {
@apply w-5 h-5 transition-transform duration-75;
}
&[aria-expanded="true"] svg[data-animated="true"] {
@apply rotate-180;
}
}
& .items_container {
@apply absolute right-0 mt-2 origin-top-right bg-neutral-900 rounded z-10;
}
}
.menu_item {
@apply flex items-center rounded w-full px-2 py-2;
& svg {
@apply w-4 h-4 mr-4 text-neutral-300;
}
&:hover, &:focus {
@apply bg-blue-500 text-blue-50;
& svg {
@apply text-blue-50;
}
}
&.danger {
&:hover, &:focus {
@apply bg-red-500 text-red-50;
& svg {
@apply text-red-50;
}
}
}
&.disabled {
@apply cursor-not-allowed hover:bg-neutral-800 opacity-30 focus:bg-transparent focus:hover:bg-neutral-800;
}
}

View File

@ -0,0 +1,2 @@
export { default as Dropdown } from './Dropdown';
export * as styles from './dropdown.module.css';

View File

@ -0,0 +1,19 @@
import React, { forwardRef } from 'react';
import styles from './inputs.module.css';
import classNames from 'classnames';
type Props = Omit<React.ComponentProps<'input'>, 'type'> & {
indeterminate?: boolean;
}
export default forwardRef<HTMLInputElement, Props>(({ className, indeterminate, ...props }, ref) => (
<input
ref={ref}
type={'checkbox'}
className={classNames('form-checkbox', {
[styles.checkbox]: true,
[styles.indeterminate]: indeterminate,
}, className)}
{...props}
/>
));

View File

@ -0,0 +1,11 @@
import React, { forwardRef } from 'react';
import classNames from 'classnames';
import styles from './inputs.module.css';
export default forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(({ className, ...props }, ref) => (
<input
ref={ref}
className={classNames('form-input', styles.text_input, className)}
{...props}
/>
));

View File

@ -0,0 +1,3 @@
export { default as Checkbox } from './Checkbox';
export { default as InputField } from './InputField';
export { default as styles } from './inputs.module.css';

View File

@ -0,0 +1,21 @@
.checkbox {
@apply w-4 h-4 rounded-sm border-neutral-500 bg-neutral-600 text-primary-500;
&:focus, &:active {
@apply ring-2 ring-primary-500 ring-offset-2 ring-offset-neutral-700;
}
&.indeterminate:checked {
@apply text-primary-500/50 border border-primary-500;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='white'%3E%3Cpath fill-rule='evenodd' d='M5 10a1 1 0 011-1h8a1 1 0 110 2H6a1 1 0 01-1-1z' clip-rule='evenodd' /%3E%3C/svg%3E");
}
}
.text_input {
@apply transition-all duration-75;
@apply bg-neutral-800 border-neutral-600 rounded px-4 py-2 outline-none;
&:focus {
@apply border-blue-600 ring-2 ring-blue-500;
}
}

View File

@ -1,3 +1,4 @@
declare module '*.jpg'; declare module '*.jpg';
declare module '*.png'; declare module '*.png';
declare module '*.svg'; declare module '*.svg';
declare module '*.css';

View File

@ -15,7 +15,6 @@ import LocationEditContainer from '@/components/admin/locations/LocationEditCont
import ServersContainer from '@/components/admin/servers/ServersContainer'; import ServersContainer from '@/components/admin/servers/ServersContainer';
import NewServerContainer from '@/components/admin/servers/NewServerContainer'; import NewServerContainer from '@/components/admin/servers/NewServerContainer';
import ServerRouter from '@/components/admin/servers/ServerRouter'; import ServerRouter from '@/components/admin/servers/ServerRouter';
import UsersContainer from '@/components/admin/users/UsersContainer';
import NewUserContainer from '@/components/admin/users/NewUserContainer'; import NewUserContainer from '@/components/admin/users/NewUserContainer';
import UserRouter from '@/components/admin/users/UserRouter'; import UserRouter from '@/components/admin/users/UserRouter';
import RolesContainer from '@/components/admin/roles/RolesContainer'; import RolesContainer from '@/components/admin/roles/RolesContainer';
@ -46,6 +45,7 @@ import {
import CollapsedIcon from '@/assets/images/pterodactyl.svg'; import CollapsedIcon from '@/assets/images/pterodactyl.svg';
import Sidebar from '@/components/admin/Sidebar'; import Sidebar from '@/components/admin/Sidebar';
import useUserPersistedState from '@/plugins/useUserPersistedState'; import useUserPersistedState from '@/plugins/useUserPersistedState';
import UsersContainerV2 from '@/components/admin/users/UsersContainerV2';
const AdminRouter = ({ location, match }: RouteComponentProps) => { const AdminRouter = ({ location, match }: RouteComponentProps) => {
const email = useStoreState((state: State<ApplicationStore>) => state.user.data!.email); const email = useStoreState((state: State<ApplicationStore>) => state.user.data!.email);
@ -132,7 +132,7 @@ const AdminRouter = ({ location, match }: RouteComponentProps) => {
<Route path={`${match.path}/servers`} component={ServersContainer} exact/> <Route path={`${match.path}/servers`} component={ServersContainer} exact/>
<Route path={`${match.path}/servers/new`} component={NewServerContainer} exact/> <Route path={`${match.path}/servers/new`} component={NewServerContainer} exact/>
<Route path={`${match.path}/servers/:id`} component={ServerRouter}/> <Route path={`${match.path}/servers/:id`} component={ServerRouter}/>
<Route path={`${match.path}/users`} component={UsersContainer} exact/> <Route path={`${match.path}/users`} component={UsersContainerV2} exact/>
<Route path={`${match.path}/users/new`} component={NewUserContainer} exact/> <Route path={`${match.path}/users/new`} component={NewUserContainer} exact/>
<Route path={`${match.path}/users/:id`} component={UserRouter}/> <Route path={`${match.path}/users/:id`} component={UserRouter}/>
<Route path={`${match.path}/roles`} component={RolesContainer} exact/> <Route path={`${match.path}/roles`} component={RolesContainer} exact/>

View File

@ -5,38 +5,16 @@ module.exports = {
'./resources/scripts/**/*.{js,ts,tsx}', './resources/scripts/**/*.{js,ts,tsx}',
], ],
theme: { theme: {
fontFamily: {
sans: [ 'Rubik', '-apple-system', 'BlinkMacSystemFont', '"Helvetica Neue"', '"Roboto"', 'system-ui', 'sans-serif' ],
header: [ '"IBM Plex Sans"', '"Roboto"', 'system-ui', 'sans-serif' ],
mono: [ '"IBM Plex Mono"', '"Source Code Pro"', 'SourceCodePro', 'Menlo', 'Monaco', 'Consolas', 'monospace' ],
},
extend: { extend: {
fontFamily: {
header: [ '"IBM Plex Sans"', '"Roboto"', 'system-ui', 'sans-serif' ],
},
colors: { colors: {
black: '#131a20', black: '#131a20',
primary: { // Deprecated, prefer "blue"...
50: '#e6f6ff', primary: colors.blue,
100: '#b8e2ff', // Deprecate, prefer "gray"...
200: '#7ac3fa', neutral: colors.gray,
300: '#49a4f3',
400: '#2487eb',
500: '#0967d3',
600: '#0550b3',
700: '#0345a0',
800: '#01337e',
900: '#002057',
},
neutral: {
50: '#f5f7fa',
100: '#e5e8eb',
200: '#cad1d8',
300: '#9aa5b1',
400: '#7b8793',
500: '#606d7b',
600: '#515f6c',
700: '#3f4d5a',
800: '#33404d',
900: '#1f2933',
},
cyan: colors.cyan, cyan: colors.cyan,
}, },
fontSize: { fontSize: {
@ -51,6 +29,8 @@ module.exports = {
}, },
}, },
plugins: [ plugins: [
require('@tailwindcss/forms'), require('@tailwindcss/forms')({
strategy: 'class',
}),
] ]
}; };

View File

@ -36,6 +36,11 @@ module.exports = {
{ {
loader: 'css-loader', loader: 'css-loader',
options: { options: {
modules: {
auto: true,
localIdentName: isProduction ? '[name]_[hash:base64:8]' : '[path][name]__[local]',
localIdentContext: path.join(__dirname, "resources/scripts/components"),
},
sourceMap: !isProduction, sourceMap: !isProduction,
importLoaders: 1, importLoaders: 1,
}, },

View File

@ -2474,6 +2474,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@headlessui/react@npm:^1.5.0":
version: 1.5.0
resolution: "@headlessui/react@npm:1.5.0"
peerDependencies:
react: ^16 || ^17 || ^18
react-dom: ^16 || ^17 || ^18
checksum: e3373dfb73936950d659a87718fb9fdedc599b344b30cb0d8b1ef0a2ec13d6f405c653fa51284236788658840ece82c6a8538a5a5931e595274e9590b2020079
languageName: node
linkType: hard
"@heroicons/react@npm:^1.0.5": "@heroicons/react@npm:^1.0.5":
version: 1.0.5 version: 1.0.5
resolution: "@heroicons/react@npm:1.0.5" resolution: "@heroicons/react@npm:1.0.5"
@ -4640,6 +4650,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"classnames@npm:^2.3.1":
version: 2.3.1
resolution: "classnames@npm:2.3.1"
checksum: 14db8889d56c267a591f08b0834989fe542d47fac659af5a539e110cc4266694e8de86e4e3bbd271157dbd831361310a8293e0167141e80b0f03a0f175c80960
languageName: node
linkType: hard
"clean-set@npm:^1.1.1": "clean-set@npm:^1.1.1":
version: 1.1.2 version: 1.1.2
resolution: "clean-set@npm:1.1.2" resolution: "clean-set@npm:1.1.2"
@ -10933,6 +10950,7 @@ fsevents@^1.2.7:
"@fortawesome/free-regular-svg-icons": ^5.15.4 "@fortawesome/free-regular-svg-icons": ^5.15.4
"@fortawesome/free-solid-svg-icons": ^5.15.4 "@fortawesome/free-solid-svg-icons": ^5.15.4
"@fortawesome/react-fontawesome": ^0.1.16 "@fortawesome/react-fontawesome": ^0.1.16
"@headlessui/react": ^1.5.0
"@heroicons/react": ^1.0.5 "@heroicons/react": ^1.0.5
"@hot-loader/react-dom": ^16.14.0 "@hot-loader/react-dom": ^16.14.0
"@tailwindcss/forms": ^0.4.0 "@tailwindcss/forms": ^0.4.0
@ -10965,6 +10983,7 @@ fsevents@^1.2.7:
babel-plugin-styled-components: ^2.0.3 babel-plugin-styled-components: ^2.0.3
browserslist: ^4.17.6 browserslist: ^4.17.6
chart.js: ^2.9.4 chart.js: ^2.9.4
classnames: ^2.3.1
cross-env: ^7.0.3 cross-env: ^7.0.3
css-loader: ^5.2.7 css-loader: ^5.2.7
date-fns: ^2.25.0 date-fns: ^2.25.0