Merge branch 'development' into feature/instant/events-buy

* development:
  fix: add --exclude-missing flag to yarn clean command
  Update CHANGELOG
  fix: remove getApproved check from OrderValidator since approval is removed after a single transfer
  Add comment
  Force scaling input component to rerender when a different asset is chosen
  Send in explicit props
  feat(instant): Add more event properties to heap
  Move out generating of event properties, and send in orderSource
  autofocus -> hasAutoFocus
  fix(instant): Right align amounts
  fix(instant): Autofocus text amount input
This commit is contained in:
Brandon Millman
2018-11-26 23:11:05 -08:00
11 changed files with 72 additions and 18 deletions

View File

@@ -28,7 +28,7 @@
"build:monorepo_scripts": "PKG=@0x/monorepo-scripts yarn build", "build:monorepo_scripts": "PKG=@0x/monorepo-scripts yarn build",
"build:ts": "tsc -b", "build:ts": "tsc -b",
"watch:ts": "tsc -b -w", "watch:ts": "tsc -b -w",
"clean": "wsrun clean $PKG --fast-exit -r --parallel", "clean": "wsrun clean $PKG --fast-exit -r --parallel --exclude-missing",
"remove_node_modules": "lerna clean --yes; rm -rf node_modules", "remove_node_modules": "lerna clean --yes; rm -rf node_modules",
"rebuild": "run-s clean build", "rebuild": "run-s clean build",
"rebuild:no_website": "run-s clean build:no_website", "rebuild:no_website": "run-s clean build:no_website",

View File

@@ -1,4 +1,14 @@
[ [
{
"name": "OrderValidator",
"version": "1.0.1",
"changes": [
{
"note": "remove `getApproved` check from ERC721 approval query",
"pr": 1149
}
]
},
{ {
"name": "Forwarder", "name": "Forwarder",
"version": "1.1.0", "version": "1.1.0",

View File

@@ -148,7 +148,7 @@ contract OrderValidator {
balance = target == owner ? 1 : 0; balance = target == owner ? 1 : 0;
// Check if ERC721Proxy is approved to spend tokenId // Check if ERC721Proxy is approved to spend tokenId
bool isApproved = IERC721Token(token).isApprovedForAll(target, assetProxy) || IERC721Token(token).getApproved(tokenId) == assetProxy; bool isApproved = IERC721Token(token).isApprovedForAll(target, assetProxy);
// Set alowance to 1 if ERC721Proxy is approved to spend tokenId // Set alowance to 1 if ERC721Proxy is approved to spend tokenId
allowance = isApproved ? 1 : 0; allowance = isApproved ? 1 : 0;

View File

@@ -198,7 +198,7 @@ describe('OrderValidator', () => {
); );
expect(newAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE); expect(newAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
}); });
it('should return an allowance of 1 when ERC721Proxy is approved for specific tokenId', async () => { it('should return an allowance of 0 when ERC721Proxy is approved for specific tokenId', async () => {
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId), await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
@@ -213,7 +213,7 @@ describe('OrderValidator', () => {
makerAddress, makerAddress,
erc721AssetData, erc721AssetData,
); );
expect(newAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE); expect(newAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
}); });
}); });
}); });
@@ -248,8 +248,9 @@ describe('OrderValidator', () => {
await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId), await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
const isApproved = true;
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await erc721Token.approve.sendTransactionAsync(erc721Proxy.address, tokenId, { await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
from: makerAddress, from: makerAddress,
}), }),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
@@ -311,8 +312,9 @@ describe('OrderValidator', () => {
await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId), await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
const isApproved = true;
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await erc721Token.approve.sendTransactionAsync(erc721Proxy.address, tokenId, { await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
from: takerAddress, from: takerAddress,
}), }),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
@@ -465,8 +467,9 @@ describe('OrderValidator', () => {
await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId), await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,
); );
const isApproved = true;
await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.awaitTransactionSuccessAsync(
await erc721Token.approve.sendTransactionAsync(erc721Proxy.address, tokenId, { await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
from: takerAddress, from: takerAddress,
}), }),
constants.AWAIT_TRANSACTION_MINED_MS, constants.AWAIT_TRANSACTION_MINED_MS,

View File

@@ -64,6 +64,9 @@ export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInput
maxFontSizePx={this.props.startingFontSizePx} maxFontSizePx={this.props.startingFontSizePx}
onAmountChange={this._handleChange} onAmountChange={this._handleChange}
onFontSizeChange={this._handleFontSizeChange} onFontSizeChange={this._handleFontSizeChange}
hasAutofocus={true}
/* We send in a key of asset data to force a rerender of this component when the user selects a new asset. We do this so the autofocus attribute will bring focus onto this input */
key={asset.assetData}
/> />
</Container> </Container>
<Container <Container

