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: Support standalone confirmation for re-redesigned confirmations #13550

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ const StakeInputView = ({ route }: StakeInputViewProps) => {
confirmationRedesignFlags?.staking_transactions;
const activeAccount = useSelector(selectSelectedInternalAccount);


const {
isEth,
currentCurrency,
Expand Down Expand Up @@ -82,6 +81,9 @@ const StakeInputView = ({ route }: StakeInputViewProps) => {
amountWei.toString(),
activeAccount?.address as string,
);
navigation.navigate('StakeScreens', {
screen: Routes.STANDALONE_CONFIRMATIONS.STAKE_DEPOSIT,
});
return;
}

Expand Down
6 changes: 6 additions & 0 deletions app/components/UI/Stake/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import StakeInputView from '../Views/StakeInputView/StakeInputView';
import Routes from '../../../../constants/navigation/Routes';
import { Confirm } from '../../../Views/confirmations/Confirm/Confirm';
import StakeConfirmationView from '../Views/StakeConfirmationView/StakeConfirmationView';
import UnstakeInputView from '../Views/UnstakeInputView/UnstakeInputView';
import UnstakeConfirmationView from '../Views/UnstakeConfirmationView/UnstakeConfirmationView';
Expand All @@ -11,6 +12,7 @@ import GasImpactModal from '../components/GasImpactModal';
import StakeEarningsHistoryView from '../Views/StakeEarningsHistoryView/StakeEarningsHistoryView';
import PoolStakingLearnMoreModal from '../components/PoolStakingLearnMoreModal';
import EarnTokenList from '../components/EarnTokenList';

const Stack = createStackNavigator();
const ModalStack = createStackNavigator();

Expand Down Expand Up @@ -43,6 +45,10 @@ const StakeScreenStack = () => (
name={Routes.STAKING.EARNINGS_HISTORY}
component={StakeEarningsHistoryView}
/>
<Stack.Screen
name={Routes.STANDALONE_CONFIRMATIONS.STAKE_DEPOSIT}
component={Confirm}
/>
</Stack.Navigator>
</StakeSDKProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ const styleSheet = (params: { theme: Theme }) => {
paddingBottom: Device.isIphoneX() ? 20 : 0,
maxHeight: '90%',
},
standaloneContainer: {
backgroundColor: theme.colors.background.alternative,
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 12,
height: '100%',
},
});
};

Expand Down
17 changes: 10 additions & 7 deletions app/components/Views/confirmations/Confirm/Confirm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import renderWithProvider from '../../../../util/test/renderWithProvider';
import {
personalSignatureConfirmationState,
securityAlertResponse,
typedSignV1ConfirmationState,
stakingDepositConfirmationState,
typedSignV1ConfirmationState,
} from '../../../../util/test/confirm-data-helpers';
// eslint-disable-next-line import/no-namespace
import * as ConfirmationRedesignEnabled from '../hooks/useConfirmationRedesignEnabled';
Expand Down Expand Up @@ -42,16 +42,12 @@ jest.mock('@react-navigation/native', () => ({
navigate: jest.fn(),
addListener: jest.fn(),
dispatch: jest.fn(),
setOptions: jest.fn(),
}),
}));

