From 3779245efd6e29b03f69cbae304b3c37b921c540 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Mon, 26 Jun 2023 20:10:58 -0400 Subject: [PATCH 1/3] remove `withLoadingAndError` and clean up 2fa --- .../components/withLoadingAndError/index.ts | 12 -- .../withLoadingAndError.test.tsx | 53 ------- .../withLoadingAndError.ts | 75 --------- .../TwoFactor/DisableTwoFactorDialog.tsx | 142 ++++++------------ .../TwoFactor/ScratchCodeDialog.tsx | 79 ++++------ .../TwoFactor/TwoFactor.tsx | 12 +- packages/manager/src/queries/profile.ts | 10 ++ 7 files changed, 93 insertions(+), 290 deletions(-) delete mode 100644 packages/manager/src/components/withLoadingAndError/index.ts delete mode 100644 packages/manager/src/components/withLoadingAndError/withLoadingAndError.test.tsx delete mode 100644 packages/manager/src/components/withLoadingAndError/withLoadingAndError.ts diff --git a/packages/manager/src/components/withLoadingAndError/index.ts b/packages/manager/src/components/withLoadingAndError/index.ts deleted file mode 100644 index 965c7c3a465..00000000000 --- a/packages/manager/src/components/withLoadingAndError/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import withLoadingAndError, { - LoadingAndErrorHandlers as _LoadingAndErrorHandlers, - LoadingAndErrorState as _LoadingAndErrorState, - Props as _Props, -} from './withLoadingAndError'; - -/* tslint:disable */ -export interface LoadingAndErrorHandlers extends _LoadingAndErrorHandlers {} -export interface LoadingAndErrorState extends _LoadingAndErrorState {} -export interface Props extends _Props {} - -export default withLoadingAndError; diff --git a/packages/manager/src/components/withLoadingAndError/withLoadingAndError.test.tsx b/packages/manager/src/components/withLoadingAndError/withLoadingAndError.test.tsx deleted file mode 100644 index acf9868a551..00000000000 --- a/packages/manager/src/components/withLoadingAndError/withLoadingAndError.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { shallow } from 'enzyme'; -import * as React from 'react'; - -import withLoadingAndError from './withLoadingAndError'; - -const MyComponent: React.SFC<{}> = (props) => { - return
; -}; - -const EnhancedComponent = withLoadingAndError(MyComponent); - -const component = shallow(); - -describe('withLoadingAndError HOC', () => { - describe('Props are defined', () => { - it('should have a loading prop', () => { - expect(component.props().loading).toBeDefined(); - }); - it('should have a setLoadingAndClearErrors prop', () => { - expect(component.props().setLoadingAndClearErrors).toBeDefined(); - }); - it('should have a setErrorAndClearLoading prop', () => { - expect(component.props().setErrorAndClearLoading).toBeDefined(); - }); - it('should have a clearLoadingAndErrors prop', () => { - expect(component.props().clearLoadingAndErrors).toBeDefined(); - }); - }); - - describe('when setLoadingAndClearErrors prop is called', () => { - it('should set loading prop and clear errors', () => { - component.props().setLoadingAndClearErrors(); - expect(component.props().loading).toBeTruthy(); - expect(component.props().error).toBeUndefined(); - }); - }); - - describe('when setErrorAndClearLoading prop is called', () => { - it('should set loading prop and clear errors', () => { - component.props().setErrorAndClearLoading('hello world'); - expect(component.props().loading).toBeFalsy(); - expect(component.props().error).toBe('hello world'); - }); - }); - - describe('when clearLoadingAndErrors prop is called', () => { - it('should set loading prop and clear errors', () => { - component.props().clearLoadingAndErrors(); - expect(component.props().loading).toBeFalsy(); - expect(component.props().error).toBeUndefined(); - }); - }); -}); diff --git a/packages/manager/src/components/withLoadingAndError/withLoadingAndError.ts b/packages/manager/src/components/withLoadingAndError/withLoadingAndError.ts deleted file mode 100644 index 0f29eb0da3c..00000000000 --- a/packages/manager/src/components/withLoadingAndError/withLoadingAndError.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { StateHandlerMap, withStateHandlers } from 'recompose'; - -export interface LoadingAndErrorState { - loading: boolean; - error?: string; -} - -export interface LoadingAndErrorHandlers { - setLoadingAndClearErrors: () => void; - setErrorAndClearLoading: (error: string) => void; - clearLoadingAndErrors: () => void; -} - -export type Props = LoadingAndErrorHandlers & LoadingAndErrorState; - -type StateAndStateUpdaters = StateHandlerMap & - LoadingAndErrorHandlers; - -/** - * This function is intended to be used similar to HOCs for the purpose of - * supplying the child component with a loading and error context. - * - * An example usage would look similar to the following - * - * @example - * const getMyInstances = () => { - * - * setLoadingAndClearErrors(); - * - * return getInstances() - * .then(response => { - * clearLoadingAndErrors(); - * }) - * .catch(e => { - * setErrorAndClearLoading('There was an issue!') - * }) - * } - * - * - * render() { - * if (props.loading) { return Loading... } - * - * if (props.error) { return Error message! } - * - * return (
Hello world!
) - * } - */ -const withStateAndHandlers = withStateHandlers< - LoadingAndErrorState, - StateAndStateUpdaters, - {} ->( - { - loading: false, - error: undefined, - }, - { - setLoadingAndClearErrors: () => () => ({ - loading: true, - error: undefined, - }), - setErrorAndClearLoading: () => (error: string) => ({ - loading: false, - error, - }), - clearLoadingAndErrors: () => () => ({ - loading: false, - error: undefined, - }), - } -); - -const withLoadingAndError = (Component: any) => withStateAndHandlers(Component); - -export default withLoadingAndError; diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/DisableTwoFactorDialog.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/DisableTwoFactorDialog.tsx index cd2009fa79d..7fe7d180857 100644 --- a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/DisableTwoFactorDialog.tsx +++ b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/DisableTwoFactorDialog.tsx @@ -1,111 +1,65 @@ -import { disableTwoFactor } from '@linode/api-v4/lib/profile'; import * as React from 'react'; -import { compose } from 'recompose'; import ActionsPanel from 'src/components/ActionsPanel'; import Button from 'src/components/Button'; import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog'; import Typography from 'src/components/core/Typography'; -import withLoadingAndError, { - Props as LoadingAndErrorProps, -} from 'src/components/withLoadingAndError'; -import { getErrorStringOrDefault } from 'src/utilities/errorUtils'; +import { useDisableTwoFactorMutation } from 'src/queries/profile'; interface Props { open: boolean; - closeDialog: () => void; + onClose: () => void; onSuccess: () => void; } -type CombinedProps = Props & LoadingAndErrorProps; +export const DisableTwoFactorDialog = (props: Props) => { + const { open, onClose, onSuccess } = props; -class DisableTwoFactorDialog extends React.PureComponent { - handleCloseDialog = () => { - this.props.clearLoadingAndErrors(); - this.props.closeDialog(); - }; + const { + mutateAsync: disableTwoFactor, + error, + isLoading, + reset, + } = useDisableTwoFactorMutation(); - handleDisableTFA = (deviceId: number) => { - const { - setLoadingAndClearErrors, - clearLoadingAndErrors, - setErrorAndClearLoading, - closeDialog, - } = this.props; - setLoadingAndClearErrors(); - disableTwoFactor() - .then(() => { - clearLoadingAndErrors(); - closeDialog(); - this.props.onSuccess(); - }) - .catch((e) => { - const errorString = getErrorStringOrDefault( - e, - 'There was an error disabling 2FA.' - ); - setErrorAndClearLoading(errorString); - }); + const handleDisableTFA = async () => { + await disableTwoFactor(); + onClose(); + onSuccess(); }; - render() { - const { open, closeDialog, error, loading } = this.props; - - return ( - - } - > - - Are you sure you want to disable two-factor authentication? - - - ); - } -} -export default compose(withLoadingAndError)( - DisableTwoFactorDialog -); + React.useEffect(() => { + if (open) { + reset(); + } + }, [open]); -interface ActionsProps { - closeDialog: () => void; - loading: boolean; - handleDisable: (deviceId?: number) => void; - deviceId?: number; -} + const actions = ( + + + + + ); -class DialogActions extends React.PureComponent { - handleSubmit = () => { - const { handleDisable, deviceId } = this.props; - return handleDisable(deviceId); - }; - render() { - return ( - - - - - ); - } -} + return ( + + + Are you sure you want to disable two-factor authentication? + + + ); +}; diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/ScratchCodeDialog.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/ScratchCodeDialog.tsx index 6031ec86699..ecfdccee488 100644 --- a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/ScratchCodeDialog.tsx +++ b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/ScratchCodeDialog.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; - import ActionsPanel from 'src/components/ActionsPanel'; import Button from 'src/components/Button'; import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog'; @@ -7,58 +6,40 @@ import Typography from 'src/components/core/Typography'; interface Props { open: boolean; - closeDialog: () => void; + onClose: () => void; scratchCode: string; } -class ScratchCodeDialog extends React.PureComponent { - render() { - const { open, closeDialog, scratchCode } = this.props; +export const ScratchCodeDialog = (props: Props) => { + const { open, onClose, scratchCode } = props; - return ( - } - > - - {`This scratch code can be used in place of two-factor authentication in the event + return ( + + + + } + > + + {`This scratch code can be used in place of two-factor authentication in the event you cannot access your two-factor authentication device. It is limited to a one-time use. Be sure to make a note of it and keep it secure, as this is the only time it will appear:`} - - - {scratchCode} - - - ); - } -} - -export default ScratchCodeDialog; - -interface ActionsProps { - closeDialog: () => void; -} - -class DialogActions extends React.PureComponent { - render() { - return ( - - - - ); - } -} + + + {scratchCode} + + + ); +}; diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/TwoFactor.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/TwoFactor.tsx index d019bfb7ba9..a44bfb7e6c6 100644 --- a/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/TwoFactor.tsx +++ b/packages/manager/src/features/Profile/AuthenticationSettings/TwoFactor/TwoFactor.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; -import DisableTwoFactorDialog from './DisableTwoFactorDialog'; +import { DisableTwoFactorDialog } from './DisableTwoFactorDialog'; import getAPIErrorFor from 'src/utilities/getAPIErrorFor'; -import ScratchDialog from './ScratchCodeDialog'; +import { ScratchCodeDialog } from './ScratchCodeDialog'; import Typography from 'src/components/core/Typography'; import { APIError } from '@linode/api-v4/lib/types'; import { EnableTwoFactorForm } from './EnableTwoFactorForm'; @@ -71,8 +71,6 @@ export const TwoFactor = (props: TwoFactorProps) => { * success when TFA is disabled */ const handleDisableSuccess = () => { - // Refetch Profile with React Query so profile is up to date - queryClient.invalidateQueries(queryKey); setErrors(undefined); setSuccess('Two-factor authentication has been disabled.'); setTwoFactorEnabled(false); @@ -227,15 +225,15 @@ export const TwoFactor = (props: TwoFactorProps) => { /> )} - ); diff --git a/packages/manager/src/queries/profile.ts b/packages/manager/src/queries/profile.ts index 550eded3d23..279deb63168 100644 --- a/packages/manager/src/queries/profile.ts +++ b/packages/manager/src/queries/profile.ts @@ -16,6 +16,7 @@ import { getTrustedDevices, TrustedDevice, deleteTrustedDevice, + disableTwoFactor, } from '@linode/api-v4/lib/profile'; import { QueryClient, @@ -171,3 +172,12 @@ export const useRevokeTrustedDeviceMutation = (id: number) => { }, }); }; + +export const useDisableTwoFactorMutation = () => { + const queryClient = useQueryClient(); + return useMutation<{}, APIError[]>(disableTwoFactor, { + onSuccess() { + queryClient.invalidateQueries([queryKey]); + }, + }); +}; From a7229ccb8b3b163579b7215f5be4a2cba8ab6603 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Mon, 26 Jun 2023 20:19:17 -0400 Subject: [PATCH 2/3] Added changeset: Remove `withLoadingAndError` and clean up 2FA components --- .../manager/.changeset/pr-9318-tech-stories-1687825157533.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/manager/.changeset/pr-9318-tech-stories-1687825157533.md diff --git a/packages/manager/.changeset/pr-9318-tech-stories-1687825157533.md b/packages/manager/.changeset/pr-9318-tech-stories-1687825157533.md new file mode 100644 index 00000000000..d9f53e58839 --- /dev/null +++ b/packages/manager/.changeset/pr-9318-tech-stories-1687825157533.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tech Stories +--- + +Remove `withLoadingAndError` and clean up 2FA components ([#9318](https://github.com/linode/manager/pull/9318)) From d18757f1e0c6f64eb70641f833595b1e4c372d57 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Fri, 30 Jun 2023 12:10:05 -0400 Subject: [PATCH 3/3] remove more legacy stuff --- packages/manager/src/requestableContext.tsx | 35 --------------------- 1 file changed, 35 deletions(-) delete mode 100644 packages/manager/src/requestableContext.tsx diff --git a/packages/manager/src/requestableContext.tsx b/packages/manager/src/requestableContext.tsx deleted file mode 100644 index b4c4cba3584..00000000000 --- a/packages/manager/src/requestableContext.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import * as React from 'react'; -import { APIError } from '@linode/api-v4/lib/types'; -import { getDisplayName } from 'src/utilities/getDisplayName'; - -export interface Requestable { - lastUpdated: number; - loading: boolean; - request: (...args: any[]) => Promise; - update: (f: (t: T) => T) => void; - data?: T; - errors?: APIError[]; -} - -/* tslint:disable */ -export function createHOCForConsumer(Consumer: any, displayName: string) { - return function withContext

(mapStateToProps?: (v: T) => any) { - return function (Component: React.ComponentType

) { - /* tslint:enable */ - return class ComponentWithContext extends React.Component

{ - static displayName = `${displayName}(${getDisplayName(Component)})`; - render() { - return ( - - {(c: any) => { - const context = mapStateToProps ? mapStateToProps(c) : c; - - return ; - }} - - ); - } - }; - }; - }; -}