View File

@@ -107,7 +107,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
private readonly _renderEthAmount = (): React.ReactNode => { private readonly _renderEthAmount = (): React.ReactNode => {
return ( return (
<Text fontSize="16px" fontColor={ColorOption.white} fontWeight={500}> <Text fontSize="16px" textAlign="right" width="100%" fontColor={ColorOption.white} fontWeight={500}>
{format.ethBaseUnitAmount( {format.ethBaseUnitAmount(
this.props.totalEthBaseUnitAmount, this.props.totalEthBaseUnitAmount,
4, 4,
@@ -119,7 +119,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
private readonly _renderDollarAmount = (): React.ReactNode => { private readonly _renderDollarAmount = (): React.ReactNode => {
return ( return (
<Text fontSize="16px" fontColor={ColorOption.white}> <Text fontSize="16px" textAlign="right" width="100%" fontColor={ColorOption.white}>
{format.ethBaseUnitAmountInUsd( {format.ethBaseUnitAmountInUsd(
this.props.totalEthBaseUnitAmount, this.props.totalEthBaseUnitAmount,
this.props.ethUsdPrice, this.props.ethUsdPrice,

View File

@@ -18,6 +18,7 @@ export interface ScalingAmountInputProps {
value?: BigNumber; value?: BigNumber;
onAmountChange: (value?: BigNumber) => void; onAmountChange: (value?: BigNumber) => void;
onFontSizeChange: (fontSizePx: number) => void; onFontSizeChange: (fontSizePx: number) => void;
hasAutofocus: boolean;
} }
interface ScalingAmountInputState { interface ScalingAmountInputState {
stringValue: string; stringValue: string;
@@ -29,6 +30,7 @@ export class ScalingAmountInput extends React.Component<ScalingAmountInputProps,
onAmountChange: util.boundNoop, onAmountChange: util.boundNoop,
onFontSizeChange: util.boundNoop, onFontSizeChange: util.boundNoop,
isDisabled: false, isDisabled: false,
hasAutofocus: false,
}; };
public constructor(props: ScalingAmountInputProps) { public constructor(props: ScalingAmountInputProps) {
super(props); super(props);
@@ -64,6 +66,7 @@ export class ScalingAmountInput extends React.Component<ScalingAmountInputProps,
placeholder="0.00" placeholder="0.00"
emptyInputWidthCh={3.5} emptyInputWidthCh={3.5}
isDisabled={this.props.isDisabled} isDisabled={this.props.isDisabled}
hasAutofocus={this.props.hasAutofocus}
/> />
); );
} }

View File

@@ -28,6 +28,7 @@ export interface ScalingInputProps {
maxLength?: number; maxLength?: number;
scalingSettings: ScalingSettings; scalingSettings: ScalingSettings;
isDisabled: boolean; isDisabled: boolean;
hasAutofocus: boolean;
} }
export interface ScalingInputState { export interface ScalingInputState {
@@ -51,6 +52,7 @@ export class ScalingInput extends React.Component<ScalingInputProps, ScalingInpu
maxLength: 7, maxLength: 7,
scalingSettings: defaultScalingSettings, scalingSettings: defaultScalingSettings,
isDisabled: false, isDisabled: false,
hasAutofocus: false,
}; };
public state: ScalingInputState = { public state: ScalingInputState = {
inputWidthPxAtPhaseChange: undefined, inputWidthPxAtPhaseChange: undefined,
@@ -123,7 +125,7 @@ export class ScalingInput extends React.Component<ScalingInputProps, ScalingInpu
} }
} }
public render(): React.ReactNode { public render(): React.ReactNode {
const { isDisabled, fontColor, onChange, placeholder, value, maxLength } = this.props; const { hasAutofocus, isDisabled, fontColor, onChange, placeholder, value, maxLength } = this.props;
const phase = ScalingInput.getPhaseFromProps(this.props); const phase = ScalingInput.getPhaseFromProps(this.props);
return ( return (
<Input <Input
@@ -136,6 +138,7 @@ export class ScalingInput extends React.Component<ScalingInputProps, ScalingInpu
width={this._calculateWidth(phase)} width={this._calculateWidth(phase)}
maxLength={maxLength} maxLength={maxLength}
disabled={isDisabled} disabled={isDisabled}
autoFocus={hasAutofocus}
/> />
); );
} }

View File

@@ -11,6 +11,7 @@ export interface TextProps {
fontSize?: string; fontSize?: string;
opacity?: number; opacity?: number;
letterSpacing?: string; letterSpacing?: string;
textAlign?: string;
textTransform?: string; textTransform?: string;
lineHeight?: string; lineHeight?: string;
className?: string; className?: string;
@@ -22,6 +23,7 @@ export interface TextProps {
noWrap?: boolean; noWrap?: boolean;
display?: string; display?: string;
href?: string; href?: string;
width?: string;
} }
export const Text: React.StatelessComponent<TextProps> = ({ href, onClick, ...rest }) => { export const Text: React.StatelessComponent<TextProps> = ({ href, onClick, ...rest }) => {
@@ -51,6 +53,8 @@ export const StyledText =
${props => (props.display ? `display: ${props.display}` : '')}; ${props => (props.display ? `display: ${props.display}` : '')};
${props => (props.letterSpacing ? `letter-spacing: ${props.letterSpacing}` : '')}; ${props => (props.letterSpacing ? `letter-spacing: ${props.letterSpacing}` : '')};
${props => (props.textTransform ? `text-transform: ${props.textTransform}` : '')}; ${props => (props.textTransform ? `text-transform: ${props.textTransform}` : '')};
${props => (props.textAlign ? `text-align: ${props.textAlign}` : '')};
${props => (props.width ? `width: ${props.width}` : '')};
&:hover { &:hover {
${props => ${props =>
props.onClick ? `color: ${darken(darkenOnHoverAmount, props.theme[props.fontColor || 'white'])}` : ''}; props.onClick ? `color: ${darken(darkenOnHoverAmount, props.theme[props.fontColor || 'white'])}` : ''};

View File

@@ -125,14 +125,15 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
// Analytics // Analytics
disableAnalytics(this.props.shouldDisableAnalyticsTracking || false); disableAnalytics(this.props.shouldDisableAnalyticsTracking || false);
analytics.addEventProperties({ analytics.addEventProperties(
embeddedHost: window.location.host, analytics.generateEventProperties(
embeddedUrl: window.location.href, state.network,
networkId: state.network, this.props.orderSource,
providerName: state.providerState.name, state.providerState,
gitSha: process.env.GIT_SHA, window,
npmVersion: process.env.NPM_PACKAGE_VERSION, this.props.affiliateInfo,
}); ),
);
analytics.trackInstantOpened(); analytics.trackInstantOpened();
} }
public componentWillUnmount(): void { public componentWillUnmount(): void {

View File

@@ -1,6 +1,8 @@
import { BuyQuote } from '@0x/asset-buyer'; import { BuyQuote } from '@0x/asset-buyer';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { AffiliateInfo, Network, OrderSource, ProviderState } from '../types';
import { EventProperties, heapUtil } from './heap'; import { EventProperties, heapUtil } from './heap';
let isDisabled = false; let isDisabled = false;
@@ -74,6 +76,9 @@ export interface AnalyticsEventOptions {
providerName?: string; providerName?: string;
gitSha?: string; gitSha?: string;
npmVersion?: string; npmVersion?: string;
orderSource?: string;
affiliateAddress?: string;
affiliateFeePercent?: number;
} }
export const analytics = { export const analytics = {
@@ -87,6 +92,28 @@ export const analytics = {
heapUtil.evaluateHeapCall(heap => heap.addEventProperties(properties)); heapUtil.evaluateHeapCall(heap => heap.addEventProperties(properties));
}); });
}, },
generateEventProperties: (
network: Network,
orderSource: OrderSource,
providerState: ProviderState,
window: Window,
affiliateInfo?: AffiliateInfo,
): AnalyticsEventOptions => {
const affiliateAddress = affiliateInfo ? affiliateInfo.feeRecipient : 'none';
const affiliateFeePercent = affiliateInfo ? parseFloat(affiliateInfo.feePercentage.toFixed(4)) : 0;
const orderSourceName = typeof orderSource === 'string' ? orderSource : 'provided';
return {
embeddedHost: window.location.host,
embeddedUrl: window.location.href,
networkId: network,
providerName: providerState.name,
gitSha: process.env.GIT_SHA,
npmVersion: process.env.NPM_PACKAGE_VERSION,
orderSource: orderSourceName,
affiliateAddress,
affiliateFeePercent,
};
},
trackInstantOpened: trackingEventFnWithoutPayload(EventNames.INSTANT_OPENED), trackInstantOpened: trackingEventFnWithoutPayload(EventNames.INSTANT_OPENED),
trackAccountLocked: trackingEventFnWithoutPayload(EventNames.ACCOUNT_LOCKED), trackAccountLocked: trackingEventFnWithoutPayload(EventNames.ACCOUNT_LOCKED),
trackAccountReady: (address: string) => trackingEventFnWithPayload(EventNames.ACCOUNT_READY)({ address }), trackAccountReady: (address: string) => trackingEventFnWithPayload(EventNames.ACCOUNT_READY)({ address }),