parent
f1403f4849
commit
1739ed9710
@ -0,0 +1,399 @@ |
||||
import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction'; |
||||
import * as actionConstants from '../../store/actionConstants'; |
||||
import reduceMetamask, { |
||||
getBlockGasLimit, |
||||
getConversionRate, |
||||
getNativeCurrency, |
||||
getSendHexDataFeatureFlagState, |
||||
getSendToAccounts, |
||||
getUnapprovedTxs, |
||||
} from './metamask'; |
||||
|
||||
describe('MetaMask Reducers', () => { |
||||
const mockState = { |
||||
metamask: reduceMetamask( |
||||
{ |
||||
isInitialized: true, |
||||
isUnlocked: true, |
||||
featureFlags: { sendHexData: true }, |
||||
identities: { |
||||
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': { |
||||
address: '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825', |
||||
name: 'Send Account 1', |
||||
}, |
||||
'0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb': { |
||||
address: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', |
||||
name: 'Send Account 2', |
||||
}, |
||||
'0x2f8d4a878cfa04a6e60d46362f5644deab66572d': { |
||||
address: '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', |
||||
name: 'Send Account 3', |
||||
}, |
||||
'0xd85a4b6a394794842887b8284293d69163007bbb': { |
||||
address: '0xd85a4b6a394794842887b8284293d69163007bbb', |
||||
name: 'Send Account 4', |
||||
}, |
||||
}, |
||||
cachedBalances: {}, |
||||
currentBlockGasLimit: '0x4c1878', |
||||
conversionRate: 1200.88200327, |
||||
nativeCurrency: 'ETH', |
||||
network: '3', |
||||
provider: { |
||||
type: 'testnet', |
||||
chainId: '0x3', |
||||
}, |
||||
accounts: { |
||||
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': { |
||||
code: '0x', |
||||
balance: '0x47c9d71831c76efe', |
||||
nonce: '0x1b', |
||||
address: '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825', |
||||
}, |
||||
'0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb': { |
||||
code: '0x', |
||||
balance: '0x37452b1315889f80', |
||||
nonce: '0xa', |
||||
address: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', |
||||
}, |
||||
'0x2f8d4a878cfa04a6e60d46362f5644deab66572d': { |
||||
code: '0x', |
||||
balance: '0x30c9d71831c76efe', |
||||
nonce: '0x1c', |
||||
address: '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', |
||||
}, |
||||
'0xd85a4b6a394794842887b8284293d69163007bbb': { |
||||
code: '0x', |
||||
balance: '0x0', |
||||
nonce: '0x0', |
||||
address: '0xd85a4b6a394794842887b8284293d69163007bbb', |
||||
}, |
||||
}, |
||||
addressBook: { |
||||
'0x3': { |
||||
'0x06195827297c7a80a443b6894d3bdb8824b43896': { |
||||
address: '0x06195827297c7a80a443b6894d3bdb8824b43896', |
||||
name: 'Address Book Account 1', |
||||
chainId: '0x3', |
||||
}, |
||||
}, |
||||
}, |
||||
unapprovedTxs: { |
||||
4768706228115573: { |
||||
id: 4768706228115573, |
||||
time: 1487363153561, |
||||
status: TRANSACTION_STATUSES.UNAPPROVED, |
||||
gasMultiplier: 1, |
||||
metamaskNetworkId: '3', |
||||
txParams: { |
||||
from: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', |
||||
to: '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761', |
||||
value: '0xde0b6b3a7640000', |
||||
metamaskId: 4768706228115573, |
||||
metamaskNetworkId: '3', |
||||
gas: '0x5209', |
||||
}, |
||||
txFee: '17e0186e60800', |
||||
txValue: 'de0b6b3a7640000', |
||||
maxCost: 'de234b52e4a0800', |
||||
gasPrice: '4a817c800', |
||||
}, |
||||
}, |
||||
}, |
||||
{}, |
||||
), |
||||
}; |
||||
it('init state', () => { |
||||
const initState = reduceMetamask(undefined, {}); |
||||
|
||||
expect.anything(initState); |
||||
}); |
||||
|
||||
it('locks MetaMask', () => { |
||||
const unlockMetaMaskState = { |
||||
isUnlocked: true, |
||||
selectedAddress: 'test address', |
||||
}; |
||||
const lockMetaMask = reduceMetamask(unlockMetaMaskState, { |
||||
type: actionConstants.LOCK_METAMASK, |
||||
}); |
||||
|
||||
expect(lockMetaMask.isUnlocked).toStrictEqual(false); |
||||
}); |
||||
|
||||
it('sets rpc target', () => { |
||||
const state = reduceMetamask( |
||||
{}, |
||||
{ |
||||
type: actionConstants.SET_RPC_TARGET, |
||||
value: 'https://custom.rpc', |
||||
}, |
||||
); |
||||
|
||||
expect(state.provider.rpcUrl).toStrictEqual('https://custom.rpc'); |
||||
}); |
||||
|
||||
it('sets provider type', () => { |
||||
const state = reduceMetamask( |
||||
{}, |
||||
{ |
||||
type: actionConstants.SET_PROVIDER_TYPE, |
||||
value: 'provider type', |
||||
}, |
||||
); |
||||
|
||||
expect(state.provider.type).toStrictEqual('provider type'); |
||||
}); |
||||
|
||||
it('shows account detail', () => { |
||||
const state = reduceMetamask( |
||||
{}, |
||||
{ |
||||
type: actionConstants.SHOW_ACCOUNT_DETAIL, |
||||
}, |
||||
); |
||||
|
||||
expect(state.isUnlocked).toStrictEqual(true); |
||||
expect(state.isInitialized).toStrictEqual(true); |
||||
}); |
||||
|
||||
it('sets account label', () => { |
||||
const state = reduceMetamask( |
||||
{}, |
||||
{ |
||||
type: actionConstants.SET_ACCOUNT_LABEL, |
||||
value: { |
||||
account: 'test account', |
||||
label: 'test label', |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
expect(state.identities).toStrictEqual({ |
||||
'test account': { name: 'test label' }, |
||||
}); |
||||
}); |
||||
|
||||
it('updates tokens', () => { |
||||
const newTokens = { |
||||
address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', |
||||
decimals: 18, |
||||
symbol: 'META', |
||||
}; |
||||
|
||||
const state = reduceMetamask( |
||||
{}, |
||||
{ |
||||
type: actionConstants.UPDATE_TOKENS, |
||||
newTokens, |
||||
}, |
||||
); |
||||
|
||||
expect(state.tokens).toStrictEqual(newTokens); |
||||
}); |
||||
|
||||
it('toggles account menu', () => { |
||||
const state = reduceMetamask( |
||||
{}, |
||||
{ |
||||
type: actionConstants.TOGGLE_ACCOUNT_MENU, |
||||
}, |
||||
); |
||||
|
||||
expect(state.isAccountMenuOpen).toStrictEqual(true); |
||||
}); |
||||
|
||||
it('updates value of tx by id', () => { |
||||
const oldState = { |
||||
currentNetworkTxList: [ |
||||
{ |
||||
id: 1, |
||||
txParams: 'foo', |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
const state = reduceMetamask(oldState, { |
||||
type: actionConstants.UPDATE_TRANSACTION_PARAMS, |
||||
id: 1, |
||||
value: 'bar', |
||||
}); |
||||
|
||||
expect(state.currentNetworkTxList[0].txParams).toStrictEqual('bar'); |
||||
}); |
||||
|
||||
it('sets blockies', () => { |
||||
const state = reduceMetamask( |
||||
{}, |
||||
{ |
||||
type: actionConstants.SET_USE_BLOCKIE, |
||||
value: true, |
||||
}, |
||||
); |
||||
|
||||
expect(state.useBlockie).toStrictEqual(true); |
||||
}); |
||||
|
||||
it('updates an arbitrary feature flag', () => { |
||||
const state = reduceMetamask( |
||||
{}, |
||||
{ |
||||
type: actionConstants.UPDATE_FEATURE_FLAGS, |
||||
value: { |
||||
foo: true, |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
expect(state.featureFlags.foo).toStrictEqual(true); |
||||
}); |
||||
|
||||
it('close welcome screen', () => { |
||||
const state = reduceMetamask( |
||||
{}, |
||||
{ |
||||
type: actionConstants.CLOSE_WELCOME_SCREEN, |
||||
}, |
||||
); |
||||
|
||||
expect(state.welcomeScreenSeen).toStrictEqual(true); |
||||
}); |
||||
|
||||
it('sets current locale', () => { |
||||
const state = reduceMetamask( |
||||
{}, |
||||
{ |
||||
type: actionConstants.SET_CURRENT_LOCALE, |
||||
value: { locale: 'ge' }, |
||||
}, |
||||
); |
||||
|
||||
expect(state.currentLocale).toStrictEqual('ge'); |
||||
}); |
||||
|
||||
it('sets pending tokens', () => { |
||||
const payload = { |
||||
address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', |
||||
decimals: 18, |
||||
symbol: 'META', |
||||
}; |
||||
|
||||
const pendingTokensState = reduceMetamask( |
||||
{}, |
||||
{ |
||||
type: actionConstants.SET_PENDING_TOKENS, |
||||
payload, |
||||
}, |
||||
); |
||||
|
||||
expect(pendingTokensState.pendingTokens).toStrictEqual(payload); |
||||
}); |
||||
|
||||
it('clears pending tokens', () => { |
||||
const payload = { |
||||
address: '0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4', |
||||
decimals: 18, |
||||
symbol: 'META', |
||||
}; |
||||
|
||||
const pendingTokensState = { |
||||
pendingTokens: payload, |
||||
}; |
||||
|
||||
const state = reduceMetamask(pendingTokensState, { |
||||
type: actionConstants.CLEAR_PENDING_TOKENS, |
||||
}); |
||||
|
||||
expect(state.pendingTokens).toStrictEqual({}); |
||||
}); |
||||
|
||||
describe('metamask state selectors', () => { |
||||
describe('getBlockGasLimit', () => { |
||||
it('should return the current block gas limit', () => { |
||||
expect(getBlockGasLimit(mockState)).toStrictEqual('0x4c1878'); |
||||
}); |
||||
}); |
||||
|
||||
describe('getConversionRate()', () => { |
||||
it('should return the eth conversion rate', () => { |
||||
expect(getConversionRate(mockState)).toStrictEqual(1200.88200327); |
||||
}); |
||||
}); |
||||
|
||||
describe('getNativeCurrency()', () => { |
||||
it('should return the ticker symbol of the selected network', () => { |
||||
expect(getNativeCurrency(mockState)).toStrictEqual('ETH'); |
||||
}); |
||||
}); |
||||
|
||||
describe('getSendHexDataFeatureFlagState()', () => { |
||||
it('should return the sendHexData feature flag state', () => { |
||||
expect(getSendHexDataFeatureFlagState(mockState)).toStrictEqual(true); |
||||
}); |
||||
}); |
||||
|
||||
describe('getSendToAccounts()', () => { |
||||
it('should return an array including all the users accounts and the address book', () => { |
||||
expect(getSendToAccounts(mockState)).toStrictEqual([ |
||||
{ |
||||
code: '0x', |
||||
balance: '0x47c9d71831c76efe', |
||||
nonce: '0x1b', |
||||
address: '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825', |
||||
name: 'Send Account 1', |
||||
}, |
||||
{ |
||||
code: '0x', |
||||
balance: '0x37452b1315889f80', |
||||
nonce: '0xa', |
||||
address: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', |
||||
name: 'Send Account 2', |
||||
}, |
||||
{ |
||||
code: '0x', |
||||
balance: '0x30c9d71831c76efe', |
||||
nonce: '0x1c', |
||||
address: '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', |
||||
name: 'Send Account 3', |
||||
}, |
||||
{ |
||||
code: '0x', |
||||
balance: '0x0', |
||||
nonce: '0x0', |
||||
address: '0xd85a4b6a394794842887b8284293d69163007bbb', |
||||
name: 'Send Account 4', |
||||
}, |
||||
{ |
||||
address: '0x06195827297c7a80a443b6894d3bdb8824b43896', |
||||
name: 'Address Book Account 1', |
||||
chainId: '0x3', |
||||
}, |
||||
]); |
||||
}); |
||||
}); |
||||
|
||||
it('should return the unapproved txs', () => { |
||||
expect(getUnapprovedTxs(mockState)).toStrictEqual({ |
||||
4768706228115573: { |
||||
id: 4768706228115573, |
||||
time: 1487363153561, |
||||
status: TRANSACTION_STATUSES.UNAPPROVED, |
||||
gasMultiplier: 1, |
||||
metamaskNetworkId: '3', |
||||
txParams: { |
||||
from: '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', |
||||
to: '0x18a3462427bcc9133bb46e88bcbe39cd7ef0e761', |
||||
value: '0xde0b6b3a7640000', |
||||
metamaskId: 4768706228115573, |
||||
metamaskNetworkId: '3', |
||||
gas: '0x5209', |
||||
}, |
||||
txFee: '17e0186e60800', |
||||
txValue: 'de0b6b3a7640000', |
||||
maxCost: 'de234b52e4a0800', |
||||
gasPrice: '4a817c800', |
||||
}, |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,382 @@ |
||||
import log from 'loglevel'; |
||||
import { estimateGas } from '../../store/actions'; |
||||
import { setCustomGasLimit } from '../gas/gas.duck'; |
||||
import { |
||||
estimateGasForSend, |
||||
calcTokenBalance, |
||||
} from '../../pages/send/send.utils'; |
||||
|
||||
// Actions
|
||||
const OPEN_TO_DROPDOWN = 'metamask/send/OPEN_TO_DROPDOWN'; |
||||
const CLOSE_TO_DROPDOWN = 'metamask/send/CLOSE_TO_DROPDOWN'; |
||||
const UPDATE_SEND_ERRORS = 'metamask/send/UPDATE_SEND_ERRORS'; |
||||
const RESET_SEND_STATE = 'metamask/send/RESET_SEND_STATE'; |
||||
const SHOW_GAS_BUTTON_GROUP = 'metamask/send/SHOW_GAS_BUTTON_GROUP'; |
||||
const HIDE_GAS_BUTTON_GROUP = 'metamask/send/HIDE_GAS_BUTTON_GROUP'; |
||||
const UPDATE_GAS_LIMIT = 'UPDATE_GAS_LIMIT'; |
||||
const UPDATE_GAS_PRICE = 'UPDATE_GAS_PRICE'; |
||||
const UPDATE_GAS_TOTAL = 'UPDATE_GAS_TOTAL'; |
||||
const UPDATE_SEND_HEX_DATA = 'UPDATE_SEND_HEX_DATA'; |
||||
const UPDATE_SEND_TOKEN_BALANCE = 'UPDATE_SEND_TOKEN_BALANCE'; |
||||
const UPDATE_SEND_TO = 'UPDATE_SEND_TO'; |
||||
const UPDATE_SEND_AMOUNT = 'UPDATE_SEND_AMOUNT'; |
||||
const UPDATE_MAX_MODE = 'UPDATE_MAX_MODE'; |
||||
const UPDATE_SEND = 'UPDATE_SEND'; |
||||
const UPDATE_SEND_TOKEN = 'UPDATE_SEND_TOKEN'; |
||||
const CLEAR_SEND = 'CLEAR_SEND'; |
||||
const GAS_LOADING_STARTED = 'GAS_LOADING_STARTED'; |
||||
const GAS_LOADING_FINISHED = 'GAS_LOADING_FINISHED'; |
||||
const UPDATE_SEND_ENS_RESOLUTION = 'UPDATE_SEND_ENS_RESOLUTION'; |
||||
const UPDATE_SEND_ENS_RESOLUTION_ERROR = 'UPDATE_SEND_ENS_RESOLUTION_ERROR'; |
||||
|
||||
const initState = { |
||||
toDropdownOpen: false, |
||||
gasButtonGroupShown: true, |
||||
errors: {}, |
||||
gasLimit: null, |
||||
gasPrice: null, |
||||
gasTotal: null, |
||||
tokenBalance: '0x0', |
||||
from: '', |
||||
to: '', |
||||
amount: '0', |
||||
memo: '', |
||||
maxModeOn: false, |
||||
editingTransactionId: null, |
||||
toNickname: '', |
||||
ensResolution: null, |
||||
ensResolutionError: '', |
||||
gasIsLoading: false, |
||||
}; |
||||
|
||||
// Reducer
|
||||
export default function reducer(state = initState, action) { |
||||
switch (action.type) { |
||||
case OPEN_TO_DROPDOWN: |
||||
return { |
||||
...state, |
||||
toDropdownOpen: true, |
||||
}; |
||||
case CLOSE_TO_DROPDOWN: |
||||
return { |
||||
...state, |
||||
toDropdownOpen: false, |
||||
}; |
||||
case UPDATE_SEND_ERRORS: |
||||
return { |
||||
...state, |
||||
errors: { |
||||
...state.errors, |
||||
...action.value, |
||||
}, |
||||
}; |
||||
case SHOW_GAS_BUTTON_GROUP: |
||||
return { |
||||
...state, |
||||
gasButtonGroupShown: true, |
||||
}; |
||||
case HIDE_GAS_BUTTON_GROUP: |
||||
return { |
||||
...state, |
||||
gasButtonGroupShown: false, |
||||
}; |
||||
case UPDATE_GAS_LIMIT: |
||||
return { |
||||
...state, |
||||
gasLimit: action.value, |
||||
}; |
||||
case UPDATE_GAS_PRICE: |
||||
return { |
||||
...state, |
||||
gasPrice: action.value, |
||||
}; |
||||
case RESET_SEND_STATE: |
||||
return { ...initState }; |
||||
case UPDATE_GAS_TOTAL: |
||||
return { |
||||
...state, |
||||
gasTotal: action.value, |
||||
}; |
||||
case UPDATE_SEND_TOKEN_BALANCE: |
||||
return { |
||||
...state, |
||||
tokenBalance: action.value, |
||||
}; |
||||
case UPDATE_SEND_HEX_DATA: |
||||
return { |
||||
...state, |
||||
data: action.value, |
||||
}; |
||||
case UPDATE_SEND_TO: |
||||
return { |
||||
...state, |
||||
to: action.value.to, |
||||
toNickname: action.value.nickname, |
||||
}; |
||||
case UPDATE_SEND_AMOUNT: |
||||
return { |
||||
...state, |
||||
amount: action.value, |
||||
}; |
||||
case UPDATE_MAX_MODE: |
||||
return { |
||||
...state, |
||||
maxModeOn: action.value, |
||||
}; |
||||
case UPDATE_SEND: |
||||
return Object.assign(state, action.value); |
||||
case UPDATE_SEND_TOKEN: { |
||||
const newSend = { |
||||
...state, |
||||
token: action.value, |
||||
}; |
||||
// erase token-related state when switching back to native currency
|
||||
if (newSend.editingTransactionId && !newSend.token) { |
||||
const unapprovedTx = |
||||
newSend?.unapprovedTxs?.[newSend.editingTransactionId] || {}; |
||||
const txParams = unapprovedTx.txParams || {}; |
||||
Object.assign(newSend, { |
||||
tokenBalance: null, |
||||
balance: '0', |
||||
from: unapprovedTx.from || '', |
||||
unapprovedTxs: { |
||||
...newSend.unapprovedTxs, |
||||
[newSend.editingTransactionId]: { |
||||
...unapprovedTx, |
||||
txParams: { |
||||
...txParams, |
||||
data: '', |
||||
}, |
||||
}, |
||||
}, |
||||
}); |
||||
} |
||||
return Object.assign(state, newSend); |
||||
} |
||||
case UPDATE_SEND_ENS_RESOLUTION: |
||||
return { |
||||
...state, |
||||
ensResolution: action.payload, |
||||
ensResolutionError: '', |
||||
}; |
||||
case UPDATE_SEND_ENS_RESOLUTION_ERROR: |
||||
return { |
||||
...state, |
||||
ensResolution: null, |
||||
ensResolutionError: action.payload, |
||||
}; |
||||
case CLEAR_SEND: |
||||
return { |
||||
...state, |
||||
gasLimit: null, |
||||
gasPrice: null, |
||||
gasTotal: null, |
||||
tokenBalance: null, |
||||
from: '', |
||||
to: '', |
||||
amount: '0x0', |
||||
memo: '', |
||||
errors: {}, |
||||
maxModeOn: false, |
||||
editingTransactionId: null, |
||||
toNickname: '', |
||||
}; |
||||
case GAS_LOADING_STARTED: |
||||
return { |
||||
...state, |
||||
gasIsLoading: true, |
||||
}; |
||||
|
||||
case GAS_LOADING_FINISHED: |
||||
return { |
||||
...state, |
||||
gasIsLoading: false, |
||||
}; |
||||
default: |
||||
return state; |
||||
} |
||||
} |
||||
|
||||
// Action Creators
|
||||
export function openToDropdown() { |
||||
return { type: OPEN_TO_DROPDOWN }; |
||||
} |
||||
|
||||
export function closeToDropdown() { |
||||
return { type: CLOSE_TO_DROPDOWN }; |
||||
} |
||||
|
||||
export function showGasButtonGroup() { |
||||
return { type: SHOW_GAS_BUTTON_GROUP }; |
||||
} |
||||
|
||||
export function hideGasButtonGroup() { |
||||
return { type: HIDE_GAS_BUTTON_GROUP }; |
||||
} |
||||
|
||||
export function updateSendErrors(errorObject) { |
||||
return { |
||||
type: UPDATE_SEND_ERRORS, |
||||
value: errorObject, |
||||
}; |
||||
} |
||||
|
||||
export function resetSendState() { |
||||
return { type: RESET_SEND_STATE }; |
||||
} |
||||
|
||||
export function setGasLimit(gasLimit) { |
||||
return { |
||||
type: UPDATE_GAS_LIMIT, |
||||
value: gasLimit, |
||||
}; |
||||
} |
||||
|
||||
export function setGasPrice(gasPrice) { |
||||
return { |
||||
type: UPDATE_GAS_PRICE, |
||||
value: gasPrice, |
||||
}; |
||||
} |
||||
|
||||
export function setGasTotal(gasTotal) { |
||||
return { |
||||
type: UPDATE_GAS_TOTAL, |
||||
value: gasTotal, |
||||
}; |
||||
} |
||||
|
||||
export function updateGasData({ |
||||
gasPrice, |
||||
blockGasLimit, |
||||
selectedAddress, |
||||
sendToken, |
||||
to, |
||||
value, |
||||
data, |
||||
}) { |
||||
return (dispatch) => { |
||||
dispatch(gasLoadingStarted()); |
||||
return estimateGasForSend({ |
||||
estimateGasMethod: estimateGas, |
||||
blockGasLimit, |
||||
selectedAddress, |
||||
sendToken, |
||||
to, |
||||
value, |
||||
estimateGasPrice: gasPrice, |
||||
data, |
||||
}) |
||||
.then((gas) => { |
||||
dispatch(setGasLimit(gas)); |
||||
dispatch(setCustomGasLimit(gas)); |
||||
dispatch(updateSendErrors({ gasLoadingError: null })); |
||||
dispatch(gasLoadingFinished()); |
||||
}) |
||||
.catch((err) => { |
||||
log.error(err); |
||||
dispatch(updateSendErrors({ gasLoadingError: 'gasLoadingError' })); |
||||
dispatch(gasLoadingFinished()); |
||||
}); |
||||
}; |
||||
} |
||||
|
||||
export function gasLoadingStarted() { |
||||
return { |
||||
type: GAS_LOADING_STARTED, |
||||
}; |
||||
} |
||||
|
||||
export function gasLoadingFinished() { |
||||
return { |
||||
type: GAS_LOADING_FINISHED, |
||||
}; |
||||
} |
||||
|
||||
export function updateSendTokenBalance({ sendToken, tokenContract, address }) { |
||||
return (dispatch) => { |
||||
const tokenBalancePromise = tokenContract |
||||
? tokenContract.balanceOf(address) |
||||
: Promise.resolve(); |
||||
return tokenBalancePromise |
||||
.then((usersToken) => { |
||||
if (usersToken) { |
||||
const newTokenBalance = calcTokenBalance({ sendToken, usersToken }); |
||||
dispatch(setSendTokenBalance(newTokenBalance)); |
||||
} |
||||
}) |
||||
.catch((err) => { |
||||
log.error(err); |
||||
updateSendErrors({ tokenBalance: 'tokenBalanceError' }); |
||||
}); |
||||
}; |
||||
} |
||||
|
||||
export function setSendTokenBalance(tokenBalance) { |
||||
return { |
||||
type: UPDATE_SEND_TOKEN_BALANCE, |
||||
value: tokenBalance, |
||||
}; |
||||
} |
||||
|
||||
export function updateSendHexData(value) { |
||||
return { |
||||
type: UPDATE_SEND_HEX_DATA, |
||||
value, |
||||
}; |
||||
} |
||||
|
||||
export function updateSendTo(to, nickname = '') { |
||||
return { |
||||
type: UPDATE_SEND_TO, |
||||
value: { to, nickname }, |
||||
}; |
||||
} |
||||
|
||||
export function updateSendAmount(amount) { |
||||
return { |
||||
type: UPDATE_SEND_AMOUNT, |
||||
value: amount, |
||||
}; |
||||
} |
||||
|
||||
export function setMaxModeTo(bool) { |
||||
return { |
||||
type: UPDATE_MAX_MODE, |
||||
value: bool, |
||||
}; |
||||
} |
||||
|
||||
export function updateSend(newSend) { |
||||
return { |
||||
type: UPDATE_SEND, |
||||
value: newSend, |
||||
}; |
||||
} |
||||
|
||||
export function updateSendToken(token) { |
||||
return { |
||||
type: UPDATE_SEND_TOKEN, |
||||
value: token, |
||||
}; |
||||
} |
||||
|
||||
export function clearSend() { |
||||
return { |
||||
type: CLEAR_SEND, |
||||
}; |
||||
} |
||||
|
||||
export function updateSendEnsResolution(ensResolution) { |
||||
return { |
||||
type: UPDATE_SEND_ENS_RESOLUTION, |
||||
payload: ensResolution, |
||||
}; |
||||
} |
||||
|
||||
export function updateSendEnsResolutionError(errorMessage) { |
||||
return { |
||||
type: UPDATE_SEND_ENS_RESOLUTION_ERROR, |
||||
payload: errorMessage, |
||||
}; |
||||
} |
@ -1,9 +0,0 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`IntroPopup renders the component with initial props 1`] = ` |
||||
<div> |
||||
<div |
||||
class="intro-popup" |
||||
/> |
||||
</div> |
||||
`; |
@ -1 +0,0 @@ |
||||
export { default } from './intro-popup'; |
@ -1,71 +0,0 @@ |
||||
.intro-popup { |
||||
&__liquidity-sources-label { |
||||
@include H7; |
||||
|
||||
font-weight: bold; |
||||
margin-bottom: 6px; |
||||
color: $Black-100; |
||||
|
||||
@media screen and (min-width: 576px) { |
||||
@include H6; |
||||
} |
||||
} |
||||
|
||||
&__learn-more-header { |
||||
@include H4; |
||||
|
||||
font-weight: bold; |
||||
margin-bottom: 12px; |
||||
margin-top: 16px; |
||||
} |
||||
|
||||
&__learn-more-link { |
||||
@include H6; |
||||
|
||||
color: $Blue-500; |
||||
margin-bottom: 8px; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
&__content { |
||||
margin-left: 24px; |
||||
|
||||
> img { |
||||
width: 96%; |
||||
margin-left: -9px; |
||||
} |
||||
} |
||||
|
||||
&__footer { |
||||
border-top: none; |
||||
} |
||||
|
||||
&__button { |
||||
border-radius: 100px; |
||||
height: 44px; |
||||
} |
||||
|
||||
&__source-logo-container { |
||||
width: 276px; |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
padding: 20px 16px; |
||||
background: $Grey-000; |
||||
border-radius: 8px; |
||||
|
||||
@media screen and (min-width: 576px) { |
||||
width: 412px; |
||||
|
||||
img { |
||||
width: 364px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
&__popover { |
||||
@media screen and (min-width: 576px) { |
||||
width: 460px; |
||||
} |
||||
} |
||||
} |
@ -1,108 +0,0 @@ |
||||
import React, { useContext } from 'react'; |
||||
import { useDispatch, useSelector } from 'react-redux'; |
||||
import { useHistory } from 'react-router-dom'; |
||||
import PropTypes from 'prop-types'; |
||||
import { setSwapsFromToken } from '../../../ducks/swaps/swaps'; |
||||
import { I18nContext } from '../../../contexts/i18n'; |
||||
import { BUILD_QUOTE_ROUTE } from '../../../helpers/constants/routes'; |
||||
import { useNewMetricEvent } from '../../../hooks/useMetricEvent'; |
||||
import { getSwapsDefaultToken } from '../../../selectors'; |
||||
import Button from '../../../components/ui/button'; |
||||
import Popover from '../../../components/ui/popover'; |
||||
|
||||
export default function IntroPopup({ onClose }) { |
||||
const dispatch = useDispatch(useDispatch); |
||||
const history = useHistory(); |
||||
const t = useContext(I18nContext); |
||||
|
||||
const swapsDefaultToken = useSelector(getSwapsDefaultToken); |
||||
const enteredSwapsEvent = useNewMetricEvent({ |
||||
event: 'Swaps Opened', |
||||
properties: { |
||||
source: 'Intro popup', |
||||
active_currency: swapsDefaultToken.symbol, |
||||
}, |
||||
category: 'swaps', |
||||
}); |
||||
const blogPostVisitedEvent = useNewMetricEvent({ |
||||
event: 'Blog Post Visited ', |
||||
category: 'swaps', |
||||
}); |
||||
const contractAuditVisitedEvent = useNewMetricEvent({ |
||||
event: 'Contract Audit Visited', |
||||
category: 'swaps', |
||||
}); |
||||
const productOverviewDismissedEvent = useNewMetricEvent({ |
||||
event: 'Product Overview Dismissed', |
||||
category: 'swaps', |
||||
}); |
||||
|
||||
return ( |
||||
<div className="intro-popup"> |
||||
<Popover |
||||
className="intro-popup__popover" |
||||
title={t('swapIntroPopupTitle')} |
||||
subtitle={t('swapIntroPopupSubTitle')} |
||||
onClose={() => { |
||||
productOverviewDismissedEvent(); |
||||
onClose(); |
||||
}} |
||||
footerClassName="intro-popup__footer" |
||||
footer={ |
||||
<Button |
||||
type="confirm" |
||||
className="intro-popup__button" |
||||
onClick={() => { |
||||
onClose(); |
||||
enteredSwapsEvent(); |
||||
dispatch(setSwapsFromToken(swapsDefaultToken)); |
||||
history.push(BUILD_QUOTE_ROUTE); |
||||
}} |
||||
> |
||||
{t('swapStartSwapping')} |
||||
</Button> |
||||
} |
||||
> |
||||
<div className="intro-popup__content"> |
||||
<div className="intro-popup__liquidity-sources-label"> |
||||
{t('swapIntroLiquiditySourcesLabel')} |
||||
</div> |
||||
<div className="intro-popup__source-logo-container"> |
||||
<img src="images/source-logos-all.svg" alt="" /> |
||||
</div> |
||||
<div className="intro-popup__learn-more-header"> |
||||
{t('swapIntroLearnMoreHeader')} |
||||
</div> |
||||
<div |
||||
className="intro-popup__learn-more-link" |
||||
onClick={() => { |
||||
global.platform.openTab({ |
||||
url: |
||||
'https://medium.com/metamask/introducing-metamask-swaps-84318c643785', |
||||
}); |
||||
blogPostVisitedEvent(); |
||||
}} |
||||
> |
||||
{t('swapIntroLearnMoreLink')} |
||||
</div> |
||||
<div |
||||
className="intro-popup__learn-more-link" |
||||
onClick={() => { |
||||
global.platform.openTab({ |
||||
url: |
||||
'https://diligence.consensys.net/audits/private/lsjipyllnw2/', |
||||
}); |
||||
contractAuditVisitedEvent(); |
||||
}} |
||||
> |
||||
{t('swapLearnMoreContractsAuditReview')} |
||||
</div> |
||||
</div> |
||||
</Popover> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
IntroPopup.propTypes = { |
||||
onClose: PropTypes.func.isRequired, |
||||
}; |
@ -1,24 +0,0 @@ |
||||
import React from 'react'; |
||||
import configureMockStore from 'redux-mock-store'; |
||||
|
||||
import { |
||||
renderWithProvider, |
||||
createSwapsMockStore, |
||||
} from '../../../../test/jest'; |
||||
import IntroPopup from '.'; |
||||
|
||||
const createProps = (customProps = {}) => { |
||||
return { |
||||
onClose: jest.fn(), |
||||
...customProps, |
||||
}; |
||||
}; |
||||
|
||||
describe('IntroPopup', () => { |
||||
it('renders the component with initial props', () => { |
||||
const store = configureMockStore()(createSwapsMockStore()); |
||||
const props = createProps(); |
||||
const { container } = renderWithProvider(<IntroPopup {...props} />, store); |
||||
expect(container).toMatchSnapshot(); |
||||
}); |
||||
}); |
Loading…
Reference in new issue