React 18 and Vite (#4510)

This commit is contained in:
Matthew Penner 2022-11-25 13:25:03 -07:00 committed by GitHub
parent 1bb1b13f6d
commit 21613fa602
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
244 changed files with 4547 additions and 8933 deletions

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import ContentBox from '@/components/elements/ContentBox';
import CreateApiKeyForm from '@/components/dashboard/forms/CreateApiKeyForm';
import getApiKeys, { ApiKey } from '@/api/account/getApiKeys';
@ -23,9 +23,9 @@ export default () => {
useEffect(() => {
getApiKeys()
.then((keys) => setKeys(keys))
.then(keys => setKeys(keys))
.then(() => setLoading(false))
.catch((error) => clearAndAddHttpError(error));
.catch(error => clearAndAddHttpError(error));
}, []);
const doDeletion = (identifier: string) => {
@ -33,8 +33,8 @@ export default () => {
clearAndAddHttpError();
deleteApiKey(identifier)
.then(() => setKeys((s) => [...(s || []).filter((key) => key.identifier !== identifier)]))
.catch((error) => clearAndAddHttpError(error))
.then(() => setKeys(s => [...(s || []).filter(key => key.identifier !== identifier)]))
.catch(error => clearAndAddHttpError(error))
.then(() => {
setLoading(false);
setDeleteIdentifier('');
@ -46,7 +46,7 @@ export default () => {
<FlashMessageRender byKey={'account'} />
<div css={tw`md:flex flex-nowrap my-10`}>
<ContentBox title={'Create API Key'} css={tw`flex-none w-full md:w-1/2`}>
<CreateApiKeyForm onKeyCreated={(key) => setKeys((s) => [...s!, key])} />
<CreateApiKeyForm onKeyCreated={key => setKeys(s => [...s!, key])} />
</ContentBox>
<ContentBox title={'API Keys'} css={tw`flex-1 overflow-hidden mt-8 md:mt-0 md:ml-8`}>
<SpinnerOverlay visible={loading} />

View file

@ -1,4 +1,3 @@
import * as React from 'react';
import ContentBox from '@/components/elements/ContentBox';
import UpdatePasswordForm from '@/components/dashboard/forms/UpdatePasswordForm';
import UpdateEmailAddressForm from '@/components/dashboard/forms/UpdateEmailAddressForm';
@ -6,7 +5,7 @@ import ConfigureTwoFactorForm from '@/components/dashboard/forms/ConfigureTwoFac
import PageContentBlock from '@/components/elements/PageContentBlock';
import tw from 'twin.macro';
import { breakpoint } from '@/theme';
import styled from 'styled-components/macro';
import styled from 'styled-components';
import MessageBox from '@/components/MessageBox';
import { useLocation } from 'react-router-dom';
@ -27,24 +26,26 @@ const Container = styled.div`
`;
export default () => {
const { state } = useLocation<undefined | { twoFactorRedirect?: boolean }>();
const { state } = useLocation();
return (
<PageContentBlock title={'Account Overview'}>
<PageContentBlock title="Account Overview">
{state?.twoFactorRedirect && (
<MessageBox title={'2-Factor Required'} type={'error'}>
<MessageBox title="2-Factor Required" type="error">
Your account must have two-factor authentication enabled in order to continue.
</MessageBox>
)}
<Container css={[tw`lg:grid lg:grid-cols-3 mb-10`, state?.twoFactorRedirect ? tw`mt-4` : tw`mt-10`]}>
<ContentBox title={'Update Password'} showFlashes={'account:password'}>
<ContentBox title="Update Password" showFlashes="account:password">
<UpdatePasswordForm />
</ContentBox>
<ContentBox css={tw`mt-8 sm:mt-0 sm:ml-8`} title={'Update Email Address'} showFlashes={'account:email'}>
<ContentBox css={tw`mt-8 sm:mt-0 sm:ml-8`} title="Update Email Address" showFlashes="account:email">
<UpdateEmailAddressForm />
</ContentBox>
<ContentBox css={tw`md:ml-8 mt-8 md:mt-0`} title={'Two-Step Verification'}>
<ContentBox css={tw`md:ml-8 mt-8 md:mt-0`} title="Two-Step Verification">
<ConfigureTwoFactorForm />
</ContentBox>
</Container>

View file

@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import { useContext } from 'react';
import tw from 'twin.macro';
import Button from '@/components/elements/Button';
import asModal from '@/hoc/asModal';

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { Server } from '@/api/server/getServer';
import getServers from '@/api/getServers';
import ServerRow from '@/components/dashboard/ServerRow';
@ -20,13 +20,13 @@ export default () => {
const [page, setPage] = useState(!isNaN(defaultPage) && defaultPage > 0 ? defaultPage : 1);
const { clearFlashes, clearAndAddHttpError } = useFlash();
const uuid = useStoreState((state) => state.user.data!.uuid);
const rootAdmin = useStoreState((state) => state.user.data!.rootAdmin);
const uuid = useStoreState(state => state.user.data!.uuid);
const rootAdmin = useStoreState(state => state.user.data!.rootAdmin);
const [showOnlyAdmin, setShowOnlyAdmin] = usePersistedState(`${uuid}:show_all_servers`, false);
const { data: servers, error } = useSWR<PaginatedResult<Server>>(
['/api/client/servers', showOnlyAdmin && rootAdmin, page],
() => getServers({ page, type: showOnlyAdmin && rootAdmin ? 'admin' : undefined })
() => getServers({ page, type: showOnlyAdmin && rootAdmin ? 'admin' : undefined }),
);
useEffect(() => {
@ -58,7 +58,7 @@ export default () => {
<Switch
name={'show_all_servers'}
defaultChecked={showOnlyAdmin}
onChange={() => setShowOnlyAdmin((s) => !s)}
onChange={() => setShowOnlyAdmin(s => !s)}
/>
</div>
)}

View file

@ -1,4 +1,5 @@
import React, { memo, useEffect, useRef, useState } from 'react';
import { memo, useEffect, useRef, useState } from 'react';
import * as React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons';
import { Link } from 'react-router-dom';
@ -8,7 +9,7 @@ import { bytesToString, ip, mbToBytes } from '@/lib/formatters';
import tw from 'twin.macro';
import GreyRowBox from '@/components/elements/GreyRowBox';
import Spinner from '@/components/elements/Spinner';
import styled from 'styled-components/macro';
import styled from 'styled-components';
import isEqual from 'react-fast-compare';
// Determines if the current value is in an alarm threshold so we can show it in red rather
@ -17,14 +18,14 @@ const isAlarmState = (current: number, limit: number): boolean => limit > 0 && c
const Icon = memo(
styled(FontAwesomeIcon)<{ $alarm: boolean }>`
${(props) => (props.$alarm ? tw`text-red-400` : tw`text-neutral-500`)};
${props => (props.$alarm ? tw`text-red-400` : tw`text-neutral-500`)};
`,
isEqual
isEqual,
);
const IconDescription = styled.p<{ $alarm: boolean }>`
${tw`text-sm ml-2`};
${(props) => (props.$alarm ? tw`text-white` : tw`text-neutral-400`)};
${props => (props.$alarm ? tw`text-white` : tw`text-neutral-400`)};
`;
const StatusIndicatorBox = styled(GreyRowBox)<{ $status: ServerPowerState | undefined }>`
@ -56,8 +57,8 @@ export default ({ server, className }: { server: Server; className?: string }) =
const getStats = () =>
getServerResourceUsage(server.uuid)
.then((data) => setStats(data))
.catch((error) => console.error(error));
.then(data => setStats(data))
.catch(error => console.error(error));
useEffect(() => {
setIsSuspended(stats?.isSuspended || server.status === 'suspended');
@ -106,8 +107,8 @@ export default ({ server, className }: { server: Server; className?: string }) =
<FontAwesomeIcon icon={faEthernet} css={tw`text-neutral-500`} />
<p css={tw`text-sm text-neutral-400 ml-2`}>
{server.allocations
.filter((alloc) => alloc.isDefault)
.map((allocation) => (
.filter(alloc => alloc.isDefault)
.map(allocation => (
<React.Fragment key={allocation.ip + allocation.port.toString()}>
{allocation.alias || ip(allocation.ip)}:{allocation.port}
</React.Fragment>

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { ActivityLogFilters, useActivityLogs } from '@/api/account/activity';
import { useFlashKey } from '@/plugins/useFlash';
import PageContentBlock from '@/components/elements/PageContentBlock';
@ -23,7 +23,7 @@ export default () => {
});
useEffect(() => {
setFilters((value) => ({ ...value, filters: { ip: hash.ip, event: hash.event } }));
setFilters(value => ({ ...value, filters: { ip: hash.ip, event: hash.event } }));
}, [hash]);
useEffect(() => {
@ -38,7 +38,7 @@ export default () => {
<Link
to={'#'}
className={classNames(btnStyles.button, btnStyles.text, 'w-full sm:w-auto')}
onClick={() => setFilters((value) => ({ ...value, filters: {} }))}
onClick={() => setFilters(value => ({ ...value, filters: {} }))}
>
Clear Filters <XCircleIcon className={'w-4 h-4 ml-2'} />
</Link>
@ -48,7 +48,7 @@ export default () => {
<Spinner centered />
) : (
<div className={'bg-gray-700'}>
{data?.items.map((activity) => (
{data?.items.map(activity => (
<ActivityLogEntry key={activity.id} activity={activity}>
{typeof activity.properties.useragent === 'string' && (
<Tooltip content={activity.properties.useragent} placement={'top'}>
@ -64,7 +64,7 @@ export default () => {
{data && (
<PaginationFooter
pagination={data.pagination}
onPageSelect={(page) => setFilters((value) => ({ ...value, page }))}
onPageSelect={page => setFilters(value => ({ ...value, page }))}
/>
)}
</PageContentBlock>

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { useStoreState } from 'easy-peasy';
import { ApplicationStore } from '@/state';
import tw from 'twin.macro';

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import { Field, Form, Formik, FormikHelpers } from 'formik';
import { object, string } from 'yup';
import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper';
@ -11,7 +11,7 @@ import { ApiKey } from '@/api/account/getApiKeys';
import tw from 'twin.macro';
import Button from '@/components/elements/Button';
import Input, { Textarea } from '@/components/elements/Input';
import styled from 'styled-components/macro';
import styled from 'styled-components';
import ApiKeyModal from '@/components/dashboard/ApiKeyModal';
interface Values {
@ -36,7 +36,7 @@ export default ({ onKeyCreated }: { onKeyCreated: (key: ApiKey) => void }) => {
setApiKey(`${key.identifier}${secretToken}`);
onKeyCreated(key);
})
.catch((error) => {
.catch(error => {
console.error(error);
addError({ key: 'account', message: httpErrorToHuman(error) });

View file

@ -1,4 +1,5 @@
import React, { useContext, useEffect, useState } from 'react';
import { useContext, useEffect, useState } from 'react';
import * as React from 'react';
import asDialog from '@/hoc/asDialog';
import { Dialog, DialogWrapperContext } from '@/components/elements/dialog';
import { Button } from '@/components/elements/button/index';
@ -14,10 +15,10 @@ const DisableTOTPDialog = () => {
const [password, setPassword] = useState('');
const { clearAndAddHttpError } = useFlashKey('account:two-step');
const { close, setProps } = useContext(DialogWrapperContext);
const updateUserData = useStoreActions((actions) => actions.user.updateUserData);
const updateUserData = useStoreActions(actions => actions.user.updateUserData);
useEffect(() => {
setProps((state) => ({ ...state, preventExternalClose: submitting }));
setProps(state => ({ ...state, preventExternalClose: submitting }));
}, [submitting]);
const submit = (e: React.FormEvent<HTMLFormElement>) => {
@ -48,7 +49,7 @@ const DisableTOTPDialog = () => {
type={'password'}
variant={Input.Text.Variants.Loose}
value={password}
onChange={(e) => setPassword(e.currentTarget.value)}
onChange={e => setPassword(e.currentTarget.value)}
/>
<Dialog.Footer>
<Button.Text onClick={close}>Cancel</Button.Text>

View file

@ -1,4 +1,3 @@
import React from 'react';
import { Dialog, DialogProps } from '@/components/elements/dialog';
import { Button } from '@/components/elements/button/index';
import CopyOnClick from '@/components/elements/CopyOnClick';
@ -30,7 +29,7 @@ export default ({ tokens, open, onClose }: RecoveryTokenDialogProps) => {
<Dialog.Icon position={'container'} type={'success'} />
<CopyOnClick text={tokens.join('\n')} showInNotification={false}>
<pre className={'bg-gray-800 rounded p-2 mt-6'}>
{grouped.map((value) => (
{grouped.map(value => (
<span key={value.join('_')} className={'block'}>
{value[0]}
<span className={'mx-2 selection:bg-gray-800'}>&nbsp;</span>

View file

@ -1,4 +1,5 @@
import React, { useContext, useEffect, useState } from 'react';
import { useContext, useEffect, useState } from 'react';
import * as React from 'react';
import { Dialog, DialogWrapperContext } from '@/components/elements/dialog';
import getTwoFactorTokenData, { TwoFactorTokenData } from '@/api/account/getTwoFactorTokenData';
import { useFlashKey } from '@/plugins/useFlash';
@ -32,11 +33,11 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
useEffect(() => {
getTwoFactorTokenData()
.then(setToken)
.catch((error) => clearAndAddHttpError(error));
.catch(error => clearAndAddHttpError(error));
}, []);
useEffect(() => {
setProps((state) => ({ ...state, preventExternalClose: submitting }));
setProps(state => ({ ...state, preventExternalClose: submitting }));
}, [submitting]);
const submit = (e: React.FormEvent<HTMLFormElement>) => {
@ -48,11 +49,11 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
setSubmitting(true);
clearAndAddHttpError();
enableAccountTwoFactor(value, password)
.then((tokens) => {
.then(tokens => {
updateUserData({ useTotp: true });
onTokens(tokens);
})
.catch((error) => {
.catch(error => {
clearAndAddHttpError(error);
setSubmitting(false);
});
@ -81,7 +82,7 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
aria-labelledby={'totp-code-description'}
variant={Input.Text.Variants.Loose}
value={value}
onChange={(e) => setValue(e.currentTarget.value)}
onChange={e => setValue(e.currentTarget.value)}
className={'mt-3'}
placeholder={'000000'}
type={'text'}
@ -97,7 +98,7 @@ const ConfigureTwoFactorForm = ({ onTokens }: Props) => {
className={'mt-1'}
type={'password'}
value={password}
onChange={(e) => setPassword(e.currentTarget.value)}
onChange={e => setPassword(e.currentTarget.value)}
/>
<Dialog.Footer>
<Button.Text onClick={close}>Cancel</Button.Text>

View file

@ -1,4 +1,4 @@
import React from 'react';
import { Fragment } from 'react';
import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy';
import { Form, Formik, FormikHelpers } from 'formik';
import * as Yup from 'yup';
@ -34,15 +34,15 @@ export default () => {
type: 'success',
key: 'account:email',
message: 'Your primary email has been updated.',
})
}),
)
.catch((error) =>
.catch(error =>
addFlash({
type: 'error',
key: 'account:email',
title: 'Error',
message: httpErrorToHuman(error),
})
}),
)
.then(() => {
resetForm();
@ -53,7 +53,7 @@ export default () => {
return (
<Formik onSubmit={submit} validationSchema={schema} initialValues={{ email: user!.email, password: '' }}>
{({ isSubmitting, isValid }) => (
<React.Fragment>
<Fragment>
<SpinnerOverlay size={'large'} visible={isSubmitting} />
<Form css={tw`m-0`}>
<Field id={'current_email'} type={'email'} name={'email'} label={'Email'} />
@ -69,7 +69,7 @@ export default () => {
<Button disabled={isSubmitting || !isValid}>Update Email</Button>
</div>
</Form>
</React.Fragment>
</Fragment>
)}
</Formik>
);

View file

@ -1,4 +1,4 @@
import React from 'react';
import { Fragment } from 'react';
import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy';
import { Form, Formik, FormikHelpers } from 'formik';
import Field from '@/components/elements/Field';
@ -24,7 +24,7 @@ const schema = Yup.object().shape({
'Password confirmation does not match the password you entered.',
function (value) {
return value === this.parent.password;
}
},
),
});
@ -43,26 +43,26 @@ export default () => {
// @ts-expect-error this is valid
window.location = '/auth/login';
})
.catch((error) =>
.catch(error =>
addFlash({
key: 'account:password',
type: 'error',
title: 'Error',
message: httpErrorToHuman(error),
})
}),
)
.then(() => setSubmitting(false));
};
return (
<React.Fragment>
<Fragment>
<Formik
onSubmit={submit}
validationSchema={schema}
initialValues={{ current: '', password: '', confirmPassword: '' }}
>
{({ isSubmitting, isValid }) => (
<React.Fragment>
<Fragment>
<SpinnerOverlay size={'large'} visible={isSubmitting} />
<Form css={tw`m-0`}>
<Field
@ -94,9 +94,9 @@ export default () => {
<Button disabled={isSubmitting || !isValid}>Update Password</Button>
</div>
</Form>
</React.Fragment>
</Fragment>
)}
</Formik>
</React.Fragment>
</Fragment>
);
};

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSearch } from '@fortawesome/free-solid-svg-icons';
import useEventListener from '@/plugins/useEventListener';
@ -18,7 +18,8 @@ export default () => {
return (
<>
{visible && <SearchModal appear visible={visible} onDismissed={() => setVisible(false)} />}
<SearchModal appear visible={visible} onDismissed={() => setVisible(false)} />
<Tooltip placement={'bottom'} content={'Search'}>
<div className={'navigation-link'} onClick={() => setVisible(true)}>
<FontAwesomeIcon icon={faSearch} />

View file

@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import Modal, { RequiredModalProps } from '@/components/elements/Modal';
import { Field, Form, Formik, FormikHelpers, useFormikContext } from 'formik';
import { Actions, useStoreActions, useStoreState } from 'easy-peasy';
@ -10,7 +10,7 @@ import getServers from '@/api/getServers';
import { Server } from '@/api/server/getServer';
import { ApplicationStore } from '@/state';
import { Link } from 'react-router-dom';
import styled from 'styled-components/macro';
import styled from 'styled-components';
import tw from 'twin.macro';
import Input from '@/components/elements/Input';
import { ip } from '@/lib/formatters';
@ -47,10 +47,10 @@ const SearchWatcher = () => {
export default ({ ...props }: Props) => {
const ref = useRef<HTMLInputElement>(null);
const isAdmin = useStoreState((state) => state.user.data!.rootAdmin);
const isAdmin = useStoreState(state => state.user.data!.rootAdmin);
const [servers, setServers] = useState<Server[]>([]);
const { clearAndAddHttpError, clearFlashes } = useStoreActions(
(actions: Actions<ApplicationStore>) => actions.flashes
(actions: Actions<ApplicationStore>) => actions.flashes,
);
const search = debounce(({ term }: Values, { setSubmitting }: FormikHelpers<Values>) => {
@ -58,8 +58,8 @@ export default ({ ...props }: Props) => {
// if (ref.current) ref.current.focus();
getServers({ query: term, type: isAdmin ? 'admin-all' : undefined })
.then((servers) => setServers(servers.items.filter((_, index) => index < 5)))
.catch((error) => {
.then(servers => setServers(servers.items.filter((_, index) => index < 5)))
.catch(error => {
console.error(error);
clearAndAddHttpError({ key: 'search', error });
})
@ -100,7 +100,7 @@ export default ({ ...props }: Props) => {
</Form>
{servers.length > 0 && (
<div css={tw`mt-6`}>
{servers.map((server) => (
{servers.map(server => (
<ServerResult
key={server.uuid}
to={`/server/${server.id}`}
@ -110,8 +110,8 @@ export default ({ ...props }: Props) => {
<p css={tw`text-sm`}>{server.name}</p>
<p css={tw`mt-1 text-xs text-neutral-400`}>
{server.allocations
.filter((alloc) => alloc.isDefault)
.map((allocation) => (
.filter(alloc => alloc.isDefault)
.map(allocation => (
<span key={allocation.ip + allocation.port.toString()}>
{allocation.alias || ip(allocation.ip)}:{allocation.port}
</span>

View file

@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import { useEffect } from 'react';
import ContentBox from '@/components/elements/ContentBox';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import FlashMessageRender from '@/components/FlashMessageRender';

View file

@ -1,4 +1,3 @@
import React from 'react';
import { Field, Form, Formik, FormikHelpers } from 'formik';
import { object, string } from 'yup';
import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper';
@ -6,7 +5,7 @@ import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import tw from 'twin.macro';
import Button from '@/components/elements/Button';
import Input, { Textarea } from '@/components/elements/Input';
import styled from 'styled-components/macro';
import styled from 'styled-components';
import { useFlashKey } from '@/plugins/useFlash';
import { createSSHKey, useSSHKeys } from '@/api/account/ssh-keys';
@ -27,11 +26,11 @@ export default () => {
clearAndAddHttpError();
createSSHKey(values.name, values.publicKey)
.then((key) => {
.then(key => {
resetForm();
mutate((data) => (data || []).concat(key));
mutate(data => (data || []).concat(key));
})
.catch((error) => clearAndAddHttpError(error))
.catch(error => clearAndAddHttpError(error))
.then(() => setSubmitting(false));
};

View file

@ -1,7 +1,7 @@
import tw from 'twin.macro';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import React, { useState } from 'react';
import { useState } from 'react';
import { useFlashKey } from '@/plugins/useFlash';
import { deleteSSHKey, useSSHKeys } from '@/api/account/ssh-keys';
import { Dialog } from '@/components/elements/dialog';
@ -16,9 +16,9 @@ export default ({ name, fingerprint }: { name: string; fingerprint: string }) =>
clearAndAddHttpError();
Promise.all([
mutate((data) => data?.filter((value) => value.fingerprint !== fingerprint), false),
mutate(data => data?.filter(value => value.fingerprint !== fingerprint), false),
deleteSSHKey(fingerprint),
]).catch((error) => {
]).catch(error => {
mutate(undefined, true).catch(console.error);
clearAndAddHttpError(error);
});