Skip to content

Commit

Permalink
feat: STAKE-824: [FE] build staking input confirmation screen (#11605)
Browse files Browse the repository at this point in the history
<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

## **Description**

<!--
Write a short description of the changes included in this pull request,
also include relevant motivation and context. Have in mind the following
questions:
1. What is the reason for the change?
2. What is the improvement/solution?
-->

This PR adds the staking confirmation screen with some mock data being
used temporarily.

### Change List
- Add staking confirmation screen.
- Connect existing `<StakeInputView />` to staking confirmation screen
when user enters valid amount to stake.


## **Related issues**
Ticket: [FE] Build staking input confirmation screen -
([link](https://consensyssoftware.atlassian.net/browse/STAKE-824))
Figma Designs -
[link](https://www.figma.com/design/1c0Y9jDJe6p0j82jydJDcs/Mobile-Staking?node-id=2979-22435&m=dev)

## **Manual testing steps**

1. Add `export MM_POOLED_STAKING_UI_ENABLED=true` to your `.js.env`
file.
2. Click on Ethereum In the token list page
3. Scroll down a bit and click "Stake more". This should open the stake
input view (not related to this PR)
4. Enter a valid amount to stake and click "Confirm"
5. You should be redirected to a staking confirmation screen. The screen
should display the amount to stake in `wETH` and Fiat.

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

Nothing would happen after clicking "Confirm" on the stake input view.
This screen is new.

### **After**

<!-- [screenshots/recordings] -->


https://github.com/user-attachments/assets/84ea4c52-50c5-48c3-8077-2c2e8a92bf21


## **Pre-merge author checklist**

- [x] I’ve followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
  • Loading branch information
Matt561 authored Oct 16, 2024
1 parent 9db29fd commit f996de1
Show file tree
Hide file tree
Showing 63 changed files with 7,149 additions and 1,074 deletions.
63 changes: 45 additions & 18 deletions app/components/UI/Navbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1824,34 +1824,61 @@ export const getSettingsNavigationOptions = (title, themeColors) => {
};
};

export function getStakingNavbar(title, navigation, themeColors) {
/**
*
* @param {String} title - Navbar Title.
* @param {NavigationProp<ParamListBase>} navigation Navigation object returned from useNavigation hook.
* @param {ThemeColors} themeColors theme.colors returned from useStyles hook.
* @param {{ backgroundColor?: string, hasCancelButton?: boolean, hasBackButton?: boolean }} [options] - Optional options for navbar.
* @returns Staking Navbar Component.
*/
export function getStakingNavbar(title, navigation, themeColors, options) {
const { hasBackButton = true, hasCancelButton = true } = options ?? {};

const innerStyles = StyleSheet.create({
headerStyle: {
backgroundColor:
options?.backgroundColor ?? themeColors.background.default,
shadowOffset: null,
},
headerLeft: {
marginHorizontal: 16,
},
headerButtonText: {
color: themeColors.primary.default,
fontSize: 14,
...fontStyles.normal,
},
headerStyle: {
backgroundColor: themeColors.background.default,
shadowColor: importedColors.transparent,
elevation: 0,
},
});

function navigationPop() {
navigation.goBack();
}

return {
headerTitle: () => (
<NavbarTitle title={title} disableNetwork translate={false} />
),
headerLeft: () => <View />,
headerRight: () => (
<TouchableOpacity
onPress={() => navigation.dangerouslyGetParent()?.pop()}
style={styles.closeButton}
>
<Text style={innerStyles.headerButtonText}>
{strings('navigation.cancel')}
</Text>
</TouchableOpacity>
<MorphText variant={TextVariant.HeadingMD}>{title}</MorphText>
),
headerStyle: innerStyles.headerStyle,
headerLeft: () =>
hasBackButton ? (
<ButtonIcon
size={ButtonIconSizes.Lg}
iconName={IconName.ArrowLeft}
onPress={navigationPop}
style={innerStyles.headerLeft}
/>
) : null,
headerRight: () =>
hasCancelButton ? (
<TouchableOpacity
onPress={() => navigation.dangerouslyGetParent()?.pop()}
style={styles.closeButton}
>
<Text style={innerStyles.headerButtonText}>
{strings('navigation.cancel')}
</Text>
</TouchableOpacity>
) : null,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Theme } from '../../../../../util/theme/models';
import { StyleSheet } from 'react-native';

const stylesSheet = (params: { theme: Theme }) => {
const { theme } = params;
const { colors } = theme;

return StyleSheet.create({
mainContainer: {
flex: 1,
paddingTop: 8,
paddingHorizontal: 16,
backgroundColor: colors.background.alternative,
justifyContent: 'space-between',
},
cardsContainer: {
paddingTop: 16,
gap: 8,
},
});
};

export default stylesSheet;
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import renderWithProvider from '../../../../../util/test/renderWithProvider';
import StakeConfirmationView from './StakeConfirmationView';
import { Image } from 'react-native';
import { createMockAccountsControllerState } from '../../../../../util/test/accountsControllerTestUtils';
import { backgroundState } from '../../../../../util/test/initial-root-state';
import configureMockStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import { StakeConfirmationViewProps } from './StakeConfirmationView.types';

jest.mock('../../../../hooks/useIpfsGateway', () => jest.fn());

Image.getSize = jest.fn((_uri, success) => {
success(100, 100); // Mock successful response for ETH native Icon Image
});

const MOCK_ADDRESS_1 = '0x0';
const MOCK_ADDRESS_2 = '0x1';

const MOCK_ACCOUNTS_CONTROLLER_STATE = createMockAccountsControllerState([
MOCK_ADDRESS_1,
MOCK_ADDRESS_2,
]);

const mockStore = configureMockStore();

const mockInitialState = {
settings: {},
engine: {
backgroundState: {
...backgroundState,
AccountsController: MOCK_ACCOUNTS_CONTROLLER_STATE,
},
},
};
const store = mockStore(mockInitialState);

jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useSelector: jest
.fn()
.mockImplementation((callback) => callback(mockInitialState)),
}));

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

describe('StakeConfirmationView', () => {
it('render matches snapshot', () => {
const props: StakeConfirmationViewProps = {
route: {
key: '1',
params: { amountWei: '3210000000000000', amountFiat: '7.46' },
name: 'params',
},
};

const { toJSON } = renderWithProvider(
<Provider store={store}>
<StakeConfirmationView {...props} />
</Provider>,
);

expect(toJSON()).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { useEffect } from 'react';
import { View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useStyles } from '../../../../hooks/useStyles';
import { getStakingNavbar } from '../../../Navbar';
import styleSheet from './StakeConfirmationView.styles';
import TokenValueStack from '../../components/StakingConfirmation/TokenValueStack/TokenValueStack';
import AccountHeaderCard from '../../components/StakingConfirmation/AccountHeaderCard/AccountHeaderCard';
import RewardsCard from '../../components/StakingConfirmation/RewardsCard/RewardsCard';
import ConfirmationFooter from '../../components/StakingConfirmation/ConfirmationFooter/ConfirmationFooter';
import { StakeConfirmationViewProps } from './StakeConfirmationView.types';
import { MOCK_GET_VAULT_RESPONSE } from '../../components/StakingBalance/mockData';
import { strings } from '../../../../../../locales/i18n';

const MOCK_REWARD_DATA = {
REWARDS: {
ETH: '0.13 ETH',
FIAT: '$334.93',
},
};

const MOCK_STAKING_CONTRACT_NAME = 'MM Pooled Staking';

const StakeConfirmationView = ({ route }: StakeConfirmationViewProps) => {
const navigation = useNavigation();

const { styles, theme } = useStyles(styleSheet, {});

useEffect(() => {
navigation.setOptions(
getStakingNavbar(strings('stake.stake'), navigation, theme.colors, {
backgroundColor: theme.colors.background.alternative,
hasCancelButton: false,
}),
);
}, [navigation, theme.colors]);

return (
<View style={styles.mainContainer}>
<View>
<TokenValueStack
amountWei={route.params.amountWei}
amountFiat={`$${route.params.amountFiat}`}
tokenSymbol="ETH"
/>
<View style={styles.cardsContainer}>
<AccountHeaderCard contractName={MOCK_STAKING_CONTRACT_NAME} />
<RewardsCard
rewardRate={MOCK_GET_VAULT_RESPONSE.apy}
rewardsEth={MOCK_REWARD_DATA.REWARDS.ETH}
rewardsFiat={MOCK_REWARD_DATA.REWARDS.FIAT}
/>
</View>
</View>
<ConfirmationFooter />
</View>
);
};

export default StakeConfirmationView;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { RouteProp } from '@react-navigation/native';

interface StakeConfirmationViewRouteParams {
amountWei: string;
amountFiat: string;
}

export interface StakeConfirmationViewProps {
route: RouteProp<{ params: StakeConfirmationViewRouteParams }, 'params'>;
}
Loading

0 comments on commit f996de1

Please sign in to comment.