A Metamask fork with Infura removed and default networks editable
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ciphermask/ui/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.compone...

292 lines
7.8 KiB

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Button from '../../../../components/ui/button';
Metametrics (#6171) * Add metametrics provider and util. * Add backend api and state for participating in metametrics. * Add frontend action for participating in metametrics. * Add metametrics opt-in screen. * Add metametrics events to first time flow. * Add metametrics events for route changes * Add metametrics events for send and confirm screens * Add metametrics events to dropdowns, transactions, log in and out, settings, sig requests and main screen * Ensures each log in is measured as a new visit by metametrics. * Ensure metametrics is called with an empty string for dimensions params if specified * Adds opt in metametrics modal after unlock for existing users * Adds settings page toggle for opting in and out of MetaMetrics * Switch metametrics dimensions to page level scope * Lint, test and translation fixes for metametrics. * Update design for metametrics opt-in screen * Complete responsive styling of metametrics-opt-in modal * Use new chart image on metrics opt in screens * Incorporate the metametrics opt-in screen into the new onboarding flow * Update e2e tests to accomodate metametrics changes * Mock out metametrics network requests in integration tests * Fix tx-list integration test to support metametrics provider. * Send number of tokens and accounts data with every metametrics event. * Update metametrics event descriptor schema and add new events. * Fix import tos bug and send gas button bug due to metametrics changes. * Various small fixes on the metametrics branch. * Add origin custom variable type to metametrics.util * Fix names of onboarding complete actions (metametrics). * Fix names of Metrics Options actions (metametrics). * Clean up code related to metametrics. * Fix bad merge conflict resolution and improve promise handling in sendMetaMetrics event and confrim tx base * Don't send a second metrics event if user has gone back during first time flow. * Collect metametrics on going back from onboarding create/import. * Add missing custom variable constants for metametrics * Fix metametrics provider * Make height of opt-in modal responsive. * Adjust text content for opt-in modal. * Update metametrics event names and clean up code in opt-in-modal * Put phishing warning step next to last in onboarding flow * Link terms of service on create and import screens of first time flow * Add subtext to options on the onboarding select action screen. * Fix styling of bullet points on end of onboarding screen. * Combine phishing warning and congratulations screens. * Fix placement of users if unlocking after an incomplete onboarding import flow. * Fix capitalization in opt-in screen * Fix last onboarding screen translations * Add link to 'Learn More' on the last screen of onboarding * Code clean up: metametrics branch * Update e2e tests for phishing warning step removal * e2e tests passing on metametrics branch * Different tracking urls for metametrics on development and prod
6 years ago
import {
INITIALIZE_END_OF_FLOW_ROUTE,
INITIALIZE_SEED_PHRASE_ROUTE,
} from '../../../../helpers/constants/routes';
import {
EVENT,
EVENT_NAMES,
} from '../../../../../shared/constants/metametrics';
import { exportAsFile } from '../../../../helpers/utils/export-utils';
import DraggableSeed from './draggable-seed.component';
const EMPTY_SEEDS = Array(12).fill(null);
export default class ConfirmSeedPhrase extends PureComponent {
static contextTypes = {
trackEvent: PropTypes.func,
t: PropTypes.func,
};
static defaultProps = {
seedPhrase: '',
};
static propTypes = {
history: PropTypes.object,
seedPhrase: PropTypes.string,
setSeedPhraseBackedUp: PropTypes.func,
};
state = {
selectedSeedIndices: [],
sortedSeedWords: [],
pendingSeedIndices: [],
draggingSeedIndex: -1,
hoveringIndex: -1,
};
componentDidMount() {
const { seedPhrase = '' } = this.props;
const sortedSeedWords = (seedPhrase.split(' ') || []).sort();
this.setState({ sortedSeedWords });
}
setDraggingSeedIndex = (draggingSeedIndex) =>
this.setState({ draggingSeedIndex });
setHoveringIndex = (hoveringIndex) => this.setState({ hoveringIndex });
onDrop = (targetIndex) => {
const { selectedSeedIndices, draggingSeedIndex } = this.state;
const indices = insert(
selectedSeedIndices,
draggingSeedIndex,
targetIndex,
true,
);
this.setState({
selectedSeedIndices: indices,
pendingSeedIndices: indices,
draggingSeedIndex: -1,
hoveringIndex: -1,
});
};
handleExport = () => {
exportAsFile('', this.props.seedPhrase, 'text/plain');
};
handleSubmit = async () => {
const { history, setSeedPhraseBackedUp } = this.props;
if (!this.isValid()) {
return;
}
try {
setSeedPhraseBackedUp(true).then(async () => {
this.context.trackEvent({
category: EVENT.CATEGORIES.ONBOARDING,
event: EVENT_NAMES.WALLET_CREATED,
properties: {
account_type: EVENT.ACCOUNT_TYPES.DEFAULT,
is_backup_skipped: false,
},
});
history.replace(INITIALIZE_END_OF_FLOW_ROUTE);
});
} catch (error) {
console.error(error.message);
this.context.trackEvent({
category: EVENT.CATEGORIES.ONBOARDING,
event: EVENT_NAMES.WALLET_SETUP_FAILED,
properties: {
account_type: EVENT.ACCOUNT_TYPES.DEFAULT,
is_backup_skipped: false,
reason: 'Seed Phrase Creation Error',
error: error.message,
},
});
}
};
handleSelectSeedWord = (index) => {
this.setState({
selectedSeedIndices: [...this.state.selectedSeedIndices, index],
pendingSeedIndices: [...this.state.pendingSeedIndices, index],
});
};
handleDeselectSeedWord = (index) => {
this.setState({
selectedSeedIndices: this.state.selectedSeedIndices.filter(
(i) => index !== i,
),
pendingSeedIndices: this.state.pendingSeedIndices.filter(
(i) => index !== i,
),
});
};
isValid() {
const { seedPhrase } = this.props;
const { selectedSeedIndices, sortedSeedWords } = this.state;
const selectedSeedWords = selectedSeedIndices.map(
(i) => sortedSeedWords[i],
);
return seedPhrase === selectedSeedWords.join(' ');
}
render() {
const { t } = this.context;
const { history } = this.props;
const { selectedSeedIndices, sortedSeedWords, draggingSeedIndex } =
this.state;
return (
<div className="confirm-seed-phrase" data-testid="confirm-seed-phrase">
<div className="confirm-seed-phrase__back-button">
<a
onClick={(e) => {
e.preventDefault();
history.push(INITIALIZE_SEED_PHRASE_ROUTE);
}}
href="#"
>
{`< ${t('back')}`}
</a>
</div>
<div className="first-time-flow__header">
{t('confirmSecretBackupPhrase')}
</div>
<div className="first-time-flow__text-block">
{t('selectEachPhrase')}
</div>
<div
className={classnames('confirm-seed-phrase__selected-seed-words', {
'confirm-seed-phrase__selected-seed-words--dragging':
draggingSeedIndex > -1,
})}
>
{this.renderPendingSeeds()}
{this.renderSelectedSeeds()}
</div>
<div
className="confirm-seed-phrase__sorted-seed-words"
data-testid="seed-phrase-sorted"
>
{sortedSeedWords.map((word, index) => {
const isSelected = selectedSeedIndices.includes(index);
return (
<DraggableSeed
key={index}
seedIndex={index}
index={index}
setHoveringIndex={this.setHoveringIndex}
onDrop={this.onDrop}
className="confirm-seed-phrase__seed-word--sorted"
selected={isSelected}
onClick={() => {
if (isSelected) {
this.handleDeselectSeedWord(index);
} else {
this.handleSelectSeedWord(index);
}
}}
word={word}
/>
);
})}
</div>
<Button
type="primary"
className="first-time-flow__button"
onClick={this.handleSubmit}
disabled={!this.isValid()}
>
{t('confirm')}
</Button>
</div>
);
}
renderSelectedSeeds() {
const { sortedSeedWords, selectedSeedIndices, draggingSeedIndex } =
this.state;
return EMPTY_SEEDS.map((_, index) => {
const seedIndex = selectedSeedIndices[index];
const word = sortedSeedWords[seedIndex];
return (
<DraggableSeed
key={`selected-${seedIndex}-${index}`}
className="confirm-seed-phrase__selected-seed-words__selected-seed"
index={index}
seedIndex={seedIndex}
word={word}
draggingSeedIndex={draggingSeedIndex}
setDraggingSeedIndex={this.setDraggingSeedIndex}
setHoveringIndex={this.setHoveringIndex}
onDrop={this.onDrop}
draggable
/>
);
});
}
renderPendingSeeds() {
const {
pendingSeedIndices,
sortedSeedWords,
draggingSeedIndex,
hoveringIndex,
} = this.state;
const indices = insert(
pendingSeedIndices,
draggingSeedIndex,
hoveringIndex,
);
return EMPTY_SEEDS.map((_, index) => {
const seedIndex = indices[index];
const word = sortedSeedWords[seedIndex];
return (
<DraggableSeed
key={`pending-${seedIndex}-${index}`}
index={index}
className={classnames(
'confirm-seed-phrase__selected-seed-words__pending-seed',
{
'confirm-seed-phrase__seed-word--hidden':
draggingSeedIndex === seedIndex && index !== hoveringIndex,
},
)}
seedIndex={seedIndex}
word={word}
draggingSeedIndex={draggingSeedIndex}
setDraggingSeedIndex={this.setDraggingSeedIndex}
setHoveringIndex={this.setHoveringIndex}
onDrop={this.onDrop}
droppable={Boolean(word)}
/>
);
});
}
}
function insert(list, value, target, removeOld) {
let nextList = [...list];
if (typeof list[target] === 'number') {
nextList = [...list.slice(0, target), value, ...list.slice(target)];
}
if (removeOld) {
nextList = nextList.filter((seed, i) => {
return seed !== value || i === target;
});
}
return nextList;
}