diff options
author | fragosti <francesco.agosti93@gmail.com> | 2018-06-02 08:25:50 +0800 |
---|---|---|
committer | fragosti <francesco.agosti93@gmail.com> | 2018-06-02 08:25:50 +0800 |
commit | 073a96cf63a8b2e5639d15133d09545f7bde1388 (patch) | |
tree | 8c2250740101b6b26e3d48745e06bca46e59260d | |
parent | 817d9b0d3e1becdb2e8dbf51caa09edab8d14ab0 (diff) | |
download | dexon-sol-tools-073a96cf63a8b2e5639d15133d09545f7bde1388.tar.gz dexon-sol-tools-073a96cf63a8b2e5639d15133d09545f7bde1388.tar.zst dexon-sol-tools-073a96cf63a8b2e5639d15133d09545f7bde1388.zip |
Implement subscription form
-rw-r--r-- | packages/website/ts/components/forms/subscribe_form.tsx | 121 | ||||
-rw-r--r-- | packages/website/ts/components/ui/button.tsx | 79 | ||||
-rw-r--r-- | packages/website/ts/components/ui/container.tsx | 15 | ||||
-rw-r--r-- | packages/website/ts/components/ui/input.tsx | 43 | ||||
-rw-r--r-- | packages/website/ts/components/ui/text.tsx | 56 | ||||
-rw-r--r-- | packages/website/ts/pages/landing/landing.tsx | 38 | ||||
-rw-r--r-- | packages/website/ts/utils/backend_client.ts | 4 |
7 files changed, 288 insertions, 68 deletions
diff --git a/packages/website/ts/components/forms/subscribe_form.tsx b/packages/website/ts/components/forms/subscribe_form.tsx index 3a6d0901f..99686efce 100644 --- a/packages/website/ts/components/forms/subscribe_form.tsx +++ b/packages/website/ts/components/forms/subscribe_form.tsx @@ -1,7 +1,12 @@ import { colors } from '@0xproject/react-shared'; import * as React from 'react'; -import RaisedButton from 'material-ui/RaisedButton'; +import { Button } from 'ts/components/ui/button'; +import { Input } from 'ts/components/ui/input'; +import { Text } from 'ts/components/ui/text'; +import { logUtils } from '@0xproject/utils'; +import { Container } from 'ts/components/ui/container'; +import { styled } from 'ts/style/theme'; import { backendClient } from 'ts/utils/backend_client'; export interface SubscribeFormProps {} @@ -15,54 +20,102 @@ export enum SubscribeFormStatus { export interface SubscribeFormState { emailText: string; + lastSubmittedEmail: string; status: SubscribeFormStatus; } export class SubscribeForm extends React.Component<SubscribeFormProps, SubscribeFormState> { public state = { emailText: '', + lastSubmittedEmail: '', status: SubscribeFormStatus.None, }; public render(): React.ReactNode { + const formFontSize = '15px'; return ( - <div> - Subscribe to our newsletter for 0x relayer and dApp updates - <div> - <input value={this.state.emailText} onChange={this._handleEmailInputChange.bind(this)} /> - <RaisedButton - labelStyle={{ - textTransform: 'none', - fontSize: 15, - fontWeight: 400, - }} - buttonStyle={{ - borderRadius: 6, - }} - style={{ - boxShadow: '0px 0px 4px rgba(0, 0, 0, 0.25)', - borderRadius: 6, - height: 48, - width: 120, - }} - labelColor="white" - backgroundColor="#252525" - onClick={this._handleSubscribeClickAsync.bind(this)} - label="Subscribe" - /> - </div> - </div> + <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={formFontSize} + backgroundColor="#343333" + width="275px" + onChange={this._handleEmailInputChange.bind(this)} + /> + </Container> + <Container marginLeft="15px" marginTop="15px"> + <Button + type="submit" + backgroundColor="#252525" + fontColor={colors.white} + fontSize={formFontSize} + > + 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; + } + return ( + <Container isHidden={!message} marginTop="30px"> + <Text center={true} fontFamily="Roboto Mono" fontColor={colors.grey}> + {message || 'spacer text'} + </Text> + </Container> + ); + } + private _handleEmailInputChange(event: React.ChangeEvent<HTMLInputElement>): void { this.setState({ emailText: event.target.value }); } - private async _handleSubscribeClickAsync(): Promise<void> { - this._setStatus(SubscribeFormStatus.Loading); - const isSuccess = await backendClient.subscribeToNewsletterAsync(this.state.emailText); - const status = isSuccess ? SubscribeFormStatus.Success : SubscribeFormStatus.Error; - this._setStatus(status); + private async _handleFormSubmitAsync(event: React.FormEvent<HTMLInputElement>): Promise<void> { + event.preventDefault(); + if (!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._setStatus(status); + } catch (error) { + logUtils.log(error); + this._setStatus(SubscribeFormStatus.Error); + } finally { + this.setState({ emailText: '' }); + } } - private _setStatus(status: SubscribeFormStatus): void { - this.setState({ status }); + private _setStatus(status: SubscribeFormStatus, then?: () => void): void { + this.setState({ status }, then); } } diff --git a/packages/website/ts/components/ui/button.tsx b/packages/website/ts/components/ui/button.tsx new file mode 100644 index 000000000..e6e31374f --- /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 CTAType = 'light' | 'dark'; + +export interface CTAProps { + type?: CTAType; + fontSize?: string; + width?: string; +} + +export const CTA: React.StatelessComponent<CTAProps> = ({ 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> + ); +}; + +CTA.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..75a453eae --- /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.placeholder}; + } +`; + +Input.defaultProps = { + width: 'auto', + backgroundColor: colors.white, + fontColor: colors.darkestGrey, + placeholderColor: colors.grey500, + fontSize: '12px', +}; + +Input.displayName = 'Input'; diff --git a/packages/website/ts/components/ui/text.tsx b/packages/website/ts/components/ui/text.tsx new file mode 100644 index 000000000..259365618 --- /dev/null +++ b/packages/website/ts/components/ui/text.tsx @@ -0,0 +1,56 @@ +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'; + +interface TranslatedProps { + children: Key; + translate: Translate; + deco?: Deco; +} + +export type TranslatedTextProps = TextProps & TranslatedProps; + +export const TranslatedText: React.StatelessComponent<TranslatedTextProps> = ({ + translate, + children, + deco, + ...textProps +}) => <Text {...textProps}>{translate.get(children, deco)}</Text>; diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index 016c62a30..0911bb2cf 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -1,7 +1,7 @@ import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; -import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; +import { CTA } from 'ts/components/ui/button'; import DocumentTitle = require('react-document-title'); import { Link } from 'react-router-dom'; import { Footer } from 'ts/components/footer'; @@ -237,7 +237,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> @@ -272,13 +272,9 @@ export class Landing extends React.Component<LandingProps, LandingState> { <div className="pt3 clearfix sm-mx-auto" style={{ maxWidth: 389 }}> <div className="lg-pr2 md-pr2 col col-6 sm-center"> <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} - /> + <CTA width="175px" type="light"> + {this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} + </CTA> </Link> </div> <div className="col col-6 sm-center"> @@ -287,23 +283,17 @@ export class Landing extends React.Component<LandingProps, LandingState> { 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} - /> + <CTA width="175px"> + {this.props.translate.get(Key.CommunityCallToAction, Deco.Cap)} + </CTA> </a> </div> </div> - <SubscribeForm /> </div> </div> </div> </div> + <SubscribeForm /> </div> ); } @@ -784,15 +774,7 @@ 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} - /> + <CTA fontSize="15px">{this.props.translate.get(Key.BuildCallToAction, Deco.Cap)}</CTA> </Link> </div> </div> diff --git a/packages/website/ts/utils/backend_client.ts b/packages/website/ts/utils/backend_client.ts index fb7c21c59..6b16aea6b 100644 --- a/packages/website/ts/utils/backend_client.ts +++ b/packages/website/ts/utils/backend_client.ts @@ -34,11 +34,11 @@ export const backendClient = { const result = await fetchUtils.requestAsync(utils.getBackendBaseUrl(), WIKI_ENDPOINT); return result; }, - async subscribeToNewsletterAsync(email: string): Promise<boolean> { + async subscribeToNewsletterAsync(email: string): Promise<Response> { const result = await fetchUtils.postAsync(utils.getBackendBaseUrl(), SUBSCRIBE_SUBSTACK_NEWSLETTER_ENDPOINT, { email, referrer: window.location.href, }); - return result.status === 200; + return result; }, }; |