Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add approval flow success and error pages #6738

Merged
merged 7 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion app/components/Nav/Main/RootRPCMethodsUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
import { BN } from 'ethereumjs-util';
import Logger from '../../../util/Logger';
import Approve from '../../Views/ApproveView/Approve';
import ApprovalFlowLoader from '../../UI/ApprovalFlowLoader';
import ApprovalFlowLoader from '../../UI/Approval/ApprovalFlowLoader';
import WatchAssetRequest from '../../UI/WatchAssetRequest';
import AccountApproval from '../../UI/AccountApproval';
import TransactionTypes from '../../../core/TransactionTypes';
Expand All @@ -58,6 +58,8 @@ import {
selectProviderType,
} from '../../../selectors/networkController';
import { createAccountConnectNavDetails } from '../../Views/AccountConnect';
import { ApprovalResult } from '../../UI/Approval/ApprovalResult';
import { ApprovalResultType } from '../../UI/Approval/ApprovalResult/ApprovalResult';

const APPROVAL_TYPES_WITH_DISABLED_CLOSE_ON_APPROVE = [
ApprovalTypes.TRANSACTION,
Expand Down Expand Up @@ -91,6 +93,8 @@ const RootRPCMethodsUI = (props) => {

const [watchAsset, setWatchAsset] = useState(undefined);

const [approvalResultRequest, setApprovalResultRequest] = useState(undefined);

const [signMessageParams, setSignMessageParams] = useState(undefined);

const setTransactionObject = props.setTransactionObject;
Expand Down Expand Up @@ -403,6 +407,46 @@ const RootRPCMethodsUI = (props) => {
</Modal>
);

const onApprovalResultConfirm = () => {
setShowPendingApproval(false);
acceptPendingApproval(approvalResultRequest.id, approvalResultRequest.data);
setApprovalResultRequest(undefined);
};

const renderApprovalResultModal = () => {
if (
![ApprovalTypes.RESULT_SUCCESS, ApprovalTypes.RESULT_ERROR].includes(
showPendingApproval?.type,
)
) {
return null;
}
return (
<Modal
isVisible
animationIn="slideInUp"
animationOut="slideOutDown"
style={styles.bottomModal}
backdropColor={colors.overlay.default}
backdropOpacity={1}
animationInTiming={300}
animationOutTiming={300}
swipeDirection={'down'}
propagateSwipe
>
<ApprovalResult
requestData={approvalResultRequest?.data}
onConfirm={onApprovalResultConfirm}
requestType={
showPendingApproval.type === ApprovalTypes.RESULT_SUCCESS
? ApprovalResultType.Success
: ApprovalResultType.Failure
}
/>
</Modal>
);
};

const renderQRSigningModal = () => {
const { isSigningQRObject, QRState } = props;

Expand Down Expand Up @@ -789,6 +833,14 @@ const RootRPCMethodsUI = (props) => {
origin: request.origin,
});
break;
case ApprovalTypes.RESULT_SUCCESS:
case ApprovalTypes.RESULT_ERROR:
setApprovalResultRequest({ data: requestData, id: request.id });
showPendingApprovalModal({
type: request.type,
origin: request.origin,
});
break;
default:
break;
}
Expand Down Expand Up @@ -850,6 +902,7 @@ const RootRPCMethodsUI = (props) => {
{renderQRSigningModal()}
{renderAccountsApprovalModal()}
{renderApprovalFlowModal()}
{renderApprovalResultModal()}
</React.Fragment>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import Device from '../../../util/device';
import { useTheme } from '../../../util/theme';
import Text from '../../Base/Text';
import Spinner from '../AnimatedSpinner';
import Device from '../../../../util/device';
import { useTheme } from '../../../../util/theme';
import Text from '../../../Base/Text';
import Spinner from '../../AnimatedSpinner';

const createStyles = (colors) =>
StyleSheet.create({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { shallow } from 'enzyme';
import ApprovalFlowLoader from './';
import ApprovalFlowLoader from '.';

describe('ApprovalFlowLoader', () => {
it('should render correctly', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { StyleSheet } from 'react-native';
import { Theme } from '../../../../util/theme/models';
import Device from '../../../../util/device';

/**
*
* @param params Style sheet params.
* @param params.theme App theme from ThemeContext.
* @param params.vars Inputs that the style sheet depends on.
* @returns StyleSheet object.
*/
const styleSheet = (params: { theme: Theme }) => {
const { theme } = params;
const { colors } = theme;
return StyleSheet.create({
root: {
backgroundColor: colors.background.default,
paddingTop: 24,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
minHeight: 200,
paddingBottom: Device.isIphoneX() ? 20 : 0,
},
accountCardWrapper: {
paddingHorizontal: 24,
},
actionContainer: {
flex: 0,
paddingVertical: 16,
justifyContent: 'center',
},
description: {
textAlign: 'center',
paddingBottom: 16,
},
snapCell: {
marginVertical: 16,
},
snapPermissionContainer: {
maxHeight: 300,
borderWidth: 1,
borderRadius: 8,
borderColor: colors.border.muted,
},
iconContainer: {
justifyContent: 'center',
alignItems: 'center',
},
iconWrapper: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: colors.success.muted,
justifyContent: 'center',
alignItems: 'center',
},
});
};

export default styleSheet;
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import { render } from '@testing-library/react-native';
import ApprovalResult, { ApprovalResultType } from './ApprovalResult';

describe('ApprovalResult', () => {
const mockProps = {
requestData: {
message: 'Success message',
},
onConfirm: jest.fn(),
requestType: ApprovalResultType.Success,
};

it('renders approval result with success type', () => {
const wrapper = render(<ApprovalResult {...mockProps} />);

expect(wrapper).toMatchSnapshot();
});

it('renders approval result with error type', () => {
const errorMockProps = {
...mockProps,
requestData: {
error: 'Error message',
},
requestType: ApprovalResultType.Failure,
};

const wrapper = render(<ApprovalResult {...errorMockProps} />);

expect(wrapper).toMatchSnapshot();
});
});
114 changes: 114 additions & 0 deletions app/components/UI/Approval/ApprovalResult/ApprovalResult.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React from 'react';
import { View } from 'react-native';
import stylesheet from './ApprovalResult.styles';
import { strings } from '../../../../../locales/i18n';
import SheetHeader from '../../../../component-library/components/Sheet/SheetHeader';
import Text, {
TextVariant,
} from '../../../../component-library/components/Texts/Text';
import Icon, {
IconColor,
IconName,
IconSize,
} from '../../../../component-library/components/Icons/Icon';
import {
ButtonSize,
ButtonVariants,
} from '../../../../component-library/components/Buttons/Button';
import BottomSheetFooter, {
ButtonsAlignment,
} from '../../../../component-library/components/BottomSheets/BottomSheetFooter';
import { ButtonProps } from '../../../../component-library/components/Buttons/Button/Button.types';
import { useStyles } from '../../../hooks/useStyles';

export enum ApprovalResultType {
Success = 'success',
Failure = 'failure',
}

export interface ApprovalResultData {
message?: string;
error?: string;
header?: unknown;
}

export interface ApprovalResultProps {
requestData: ApprovalResultData;
onConfirm: () => void;
requestType: ApprovalResultType;
}
const isApprovalResultTypeSuccess = (type: string) =>
ApprovalResultType.Success === type;

const processMessage = (
requestData: ApprovalResultData,
requestType: ApprovalResultType,
) => {
if (isApprovalResultTypeSuccess(requestType)) {
return (
requestData?.message ??
strings('approval_result.resultPageSuccessDefaultMessage')
);
}
return (
requestData?.error ??
strings('approval_result.resultPageErrorDefaultMessage')
);
};

const ApprovalResult = ({
requestData,
onConfirm,
requestType,
}: ApprovalResultProps) => {
const { styles } = useStyles(stylesheet, {});

const okButtonProps: ButtonProps = {
variant: ButtonVariants.Primary,
label: strings('approval_result.ok'),
size: ButtonSize.Lg,
onPress: onConfirm,
};

return (
<View style={styles.root}>
<View style={styles.accountCardWrapper}>
<View style={styles.iconContainer}>
<View style={styles.iconWrapper}>
<Icon
name={
isApprovalResultTypeSuccess(requestType)
? IconName.Confirmation
: IconName.Warning
}
color={
isApprovalResultTypeSuccess(requestType)
? IconColor.Success
: IconColor.Error
}
size={IconSize.Lg}
/>
</View>
</View>
<SheetHeader
title={
isApprovalResultTypeSuccess(requestType)
? strings('approval_result.success')
: strings('approval_result.error')
}
/>
<Text style={styles.description} variant={TextVariant.BodyMD}>
{processMessage(requestData, requestType)}
</Text>
<View style={styles.actionContainer}>
<BottomSheetFooter
buttonsAlignment={ButtonsAlignment.Horizontal}
buttonPropsArray={[okButtonProps]}
/>
</View>
</View>
</View>
);
};

export default ApprovalResult;
Loading
Loading