BETA - Add beta banner to all screens (#16307)

* Add beta home banner to home screen

* Move the beta home notification to the app-header

* Updates to formatting

* Add beta home banner to home screen

* Move the beta home notification to the app-header

* Updates to formatting

* Update ui/components/app/app-header/index.scss

Co-authored-by: George Marshall <george.marshall@consensys.net>

* Update ui/components/app/app-header/index.scss

Co-authored-by: George Marshall <george.marshall@consensys.net>

* Update ui/components/app/app-header/index.scss

Co-authored-by: George Marshall <george.marshall@consensys.net>

* Move banner to top of page

* Move to own folder, update styles

* Swith to BOX component

* Address feedback

* Add tests

* Update name of component

* Fix tests

* Fix proptype errors

* Fixups

* Remove unrelated changes

* Remove unwanted string changes

* Update beta component name and text

* Update mock data

Co-authored-by: George Marshall <george.marshall@consensys.net>
feature/default_network_editable
David Walsh 2 years ago committed by Dan J Miller
parent 83f952228b
commit 880af262a4
  1. 4
      app/_locales/en/messages.json
  2. 11
      app/scripts/controllers/app-state.js
  3. 2
      app/scripts/metamask-controller.js
  4. 1
      test/data/mock-state.json
  5. 1
      ui/components/app/app-components.scss
  6. 62
      ui/components/app/app-header/app-header.component.js
  7. 18
      ui/components/app/app-header/app-header.container.js
  8. 36
      ui/components/app/beta-header/__snapshots__/beta-header.test.js.snap
  9. 24
      ui/components/app/beta-header/beta-header.stories.js
  10. 45
      ui/components/app/beta-header/beta-header.test.js
  11. 59
      ui/components/app/beta-header/index.js
  12. 16
      ui/components/app/beta-header/index.scss
  13. 1
      ui/helpers/constants/beta.js
  14. 4
      ui/selectors/selectors.js
  15. 4
      ui/store/actions.js

@ -437,6 +437,10 @@
"beta": { "beta": {
"message": "Beta" "message": "Beta"
}, },
"betaHeaderText": {
"message": "This is a BETA version. Please report bugs $1",
"description": "$1 represents the word 'here' in a hyperlink"
},
"betaMetamaskDescription": { "betaMetamaskDescription": {
"message": "Trusted by millions, MetaMask is a secure wallet making the world of web3 accessible to all." "message": "Trusted by millions, MetaMask is a secure wallet making the world of web3 accessible to all."
}, },

@ -4,6 +4,7 @@ import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import { MINUTE } from '../../../shared/constants/time'; import { MINUTE } from '../../../shared/constants/time';
import { AUTO_LOCK_TIMEOUT_ALARM } from '../../../shared/constants/alarms'; import { AUTO_LOCK_TIMEOUT_ALARM } from '../../../shared/constants/alarms';
import { isManifestV3 } from '../../../shared/modules/mv3.utils'; import { isManifestV3 } from '../../../shared/modules/mv3.utils';
import { isBeta } from '../../../ui/helpers/utils/build-types';
export default class AppStateController extends EventEmitter { export default class AppStateController extends EventEmitter {
/** /**
@ -36,6 +37,7 @@ export default class AppStateController extends EventEmitter {
enableEIP1559V2NoticeDismissed: false, enableEIP1559V2NoticeDismissed: false,
showTestnetMessageInDropdown: true, showTestnetMessageInDropdown: true,
showPortfolioTooltip: true, showPortfolioTooltip: true,
showBetaHeader: isBeta(),
trezorModel: null, trezorModel: null,
...initState, ...initState,
qrHardware: {}, qrHardware: {},
@ -291,6 +293,15 @@ export default class AppStateController extends EventEmitter {
this.store.updateState({ showPortfolioTooltip }); this.store.updateState({ showPortfolioTooltip });
} }
/**
* Sets whether the beta notification heading on the home page
*
* @param showBetaHeader
*/
setShowBetaHeader(showBetaHeader) {
this.store.updateState({ showBetaHeader });
}
/** /**
* Sets a property indicating the model of the user's Trezor hardware wallet * Sets a property indicating the model of the user's Trezor hardware wallet
* *

@ -1697,6 +1697,8 @@ export default class MetamaskController extends EventEmitter {
), ),
setShowPortfolioTooltip: setShowPortfolioTooltip:
appStateController.setShowPortfolioTooltip.bind(appStateController), appStateController.setShowPortfolioTooltip.bind(appStateController),
setShowBetaHeader:
appStateController.setShowBetaHeader.bind(appStateController),
setCollectiblesDetectionNoticeDismissed: setCollectiblesDetectionNoticeDismissed:
appStateController.setCollectiblesDetectionNoticeDismissed.bind( appStateController.setCollectiblesDetectionNoticeDismissed.bind(
appStateController, appStateController,

@ -19,6 +19,7 @@
}, },
"metamask": { "metamask": {
"gasEstimateType": "fee-market", "gasEstimateType": "fee-market",
"showBetaHeader": false,
"gasFeeEstimates": { "gasFeeEstimates": {
"low": { "low": {
"minWaitTimeEstimate": 180000, "minWaitTimeEstimate": 180000,

@ -9,6 +9,7 @@
@import 'alerts/alerts'; @import 'alerts/alerts';
@import 'app-header/index'; @import 'app-header/index';
@import 'asset-list-item/asset-list-item'; @import 'asset-list-item/asset-list-item';
@import 'beta-header/index';
@import 'cancel-speedup-popover/index'; @import 'cancel-speedup-popover/index';
@import 'confirm-page-container/index'; @import 'confirm-page-container/index';
@import 'confirm-page-container/enableEIP1559V2-notice'; @import 'confirm-page-container/enableEIP1559V2-notice';

@ -7,6 +7,10 @@ import { DEFAULT_ROUTE } from '../../../helpers/constants/routes';
import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics'; import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics';
import NetworkDisplay from '../network-display'; import NetworkDisplay from '../network-display';
///: BEGIN:ONLY_INCLUDE_IN(beta)
import BetaHeader from '../beta-header';
///: END:ONLY_INCLUDE_IN(beta)
export default class AppHeader extends PureComponent { export default class AppHeader extends PureComponent {
static propTypes = { static propTypes = {
history: PropTypes.object, history: PropTypes.object,
@ -23,6 +27,9 @@ export default class AppHeader extends PureComponent {
///: BEGIN:ONLY_INCLUDE_IN(flask) ///: BEGIN:ONLY_INCLUDE_IN(flask)
unreadNotificationsCount: PropTypes.number, unreadNotificationsCount: PropTypes.number,
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(beta)
showBetaHeader: PropTypes.bool,
///: END:ONLY_INCLUDE_IN
onClick: PropTypes.func, onClick: PropTypes.func,
}; };
@ -112,33 +119,44 @@ export default class AppHeader extends PureComponent {
disableNetworkIndicator, disableNetworkIndicator,
disabled, disabled,
onClick, onClick,
///: BEGIN:ONLY_INCLUDE_IN(beta)
showBetaHeader,
///: END:ONLY_INCLUDE_IN(beta)
} = this.props; } = this.props;
return ( return (
<div className="app-header"> <>
<div className="app-header__contents"> {
<MetaFoxLogo ///: BEGIN:ONLY_INCLUDE_IN(beta)
unsetIconHeight showBetaHeader ? <BetaHeader /> : null
onClick={async () => { ///: END:ONLY_INCLUDE_IN(beta)
if (onClick) { }
await onClick();
} <div className="app-header">
history.push(DEFAULT_ROUTE); <div className="app-header__contents">
}} <MetaFoxLogo
/> unsetIconHeight
<div className="app-header__account-menu-container"> onClick={async () => {
{!hideNetworkIndicator && ( if (onClick) {
<div className="app-header__network-component-wrapper"> await onClick();
<NetworkDisplay }
onClick={(event) => this.handleNetworkIndicatorClick(event)} history.push(DEFAULT_ROUTE);
disabled={disabled || disableNetworkIndicator} }}
/> />
</div> <div className="app-header__account-menu-container">
)} {!hideNetworkIndicator && (
{this.renderAccountMenu()} <div className="app-header__network-component-wrapper">
<NetworkDisplay
onClick={(event) => this.handleNetworkIndicatorClick(event)}
disabled={disabled || disableNetworkIndicator}
/>
</div>
)}
{this.renderAccountMenu()}
</div>
</div> </div>
</div> </div>
</div> </>
); );
} }
} }

@ -1,9 +1,14 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { compose } from 'redux'; import { compose } from 'redux';
///: BEGIN:ONLY_INCLUDE_IN(flask) import {
import { getUnreadNotificationsCount } from '../../../selectors'; ///: BEGIN:ONLY_INCLUDE_IN(flask)
///: END:ONLY_INCLUDE_IN getUnreadNotificationsCount,
///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(beta)
getShowBetaHeader,
///: END:ONLY_INCLUDE_IN
} from '../../../selectors';
import * as actions from '../../../store/actions'; import * as actions from '../../../store/actions';
import AppHeader from './app-header.component'; import AppHeader from './app-header.component';
@ -17,6 +22,10 @@ const mapStateToProps = (state) => {
const unreadNotificationsCount = getUnreadNotificationsCount(state); const unreadNotificationsCount = getUnreadNotificationsCount(state);
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(beta)
const showBetaHeader = getShowBetaHeader(state);
///: END:ONLY_INCLUDE_IN
return { return {
networkDropdownOpen, networkDropdownOpen,
selectedAddress, selectedAddress,
@ -25,6 +34,9 @@ const mapStateToProps = (state) => {
///: BEGIN:ONLY_INCLUDE_IN(flask) ///: BEGIN:ONLY_INCLUDE_IN(flask)
unreadNotificationsCount, unreadNotificationsCount,
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(beta)
showBetaHeader,
///: END:ONLY_INCLUDE_IN
}; };
}; };

@ -0,0 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Beta Header should match snapshot 1`] = `
<div>
<div
class="box beta-header box--padding-2 box--display-flex box--flex-direction-row box--width-full box--background-color-warning-default"
>
<h6
class="box box--flex-direction-row typography beta-header__message typography--h7 typography--weight-normal typography--style-normal typography--color-warning-inverse"
>
<span>
This is a BETA version. Please report bugs
<a
href="https://metamask.zendesk.com/hc/en-us"
rel="noreferrer noopener"
target="_blank"
>
here
</a>
</span>
</h6>
<button
aria-label="Close"
class="beta-header__button"
data-testid="beta-header-close"
>
<i
class="fa fa-times"
/>
</button>
</div>
</div>
`;

@ -0,0 +1,24 @@
import React from 'react';
import { Provider } from 'react-redux';
import testData from '../../../../.storybook/test-data';
import configureStore from '../../../store/store';
import BetaHeader from '.';
const store = configureStore({
...testData,
metamask: { ...testData.metamask, isUnlocked: true, showBetaHeader: true },
});
export default {
title: 'Components/App/BetaHeader',
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
id: __filename,
};
export const DefaultStory = () => (
<>
<BetaHeader />
</>
);
DefaultStory.storyName = 'Default';

@ -0,0 +1,45 @@
import React from 'react';
import { fireEvent } from '@testing-library/react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import mockState from '../../../../test/data/mock-state.json';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import BetaHeader from '.';
const mockHideBetaHeader = jest.fn();
jest.mock('../../../store/actions', () => {
return {
hideBetaHeader: () => {
mockHideBetaHeader();
},
};
});
describe('Beta Header', () => {
let store;
beforeEach(() => {
store = configureMockStore([thunk])(mockState);
});
afterEach(() => {
mockHideBetaHeader.mockClear();
});
it('should match snapshot', () => {
const { container } = renderWithProvider(<BetaHeader />, store);
expect(container).toMatchSnapshot();
});
describe('Beta Header', () => {
it('gets hidden when close button is clicked', () => {
const { queryByTestId } = renderWithProvider(<BetaHeader />, store);
const closeButton = queryByTestId('beta-header-close');
fireEvent.click(closeButton);
expect(mockHideBetaHeader).toHaveBeenCalledTimes(1);
});
});
});

@ -0,0 +1,59 @@
import React from 'react';
import { useI18nContext } from '../../../hooks/useI18nContext';
import Box from '../../ui/box/box';
import Typography from '../../ui/typography/typography';
import {
TYPOGRAPHY,
COLORS,
BLOCK_SIZES,
DISPLAY,
} from '../../../helpers/constants/design-system';
import { BETA_BUGS_URL } from '../../../helpers/constants/beta';
import { hideBetaHeader } from '../../../store/actions';
const BetaHeader = () => {
const t = useI18nContext();
return (
<Box
display={DISPLAY.FLEX}
width={BLOCK_SIZES.FULL}
backgroundColor={COLORS.WARNING_DEFAULT}
padding={2}
className="beta-header"
>
<Typography
variant={TYPOGRAPHY.H7}
marginTop={0}
marginBottom={0}
className="beta-header__message"
color={COLORS.WARNING_INVERSE}
>
{t('betaHeaderText', [
<a
href={BETA_BUGS_URL}
key="link"
target="_blank"
rel="noreferrer noopener"
>
{t('here')}
</a>,
])}
</Typography>
<button
className="beta-header__button"
data-testid="beta-header-close"
onClick={() => {
hideBetaHeader();
}}
aria-label={t('close')}
>
<i className="fa fa-times" />
</button>
</Box>
);
};
export default BetaHeader;

@ -0,0 +1,16 @@
.beta-header {
&__message {
text-align: center;
flex-grow: 1;
}
&__button {
background: transparent;
padding: 0 6px;
margin: 0;
i {
color: var(--color-warning-inverse);
}
}
}

@ -0,0 +1 @@
export const BETA_BUGS_URL = 'https://metamask.zendesk.com/hc/en-us';

@ -978,6 +978,10 @@ export function getShowPortfolioTooltip(state) {
return state.metamask.showPortfolioTooltip; return state.metamask.showPortfolioTooltip;
} }
export function getShowBetaHeader(state) {
return state.metamask.showBetaHeader;
}
/** /**
* To get the useTokenDetection flag which determines whether a static or dynamic token list is used * To get the useTokenDetection flag which determines whether a static or dynamic token list is used
* *

@ -3779,6 +3779,10 @@ export function hidePortfolioTooltip() {
return submitRequestToBackground('setShowPortfolioTooltip', [false]); return submitRequestToBackground('setShowPortfolioTooltip', [false]);
} }
export function hideBetaHeader() {
return submitRequestToBackground('setShowBetaHeader', [false]);
}
export function setCollectiblesDetectionNoticeDismissed() { export function setCollectiblesDetectionNoticeDismissed() {
return submitRequestToBackground('setCollectiblesDetectionNoticeDismissed', [ return submitRequestToBackground('setCollectiblesDetectionNoticeDismissed', [
true, true,

Loading…
Cancel
Save