describe('Confirm', () => {
it('renders flat confirmation', async () => {
const { getByTestId } = renderWithProvider(<Confirm />, {
state: stakingDepositConfirmationState,
});
expect(getByTestId('flat-confirmation-container')).toBeDefined();
});
// TODO: Add tests for flat confirmation

it('renders modal confirmation', async () => {
const { getByTestId } = renderWithProvider(<Confirm />, {
Expand Down Expand Up @@ -89,6 +85,13 @@ describe('Confirm', () => {
expect(queryByText('This is a deceptive request')).toBeNull();
});

it('renders correct information for staking deposit', async () => {
const { getByText } = renderWithProvider(<Confirm />, {
state: stakingDepositConfirmationState,
});
expect(getByText('0.0001 ETH')).toBeDefined();
});

it('renders blockaid banner if confirmation has blockaid error response', async () => {
const { getByText } = renderWithProvider(<Confirm />, {
state: {
Expand Down
10 changes: 10 additions & 0 deletions app/components/Views/confirmations/Confirm/Confirm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import useApprovalRequest from '../hooks/useApprovalRequest';
import { useConfirmActions } from '../hooks/useConfirmActions';
import { useConfirmationRedesignEnabled } from '../hooks/useConfirmationRedesignEnabled';
import { useFlatConfirmation } from '../hooks/useFlatConfirmation';
import { useStandaloneConfirmation } from '../hooks/useStandaloneConfirmation';
import styleSheet from './Confirm.styles';

const ConfirmWrapped = () => (
Expand All @@ -34,6 +35,7 @@ export const Confirm = () => {
const { approvalRequest } = useApprovalRequest();
const { isFlatConfirmation } = useFlatConfirmation();
const { isRedesignedEnabled } = useConfirmationRedesignEnabled();
const { isStandaloneConfirmation } = useStandaloneConfirmation();
const { onReject } = useConfirmActions();

const { styles } = useStyles(styleSheet, {});
Expand All @@ -42,6 +44,14 @@ export const Confirm = () => {
return null;
}

if (isStandaloneConfirmation) {
return (
<View style={styles.standaloneContainer}>
<ConfirmWrapped />
</View>
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not StandaloneConfirmation be same as FlatConfirmation ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because FlatConfirmation is a confirmation with fullscreen that's all.

One example of FlatConfirmation will be SendFlow, where it will have it's own Stack.Navigator and Stack.Screens.

Copy link
Member Author

@OGPoyraz OGPoyraz Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In summary;

ModalConfirmations => for using Signatures, all other Transaction types etc
FlatConfirmation => A confirmation where it needs to be a full flow, has it's own navigator inside
StandaloneConfirmation => Where we can put a confirmation anywhere in the app (In any Stack)

I think these covering all the use cases we have it in mobile today

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I think we need only 2 types of styling, which essentially is point of classification here - modal and full screen.


if (isFlatConfirmation) {
return (
<View style={styles.flatContainer} testID="flat-confirmation-container">
Expand Down
15 changes: 3 additions & 12 deletions app/components/Views/confirmations/Confirm/ConfirmRoot.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import React from 'react';

import renderWithProvider from '../../../../util/test/renderWithProvider';
import {
personalSignatureConfirmationState,
stakingDepositConfirmationState,
} from '../../../../util/test/confirm-data-helpers';
import { personalSignatureConfirmationState } from '../../../../util/test/confirm-data-helpers';
import Routes from '../../../../constants/navigation/Routes';

import { ConfirmRoot } from './ConfirmRoot';
Expand All @@ -24,15 +21,9 @@ describe('Confirm', () => {
jest.clearAllMocks();
});

it('renders flat confirmation', async () => {
renderWithProvider(<ConfirmRoot />, {
state: stakingDepositConfirmationState,
});
expect(mockNavigate).toHaveBeenCalledTimes(1);
expect(mockNavigate).toHaveBeenCalledWith(Routes.CONFIRM_FLAT_PAGE);
});
// TODO: Add unit test for once we have any existing flat confirmation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not remove the test case till code is there.


it('renders modal confirmation', async () => {
it('navigates to modal confirmation', async () => {
renderWithProvider(<ConfirmRoot />, {
state: personalSignatureConfirmationState,
});
Expand Down
12 changes: 11 additions & 1 deletion app/components/Views/confirmations/Confirm/ConfirmRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,29 @@ import { useNavigation } from '@react-navigation/native';
import Routes from '../../../../constants/navigation/Routes';
import { useFlatConfirmation } from '../hooks/useFlatConfirmation';
import { useConfirmationRedesignEnabled } from '../hooks/useConfirmationRedesignEnabled';
import { useStandaloneConfirmation } from '../hooks/useStandaloneConfirmation';

export const ConfirmRoot = () => {
const { isRedesignedEnabled } = useConfirmationRedesignEnabled();
const { isFlatConfirmation } = useFlatConfirmation();
const { isStandaloneConfirmation } = useStandaloneConfirmation();
const navigation = useNavigation();

useEffect(() => {
if (isRedesignedEnabled) {
if (isStandaloneConfirmation) {
return;
}
navigation.navigate(
isFlatConfirmation ? Routes.CONFIRM_FLAT_PAGE : Routes.CONFIRM_MODAL,
);
}
}, [isFlatConfirmation, isRedesignedEnabled, navigation]);
}, [
isFlatConfirmation,
isRedesignedEnabled,
isStandaloneConfirmation,
navigation,
]);

return null;
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import React from 'react';
import { Text } from 'react-native';

import renderWithProvider from '../../../../../../util/test/renderWithProvider';
import {
personalSignatureConfirmationState,
stakingDepositConfirmationState,
} from '../../../../../../util/test/confirm-data-helpers';
import { personalSignatureConfirmationState } from '../../../../../../util/test/confirm-data-helpers';
// eslint-disable-next-line import/no-namespace
import * as QRHardwareHook from '../../../context/QRHardwareContext/QRHardwareContext';
import Info from './Info';
import { Text } from 'react-native';

jest.mock('@react-navigation/native', () => ({
...jest.requireActual('@react-navigation/native'),
Expand Down Expand Up @@ -53,12 +50,4 @@ describe('Info', () => {
});
expect(getByText('QR Scanning Component')).toBeTruthy();
});
describe('Staking Deposit', () => {
it('should render correctly', async () => {
const { getByText } = renderWithProvider(<Info />, {
state: stakingDepositConfirmationState,
});
expect(getByText('Stake')).toBeDefined();
});
});
});
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 { getStakingDepositNavbar } from './Navbar';

describe('getStakingDepositNavbar', () => {
it('renders the header title correctly', () => {
const title = 'Test Title';
const { getByText } = render(
<>
{getStakingDepositNavbar({ title, onReject: jest.fn() }).headerTitle()}
</>,
);

expect(getByText(title)).toBeTruthy();
});

it('calls onReject when the back button is pressed', () => {
const onRejectMock = jest.fn();
const { getByTestId } = render(
<>
{getStakingDepositNavbar({
title: 'Test Title',
onReject: onRejectMock,
}).headerLeft()}
</>,
);

const backButton = getByTestId('staking-deposit-navbar-back-button');
backButton.props.onPress();

expect(onRejectMock).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import {
default as MorphText,
TextVariant,
} from '../../../../../../../component-library/components/Texts/Text';
import ButtonIcon, {
ButtonIconSizes,
} from '../../../../../../../component-library/components/Buttons/ButtonIcon';
import { IconName } from '../../../../../../../component-library/components/Icons/Icon';

export function getStakingDepositNavbar({
title,
onReject,
}: {
title: string;
onReject: () => void;
}) {
const innerStyles = StyleSheet.create({
headerLeft: {
marginHorizontal: 16,
},
headerTitle: {
alignItems: 'center',
},
});

function handleBackPress() {
if (onReject) {
onReject();
}
}

return {
headerTitle: () => (
<View style={innerStyles.headerTitle}>
<MorphText variant={TextVariant.HeadingMD}>{title}</MorphText>
</View>
),
headerLeft: () => (
<ButtonIcon
size={ButtonIconSizes.Lg}
iconName={IconName.ArrowLeft}
onPress={handleBackPress}
style={innerStyles.headerLeft}
testID="staking-deposit-navbar-back-button"
/>
),
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';

import renderWithProvider from '../../../../../../../util/test/renderWithProvider';
import { stakingDepositConfirmationState } from '../../../../../../../util/test/confirm-data-helpers';
import StakingDeposit from './StakingDeposit';

jest.mock('@react-navigation/native', () => {
const actualNav = jest.requireActual('@react-navigation/native');
return {
...actualNav,
useNavigation: () => ({
navigate: jest.fn(),
setOptions: jest.fn(),
}),
};
});

describe('StakingDeposit', () => {
it('should render correctly', () => {
const { getByText } = renderWithProvider(<StakingDeposit />, {
state: stakingDepositConfirmationState,
});
expect(getByText('0.0001 ETH')).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import React from 'react';
import React, { useEffect } from 'react';
import { useNavigation } from '@react-navigation/native';

import { strings } from '../../../../../../../../locales/i18n';
import FlatNavHeader from '../../FlatNavHeader';
import { useConfirmActions } from '../../../../hooks/useConfirmActions';
import StakingDetails from '../../StakingDetails';
import TokenHero from '../../TokenHero';
import { getStakingDepositNavbar } from './Navbar';

const StakingDeposit = () => (
<>
<FlatNavHeader title={strings('stake.stake')} />
<TokenHero />
<StakingDetails />
</>
);
const StakingDeposit = () => {
const navigation = useNavigation();
const { onReject } = useConfirmActions();
const title = strings('stake.stake');

useEffect(() => {
navigation.setOptions(getStakingDepositNavbar({ title, onReject }));
}, [navigation, onReject, title]);

return (
<>
<TokenHero />
<StakingDetails />
</>
);
};
export default StakingDeposit;
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default } from './StakingDetails';
export { default } from './StakingDetails';
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Text from '../../../../../../component-library/components/Texts/Text';
import useApprovalRequest from '../../../hooks/useApprovalRequest';
import { useSignatureRequest } from '../../../hooks/useSignatureRequest';
import { isSIWESignatureRequest , isRecognizedPermit, parseTypedDataMessageFromSignatureRequest } from '../../../utils/signature';
import { useStandaloneConfirmation } from '../../../hooks/useStandaloneConfirmation';
import styleSheet from './Title.styles';

const getTitleAndSubTitle = (approvalRequest?: ApprovalRequest<{ data: string }>, signatureRequest?: SignatureRequest) => {
Expand Down Expand Up @@ -60,9 +61,14 @@ const Title = () => {
const { approvalRequest } = useApprovalRequest();
const signatureRequest = useSignatureRequest();
const { styles } = useStyles(styleSheet, {});
const { isStandaloneConfirmation } = useStandaloneConfirmation();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to use useFlatConfirmation here.


const { title, subTitle } = getTitleAndSubTitle(approvalRequest, signatureRequest);

if (isStandaloneConfirmation) {
return null;
}

return (
<View style={styles.titleContainer}>
<Text style={styles.title}>{title}</Text>
Expand Down
Loading
Loading