diff options
author | Fabio Berger <me@fabioberger.com> | 2018-06-06 17:10:27 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2018-06-06 17:10:27 +0800 |
commit | cbfed99bc6f9c86dee2dc31cafe401ded0fc9084 (patch) | |
tree | 6a1608b6fca9366f6b407e8a2cf746a79b86858b /packages/website | |
parent | 05b9dfbe30c55a67e6d2d63898f52e5fa412fcfb (diff) | |
parent | 39570a9663abae56b0220745306386197fae65c1 (diff) | |
download | dexon-0x-contracts-cbfed99bc6f9c86dee2dc31cafe401ded0fc9084.tar dexon-0x-contracts-cbfed99bc6f9c86dee2dc31cafe401ded0fc9084.tar.gz dexon-0x-contracts-cbfed99bc6f9c86dee2dc31cafe401ded0fc9084.tar.bz2 dexon-0x-contracts-cbfed99bc6f9c86dee2dc31cafe401ded0fc9084.tar.lz dexon-0x-contracts-cbfed99bc6f9c86dee2dc31cafe401ded0fc9084.tar.xz dexon-0x-contracts-cbfed99bc6f9c86dee2dc31cafe401ded0fc9084.tar.zst dexon-0x-contracts-cbfed99bc6f9c86dee2dc31cafe401ded0fc9084.zip |
Merge branch 'v2-prototype' into fixes/misc-small-fixes
* v2-prototype:
Remove TranslatedText
Fix prettier
Add back UMD bundles for 0x.js
Move portal disclaimer to the account management section
Move prices into portal
Use stricter check for subscribe input text
Make buttons stack on mobile
Do not show subscribe form if language is not english
Address PR feedback
Lint and cleanup
Implement subscription form
Add styled-components and polished
Have basic newsletter subscribe form working
Diffstat (limited to 'packages/website')
29 files changed, 433 insertions, 107 deletions
diff --git a/packages/website/package.json b/packages/website/package.json index a17964f2b..54780f600 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -37,6 +37,7 @@ "lodash": "^4.17.4", "material-ui": "^0.17.1", "moment": "2.21.0", + "polished": "^1.9.2", "query-string": "^6.0.0", "react": "15.6.1", "react-copy-to-clipboard": "^4.2.3", @@ -52,6 +53,7 @@ "redux": "^3.6.0", "redux-devtools-extension": "^2.13.2", "semver-sort": "0.0.4", + "styled-components": "^3.3.0", "thenby": "^1.2.3", "truffle-contract": "2.0.1", "web3": "^0.20.0", diff --git a/packages/website/ts/components/forms/subscribe_form.tsx b/packages/website/ts/components/forms/subscribe_form.tsx new file mode 100644 index 000000000..04ef28e70 --- /dev/null +++ b/packages/website/ts/components/forms/subscribe_form.tsx @@ -0,0 +1,124 @@ +import { colors } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { Button } from 'ts/components/ui/button'; +import { Container } from 'ts/components/ui/container'; +import { Input } from 'ts/components/ui/input'; +import { Text } from 'ts/components/ui/text'; +import { styled } from 'ts/style/theme'; +import { backendClient } from 'ts/utils/backend_client'; + +export interface SubscribeFormProps {} + +export enum SubscribeFormStatus { + None, + Error, + Success, + Loading, + Other, +} + +export interface SubscribeFormState { + emailText: string; + lastSubmittedEmail: string; + status: SubscribeFormStatus; +} + +const FORM_FONT_SIZE = '15px'; + +// TODO: Translate visible strings. https://app.asana.com/0/628666249318202/697485674422001 +export class SubscribeForm extends React.Component<SubscribeFormProps, SubscribeFormState> { + public state = { + emailText: '', + lastSubmittedEmail: '', + status: SubscribeFormStatus.None, + }; + public render(): React.ReactNode { + return ( + <Container className="flex flex-column items-center justify-between md-mx2 sm-mx2"> + <Container marginBottom="15px"> + <Text fontFamily="Roboto Mono" fontColor={colors.grey} center={true}> + Subscribe to our newsletter for 0x relayer and dApp updates + </Text> + </Container> + <form onSubmit={this._handleFormSubmitAsync.bind(this)}> + <Container className="flex flex-wrap justify-center items-center"> + <Container marginTop="15px"> + <Input + placeholder="you@email.com" + value={this.state.emailText} + fontColor={colors.white} + fontSize={FORM_FONT_SIZE} + backgroundColor={colors.projectsGrey} + width="300px" + onChange={this._handleEmailInputChange.bind(this)} + /> + </Container> + <Container marginLeft="15px" marginTop="15px"> + <Button + type="submit" + backgroundColor={colors.darkestGrey} + fontColor={colors.white} + fontSize={FORM_FONT_SIZE} + > + Subscribe + </Button> + </Container> + </Container> + </form> + {this._renderMessage()} + </Container> + ); + } + private _renderMessage(): React.ReactNode { + let message = null; + switch (this.state.status) { + case SubscribeFormStatus.Error: + message = 'Sorry, something went wrong. Try again later.'; + break; + case SubscribeFormStatus.Loading: + message = 'One second...'; + break; + case SubscribeFormStatus.Success: + message = `Thanks! ${this.state.lastSubmittedEmail} is now on the mailing list.`; + break; + case SubscribeFormStatus.None: + break; + default: + throw new Error( + 'The SubscribeFormStatus switch statement is not exhaustive when choosing an error message.', + ); + } + return ( + <Container isHidden={!message} marginTop="30px"> + <Text center={true} fontFamily="Roboto Mono" fontColor={colors.grey}> + {message || 'spacer text (never shown to user)'} + </Text> + </Container> + ); + } + private _handleEmailInputChange(event: React.ChangeEvent<HTMLInputElement>): void { + this.setState({ emailText: event.target.value }); + } + private async _handleFormSubmitAsync(event: React.FormEvent<HTMLInputElement>): Promise<void> { + event.preventDefault(); + if (_.isUndefined(this.state.emailText) || _.isEmpty(this.state.emailText)) { + return; + } + this.setState({ + status: SubscribeFormStatus.Loading, + lastSubmittedEmail: this.state.emailText, + }); + try { + const response = await backendClient.subscribeToNewsletterAsync(this.state.emailText); + const status = response.status === 200 ? SubscribeFormStatus.Success : SubscribeFormStatus.Error; + this.setState({ status, emailText: '' }); + } catch (error) { + this._setStatus(SubscribeFormStatus.Error); + } + } + private _setStatus(status: SubscribeFormStatus): void { + this.setState({ status }); + } +} diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx index d61dfa87d..a8df4935a 100644 --- a/packages/website/ts/components/inputs/allowance_toggle.tsx +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -5,9 +5,9 @@ import Toggle from 'material-ui/Toggle'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { colors } from 'ts/style/colors'; import { BalanceErrs, Token, TokenState } from 'ts/types'; import { analytics } from 'ts/utils/analytics'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/components/onboarding/onboarding_flow.tsx b/packages/website/ts/components/onboarding/onboarding_flow.tsx index 4066babaf..158ac23ec 100644 --- a/packages/website/ts/components/onboarding/onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/onboarding_flow.tsx @@ -5,7 +5,7 @@ import { Placement, Popper, PopperChildrenProps } from 'react-popper'; import { ContinueButtonDisplay, OnboardingTooltip } from 'ts/components/onboarding/onboarding_tooltip'; import { Container } from 'ts/components/ui/container'; import { Overlay } from 'ts/components/ui/overlay'; -import { zIndex } from 'ts/utils/style'; +import { zIndex } from 'ts/style/z_index'; export interface Step { target: string; diff --git a/packages/website/ts/components/portal/back_button.tsx b/packages/website/ts/components/portal/back_button.tsx index 48858613c..2d0bbefc3 100644 --- a/packages/website/ts/components/portal/back_button.tsx +++ b/packages/website/ts/components/portal/back_button.tsx @@ -2,7 +2,7 @@ import { Styles } from '@0xproject/react-shared'; import * as React from 'react'; import { Link } from 'react-router-dom'; -import { colors } from 'ts/utils/colors'; +import { colors } from 'ts/style/colors'; export interface BackButtonProps { to: string; diff --git a/packages/website/ts/components/portal/drawer_menu.tsx b/packages/website/ts/components/portal/drawer_menu.tsx index ace11639a..8ac2b9091 100644 --- a/packages/website/ts/components/portal/drawer_menu.tsx +++ b/packages/website/ts/components/portal/drawer_menu.tsx @@ -4,8 +4,8 @@ import * as React from 'react'; import { defaultMenuItemEntries, Menu } from 'ts/components/portal/menu'; import { Identicon } from 'ts/components/ui/identicon'; +import { colors } from 'ts/style/colors'; import { WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; const IDENTICON_DIAMETER = 45; diff --git a/packages/website/ts/components/portal/menu.tsx b/packages/website/ts/components/portal/menu.tsx index 6e97ee37e..f98346101 100644 --- a/packages/website/ts/components/portal/menu.tsx +++ b/packages/website/ts/components/portal/menu.tsx @@ -2,8 +2,8 @@ import { Styles } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { MenuItem } from 'ts/components/ui/menu_item'; +import { colors } from 'ts/style/colors'; import { Environments, WebsitePaths } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; export interface MenuTheme { diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 9aa83546a..9420395cf 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -34,6 +34,7 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { BlockchainErrs, HashData, + ItemByAddress, Order, ProviderType, ScreenWidths, @@ -43,6 +44,7 @@ import { TokenVisibility, WebsitePaths, } from 'ts/types'; +import { backendClient } from 'ts/utils/backend_client'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { orderParser } from 'ts/utils/order_parser'; @@ -266,10 +268,6 @@ export class Portal extends React.Component<PortalProps, PortalState> { toggleDialogFn={updateShouldBlockchainErrDialogBeOpen} networkId={this.props.networkId} /> - <PortalDisclaimerDialog - isOpen={this.state.isDisclaimerDialogOpen} - onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} - /> <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} /> {this.props.blockchainIsLoaded && ( <LedgerConfigDialog @@ -394,18 +392,24 @@ export class Portal extends React.Component<PortalProps, PortalState> { }, ]; return ( - <Switch> - {_.map(accountManagementItems, item => { - return ( - <Route - key={item.pathName} - path={item.pathName} - render={this._renderAccountManagementItem.bind(this, item)} - /> - ); - })}} - <Route render={this._renderNotFoundMessage.bind(this)} /> - </Switch> + <div> + <Switch> + {_.map(accountManagementItems, item => { + return ( + <Route + key={item.pathName} + path={item.pathName} + render={this._renderAccountManagementItem.bind(this, item)} + /> + ); + })}} + <Route render={this._renderNotFoundMessage.bind(this)} /> + </Switch> + <PortalDisclaimerDialog + isOpen={this.state.isDisclaimerDialogOpen} + onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} + /> + </div> ); } private _renderAccountManagementItem(item: AccountManagementItem): React.ReactNode { @@ -587,6 +591,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { return this._blockchain.getTokenBalanceAndAllowanceAsync(userAddressIfExists, tokenAddress); }), ); + const priceByAddress = await this._getPriceByAddressAsync(tokenAddresses); for (let i = 0; i < tokenAddresses.length; i++) { // Order is preserved in Promise.all const [balance, allowance] = balancesAndAllowances[i]; @@ -595,6 +600,7 @@ export class Portal extends React.Component<PortalProps, PortalState> { balance, allowance, isLoaded: true, + price: priceByAddress[tokenAddress], }; } this.setState({ @@ -602,6 +608,34 @@ export class Portal extends React.Component<PortalProps, PortalState> { }); } + private async _getPriceByAddressAsync(tokenAddresses: string[]): Promise<ItemByAddress<BigNumber>> { + if (_.isEmpty(tokenAddresses)) { + return {}; + } + // for each input token address, search for the corresponding symbol in this.props.tokenByAddress, if it exists + // create a mapping from existing symbols -> address + const tokenAddressBySymbol: { [symbol: string]: string } = {}; + _.each(tokenAddresses, address => { + const tokenIfExists = _.get(this.props.tokenByAddress, address); + if (!_.isUndefined(tokenIfExists)) { + const symbol = tokenIfExists.symbol; + tokenAddressBySymbol[symbol] = address; + } + }); + const tokenSymbols = _.keys(tokenAddressBySymbol); + try { + const priceBySymbol = await backendClient.getPriceInfoAsync(tokenSymbols); + const priceByAddress = _.mapKeys(priceBySymbol, (value, symbol) => _.get(tokenAddressBySymbol, symbol)); + const result = _.mapValues(priceByAddress, price => { + const priceBigNumber = new BigNumber(price); + return priceBigNumber; + }); + return result; + } catch (err) { + return {}; + } + } + private async _refetchTokenStateAsync(tokenAddress: string): Promise<void> { await this._fetchBalancesAndAllowancesAsync([tokenAddress]); } diff --git a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx index ad6ab3de1..41febebe9 100644 --- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx +++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx @@ -7,8 +7,8 @@ import { TopTokens } from 'ts/components/relayer_index/relayer_top_tokens'; import { Container } from 'ts/components/ui/container'; import { Island } from 'ts/components/ui/island'; import { TokenIcon } from 'ts/components/ui/token_icon'; +import { colors } from 'ts/style/colors'; import { Token, WebsiteBackendRelayerInfo } from 'ts/types'; -import { colors } from 'ts/utils/colors'; export interface RelayerGridTileProps { relayerInfo: WebsiteBackendRelayerInfo; diff --git a/packages/website/ts/components/relayer_index/relayer_index.tsx b/packages/website/ts/components/relayer_index/relayer_index.tsx index 9ef6eaf59..683f7084b 100644 --- a/packages/website/ts/components/relayer_index/relayer_index.tsx +++ b/packages/website/ts/components/relayer_index/relayer_index.tsx @@ -6,9 +6,9 @@ import { GridList } from 'material-ui/GridList'; import * as React from 'react'; import { RelayerGridTile } from 'ts/components/relayer_index/relayer_grid_tile'; +import { colors } from 'ts/style/colors'; import { ScreenWidths, WebsiteBackendRelayerInfo } from 'ts/types'; import { backendClient } from 'ts/utils/backend_client'; -import { colors } from 'ts/utils/colors'; export interface RelayerIndexProps { networkId: number; diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 7a0742bbe..2bc065b0f 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -77,11 +77,11 @@ interface TokenBalancesProps { interface TokenBalancesState { errorType: BalanceErrs; + trackedTokenStateByAddress: TokenStateByAddress; isBalanceSpinnerVisible: boolean; isZRXSpinnerVisible: boolean; isTokenPickerOpen: boolean; isAddingToken: boolean; - trackedTokenStateByAddress: TokenStateByAddress; } export class TokenBalances extends React.Component<TokenBalancesProps, TokenBalancesState> { diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx index 8a337119a..dba08f85c 100644 --- a/packages/website/ts/components/top_bar/provider_display.tsx +++ b/packages/website/ts/components/top_bar/provider_display.tsx @@ -9,10 +9,10 @@ import { ProviderPicker } from 'ts/components/top_bar/provider_picker'; import { DropDown } from 'ts/components/ui/drop_down'; import { Identicon } from 'ts/components/ui/identicon'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { colors } from 'ts/style/colors'; +import { zIndex } from 'ts/style/z_index'; import { ProviderType } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; -import { zIndex } from 'ts/utils/style'; import { utils } from 'ts/utils/utils'; const ROOT_HEIGHT = 24; diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index e2d791ae3..606fd845a 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -16,9 +16,9 @@ import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item'; import { DropDown } from 'ts/components/ui/drop_down'; import { Identicon } from 'ts/components/ui/identicon'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { zIndex } from 'ts/style/z_index'; import { Deco, Key, ProviderType, WebsiteLegacyPaths, WebsitePaths } from 'ts/types'; import { constants } from 'ts/utils/constants'; -import { zIndex } from 'ts/utils/style'; import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/components/ui/button.tsx b/packages/website/ts/components/ui/button.tsx new file mode 100644 index 000000000..4c7d59839 --- /dev/null +++ b/packages/website/ts/components/ui/button.tsx @@ -0,0 +1,79 @@ +import { colors } from '@0xproject/react-shared'; +import { darken } from 'polished'; +import * as React from 'react'; +import { styled } from 'ts/style/theme'; + +export interface ButtonProps { + className?: string; + fontSize?: string; + fontColor?: string; + backgroundColor?: string; + borderColor?: string; + width?: string; + type?: string; + onClick?: (event: React.MouseEvent<HTMLElement>) => void; +} + +const PlainButton: React.StatelessComponent<ButtonProps> = ({ children, onClick, type, className }) => ( + <button type={type} className={className} onClick={onClick}> + {children} + </button> +); + +export const Button = styled(PlainButton)` + cursor: pointer; + font-size: ${props => props.fontSize}; + color: ${props => props.fontColor}; + padding: 0.8em 2.2em; + border-radius: 6px; + box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25); + font-weight: 500; + font-family: 'Roboto'; + width: ${props => props.width}; + background-color: ${props => props.backgroundColor}; + border: ${props => (props.borderColor ? `1px solid ${props.borderColor}` : 'none')}; + &:hover { + background-color: ${props => darken(0.1, props.backgroundColor)}; + } + &:active { + background-color: ${props => darken(0.2, props.backgroundColor)}; + } +`; + +Button.defaultProps = { + fontSize: '12px', + backgroundColor: colors.white, + width: 'auto', +}; + +Button.displayName = 'Button'; + +type CallToActionType = 'light' | 'dark'; + +export interface CallToActionProps { + type?: CallToActionType; + fontSize?: string; + width?: string; +} + +export const CallToAction: React.StatelessComponent<CallToActionProps> = ({ children, type, fontSize, width }) => { + const isLight = type === 'light'; + const backgroundColor = isLight ? colors.white : colors.heroGrey; + const fontColor = isLight ? colors.heroGrey : colors.white; + const borderColor = isLight ? undefined : colors.white; + return ( + <Button + fontSize={fontSize} + backgroundColor={backgroundColor} + fontColor={fontColor} + width={width} + borderColor={borderColor} + > + {children} + </Button> + ); +}; + +CallToAction.defaultProps = { + type: 'dark', +}; diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx index d577447b0..c6a78e181 100644 --- a/packages/website/ts/components/ui/container.tsx +++ b/packages/website/ts/components/ui/container.tsx @@ -11,13 +11,20 @@ export interface ContainerProps { paddingBottom?: StringOrNum; paddingRight?: StringOrNum; paddingLeft?: StringOrNum; + backgroundColor?: string; + borderRadius?: StringOrNum; maxWidth?: StringOrNum; - children?: React.ReactNode; + isHidden?: boolean; + className?: string; } -export const Container: React.StatelessComponent<ContainerProps> = (props: ContainerProps) => { - const { children, ...style } = props; - return <div style={style}>{children}</div>; +export const Container: React.StatelessComponent<ContainerProps> = ({ children, className, isHidden, ...style }) => { + const visibility = isHidden ? 'hidden' : undefined; + return ( + <div style={{ ...style, visibility }} className={className}> + {children} + </div> + ); }; Container.displayName = 'Container'; diff --git a/packages/website/ts/components/ui/input.tsx b/packages/website/ts/components/ui/input.tsx new file mode 100644 index 000000000..e01a71a53 --- /dev/null +++ b/packages/website/ts/components/ui/input.tsx @@ -0,0 +1,43 @@ +import { colors } from '@0xproject/react-shared'; +import * as React from 'react'; +import { styled } from 'ts/style/theme'; + +export interface InputProps { + className?: string; + value?: string; + width?: string; + fontSize?: string; + fontColor?: string; + placeholderColor?: string; + placeholder?: string; + backgroundColor?: string; + onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void; +} + +const PlainInput: React.StatelessComponent<InputProps> = ({ value, className, placeholder, onChange }) => ( + <input className={className} value={value} onChange={onChange} placeholder={placeholder} /> +); + +export const Input = styled(PlainInput)` + font-size: ${props => props.fontSize}; + width: ${props => props.width}; + padding: 0.8em 1.2em; + border-radius: 3px; + font-family: 'Roboto Mono'; + color: ${props => props.fontColor}; + border: none; + background-color: ${props => props.backgroundColor}; + &::placeholder { + color: ${props => props.placeholderColor}; + } +`; + +Input.defaultProps = { + width: 'auto', + backgroundColor: colors.white, + fontColor: colors.darkestGrey, + placeholderColor: colors.darkGrey, + fontSize: '12px', +}; + +Input.displayName = 'Input'; diff --git a/packages/website/ts/components/ui/island.tsx b/packages/website/ts/components/ui/island.tsx index f5480c9c9..802a7830a 100644 --- a/packages/website/ts/components/ui/island.tsx +++ b/packages/website/ts/components/ui/island.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; +import { colors } from 'ts/style/colors'; import { Styleable } from 'ts/types'; -import { colors } from 'ts/utils/colors'; export interface IslandProps { style?: React.CSSProperties; diff --git a/packages/website/ts/components/ui/overlay.tsx b/packages/website/ts/components/ui/overlay.tsx index bb2ed8e59..acca8194f 100644 --- a/packages/website/ts/components/ui/overlay.tsx +++ b/packages/website/ts/components/ui/overlay.tsx @@ -2,7 +2,7 @@ import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; -import { zIndex } from 'ts/utils/style'; +import { zIndex } from 'ts/style/z_index'; export interface OverlayProps { children?: React.ReactNode; diff --git a/packages/website/ts/components/ui/text.tsx b/packages/website/ts/components/ui/text.tsx new file mode 100644 index 000000000..99bf89966 --- /dev/null +++ b/packages/website/ts/components/ui/text.tsx @@ -0,0 +1,41 @@ +import { colors } from '@0xproject/react-shared'; +import * as React from 'react'; +import { styled } from 'ts/style/theme'; +import { Deco, Key } from 'ts/types'; +import { Translate } from 'ts/utils/translate'; + +export type TextTag = 'p' | 'div' | 'span' | 'label'; + +export interface TextProps { + className?: string; + Tag?: TextTag; + fontSize?: string; + fontFamily?: string; + fontColor?: string; + lineHeight?: string; + center?: boolean; + fontWeight?: number; +} + +const PlainText: React.StatelessComponent<TextProps> = ({ children, className, Tag }) => ( + <Tag className={className}>{children}</Tag> +); + +export const Text = styled(PlainText)` + font-family: ${props => props.fontFamily}; + font-weight: ${props => props.fontWeight}; + font-size: ${props => props.fontSize}; + ${props => (props.lineHeight ? `line-height: ${props.lineHeight}` : '')}; + ${props => (props.center ? 'text-align: center' : '')}; + color: ${props => props.fontColor}; +`; + +Text.defaultProps = { + fontFamily: 'Roboto', + fontWeight: 400, + fontColor: colors.white, + fontSize: '14px', + Tag: 'div', +}; + +Text.displayName = 'Text'; diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index 7059ca350..37233930e 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -32,10 +32,11 @@ import { TokenIcon } from 'ts/components/ui/token_icon'; import { WalletDisconnectedItem } from 'ts/components/wallet/wallet_disconnected_item'; import { WrapEtherItem } from 'ts/components/wallet/wrap_ether_item'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { colors } from 'ts/style/colors'; +import { zIndex } from 'ts/style/z_index'; import { BalanceErrs, BlockchainErrs, - ItemByAddress, ProviderType, ScreenWidths, Side, @@ -45,10 +46,7 @@ import { TokenStateByAddress, WebsitePaths, } from 'ts/types'; -import { backendClient } from 'ts/utils/backend_client'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; -import { zIndex } from 'ts/utils/style'; import { utils } from 'ts/utils/utils'; import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles'; @@ -523,34 +521,6 @@ export class Wallet extends React.Component<WalletProps, WalletState> { }); return trackedTokenStateByAddress; } - - private async _getPriceByAddressAsync(tokenAddresses: string[]): Promise<ItemByAddress<BigNumber>> { - if (_.isEmpty(tokenAddresses)) { - return {}; - } - // for each input token address, search for the corresponding symbol in this.props.tokenByAddress, if it exists - // create a mapping from existing symbols -> address - const tokenAddressBySymbol: { [symbol: string]: string } = {}; - _.each(tokenAddresses, address => { - const tokenIfExists = _.get(this.props.tokenByAddress, address); - if (!_.isUndefined(tokenIfExists)) { - const symbol = tokenIfExists.symbol; - tokenAddressBySymbol[symbol] = address; - } - }); - const tokenSymbols = _.keys(tokenAddressBySymbol); - try { - const priceBySymbol = await backendClient.getPriceInfoAsync(tokenSymbols); - const priceByAddress = _.mapKeys(priceBySymbol, (value, symbol) => _.get(tokenAddressBySymbol, symbol)); - const result = _.mapValues(priceByAddress, price => { - const priceBigNumber = new BigNumber(price); - return priceBigNumber; - }); - return result; - } catch (err) { - return {}; - } - } private _openWrappedEtherActionRow(wrappedEtherDirection: Side): void { this.setState({ wrappedEtherDirection, diff --git a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx index 17fd8a19e..e3b88dc54 100644 --- a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx +++ b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx @@ -3,8 +3,8 @@ import FlatButton from 'material-ui/FlatButton'; import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; import * as React from 'react'; +import { colors } from 'ts/style/colors'; import { ProviderType } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/components/wallet/wrap_ether_item.tsx b/packages/website/ts/components/wallet/wrap_ether_item.tsx index 1dfcffadf..50b64d05c 100644 --- a/packages/website/ts/components/wallet/wrap_ether_item.tsx +++ b/packages/website/ts/components/wallet/wrap_ether_item.tsx @@ -10,8 +10,8 @@ import { Blockchain } from 'ts/blockchain'; import { EthAmountInput } from 'ts/components/inputs/eth_amount_input'; import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { colors } from 'ts/style/colors'; import { BlockchainCallErrs, Side, Token } from 'ts/types'; -import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index 9d5e54c22..ed6ec4ec6 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -1,11 +1,13 @@ import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; -import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; import DocumentTitle = require('react-document-title'); import { Link } from 'react-router-dom'; import { Footer } from 'ts/components/footer'; +import { SubscribeForm } from 'ts/components/forms/subscribe_form'; import { TopBar } from 'ts/components/top_bar/top_bar'; +import { CallToAction } from 'ts/components/ui/button'; +import { Container } from 'ts/components/ui/container'; import { Dispatcher } from 'ts/redux/dispatcher'; import { Deco, Key, Language, ScreenWidths, WebsitePaths } from 'ts/types'; import { constants } from 'ts/utils/constants'; @@ -236,7 +238,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { <div className="clearfix py4" style={{ backgroundColor: colors.heroGrey }}> <div className="mx-auto max-width-4 clearfix"> {this._renderWhatsNew()} - <div className="lg-pt4 md-pt4 sm-pt2 lg-pb4 md-pb4 lg-my4 md-my4 sm-mt2 sm-mb4 clearfix"> + <div className="lg-pt4 md-pt4 sm-pt2 lg-pb4 md-pb4 lg-mt4 md-mt4 sm-mt2 sm-mb4 clearfix"> <div className="col lg-col-5 md-col-5 col-12 sm-center"> <img src="/images/landing/hero_chip_image.png" height={isSmallScreen ? 300 : 395} /> </div> @@ -268,40 +270,31 @@ export class Landing extends React.Component<LandingProps, LandingState> { > {this.props.translate.get(Key.TopTagline)} </div> - <div className="pt3 clearfix sm-mx-auto" style={{ maxWidth: 389 }}> - <div className="lg-pr2 md-pr2 col col-6 sm-center"> + <Container className="pt3 clearfix sm-mx-auto" maxWidth="390px"> + <div className="lg-pr2 md-pr2 lg-col lg-col-6 sm-center sm-col sm-col-12 mb2"> <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <RaisedButton - style={{ borderRadius: 6, minWidth: 157.36 }} - buttonStyle={{ borderRadius: 6 }} - labelStyle={buttonLabelStyle} - label={this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} - onClick={_.noop} - /> + <CallToAction width="175px" type="light"> + {this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} + </CallToAction> </Link> </div> - <div className="col col-6 sm-center"> + <div className="lg-col lg-col-6 sm-center sm-col sm-col-12"> <a href={constants.URL_ZEROEX_CHAT} target="_blank" className="text-decoration-none" > - <RaisedButton - style={{ borderRadius: 6, minWidth: 150 }} - buttonStyle={lightButtonStyle} - labelColor="white" - backgroundColor={colors.heroGrey} - labelStyle={buttonLabelStyle} - label={this.props.translate.get(Key.CommunityCallToAction, Deco.Cap)} - onClick={_.noop} - /> + <CallToAction width="175px"> + {this.props.translate.get(Key.CommunityCallToAction, Deco.Cap)} + </CallToAction> </a> </div> - </div> + </Container> </div> </div> </div> </div> + {this.props.translate.getLanguage() === Language.English && <SubscribeForm />} </div> ); } @@ -785,15 +778,9 @@ export class Landing extends React.Component<LandingProps, LandingState> { </div> <div className="sm-center sm-pt2 lg-table-cell md-table-cell"> <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <RaisedButton - style={{ borderRadius: 6, minWidth: 150 }} - buttonStyle={lightButtonStyle} - labelColor={colors.white} - backgroundColor={colors.heroGrey} - labelStyle={buttonLabelStyle} - label={this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} - onClick={_.noop} - /> + <CallToAction fontSize="15px"> + {this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} + </CallToAction> </Link> </div> </div> diff --git a/packages/website/ts/utils/colors.ts b/packages/website/ts/style/colors.ts index 5ffdd6ba7..5ffdd6ba7 100644 --- a/packages/website/ts/utils/colors.ts +++ b/packages/website/ts/style/colors.ts diff --git a/packages/website/ts/style/theme.ts b/packages/website/ts/style/theme.ts new file mode 100644 index 000000000..9e447e7ee --- /dev/null +++ b/packages/website/ts/style/theme.ts @@ -0,0 +1,15 @@ +import * as styledComponents from 'styled-components'; + +const { + default: styled, + css, + injectGlobal, + keyframes, + ThemeProvider, +} = styledComponents as styledComponents.ThemedStyledComponentsModule<IThemeInterface>; + +export interface IThemeInterface {} + +export const theme = {}; + +export { styled, css, injectGlobal, keyframes, ThemeProvider }; diff --git a/packages/website/ts/utils/style.ts b/packages/website/ts/style/z_index.ts index 0411cdd91..0411cdd91 100644 --- a/packages/website/ts/utils/style.ts +++ b/packages/website/ts/style/z_index.ts diff --git a/packages/website/ts/utils/backend_client.ts b/packages/website/ts/utils/backend_client.ts index c440b1604..6b16aea6b 100644 --- a/packages/website/ts/utils/backend_client.ts +++ b/packages/website/ts/utils/backend_client.ts @@ -8,6 +8,7 @@ const ETH_GAS_STATION_ENDPOINT = '/eth_gas_station'; const PRICES_ENDPOINT = '/prices'; const RELAYERS_ENDPOINT = '/relayers'; const WIKI_ENDPOINT = '/wiki'; +const SUBSCRIBE_SUBSTACK_NEWSLETTER_ENDPOINT = '/newsletter_subscriber/substack'; export const backendClient = { async getGasInfoAsync(): Promise<WebsiteBackendGasInfo> { @@ -33,4 +34,11 @@ export const backendClient = { const result = await fetchUtils.requestAsync(utils.getBackendBaseUrl(), WIKI_ENDPOINT); return result; }, + async subscribeToNewsletterAsync(email: string): Promise<Response> { + const result = await fetchUtils.postAsync(utils.getBackendBaseUrl(), SUBSCRIBE_SUBSTACK_NEWSLETTER_ENDPOINT, { + email, + referrer: window.location.href, + }); + return result; + }, }; diff --git a/packages/website/ts/utils/fetch_utils.ts b/packages/website/ts/utils/fetch_utils.ts index d2e902db5..513f7e479 100644 --- a/packages/website/ts/utils/fetch_utils.ts +++ b/packages/website/ts/utils/fetch_utils.ts @@ -4,22 +4,38 @@ import * as queryString from 'query-string'; import { errorReporter } from 'ts/utils/error_reporter'; +const logErrorIfPresent = (response: Response, requestedURL: string) => { + if (response.status !== 200) { + const errorText = `Error requesting url: ${requestedURL}, ${response.status}: ${response.statusText}`; + logUtils.log(errorText); + const error = Error(errorText); + // tslint:disable-next-line:no-floating-promises + errorReporter.reportAsync(error); + throw error; + } +}; + export const fetchUtils = { async requestAsync(baseUrl: string, path: string, queryParams?: object): Promise<any> { const query = queryStringFromQueryParams(queryParams); const url = `${baseUrl}${path}${query}`; const response = await fetch(url); - if (response.status !== 200) { - const errorText = `Error requesting url: ${url}, ${response.status}: ${response.statusText}`; - logUtils.log(errorText); - const error = Error(errorText); - // tslint:disable-next-line:no-floating-promises - errorReporter.reportAsync(error); - throw error; - } + logErrorIfPresent(response, url); const result = await response.json(); return result; }, + async postAsync(baseUrl: string, path: string, body: object): Promise<Response> { + const url = `${baseUrl}${path}`; + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + logErrorIfPresent(response, url); + return response; + }, }; function queryStringFromQueryParams(queryParams?: object): string { diff --git a/packages/website/ts/utils/wallet_item_styles.ts b/packages/website/ts/utils/wallet_item_styles.ts index 6b038efd2..9d6033d74 100644 --- a/packages/website/ts/utils/wallet_item_styles.ts +++ b/packages/website/ts/utils/wallet_item_styles.ts @@ -1,6 +1,6 @@ import { Styles } from '@0xproject/react-shared'; -import { colors } from 'ts/utils/colors'; +import { colors } from 'ts/style/colors'; export const styles: Styles = { focusedItem: { |