2019-08-02 12:33:34 -07:00

635 lines
23 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as _ from 'lodash';
import * as React from 'react';
import styled from 'styled-components';
import { colors } from 'ts/style/colors';
import { DialogContent, DialogOverlay } from '@reach/dialog';
import '@reach/dialog/styles.css';
import { Button } from 'ts/components/button';
import { Icon } from 'ts/components/icon';
import { CheckBoxInput, Input, InputWidth, OptionSelector } from 'ts/components/modals/input';
import { Heading, Paragraph } from 'ts/components/text';
import { GlobalStyle } from 'ts/constants/globalStyle';
import { utils } from 'ts/utils/utils';
export enum ModalContactType {
General = 'GENERAL',
MarketMaker = 'MARKET_MAKER',
Credits = 'CREDITS',
Explore = 'EXPLORE',
}
interface OptionMetadata {
label: string;
name: string;
}
const CREDIT_SERVICES_OPTIONS: OptionMetadata[] = [
{
label: 'AWS',
name: 'aws',
},
{
label: 'Alchemy',
name: 'alchemy',
},
{
label: 'Digital Ocean',
name: 'digital_ocean',
},
];
interface Props {
theme?: GlobalStyle;
isOpen?: boolean;
onDismiss?: () => void;
modalContactType: ModalContactType;
}
interface FormProps {
isSuccessful?: boolean;
isSubmitting?: boolean;
}
interface ErrorResponseProps {
param: string;
location: string;
msg: string;
}
interface ErrorResponse {
errors: ErrorResponseProps[];
}
interface ErrorProps {
[key: string]: string;
}
export class ModalContact extends React.Component<Props> {
public static defaultProps = {
modalContactType: ModalContactType.General,
};
public state = {
creditLeadsServices: [] as string[],
exploreSupportInstant: false,
isSubmitting: false,
isSuccessful: false,
errors: {},
};
// shared fields
public nameRef: React.RefObject<HTMLInputElement> = React.createRef();
public emailRef: React.RefObject<HTMLInputElement> = React.createRef();
public companyProjectRef: React.RefObject<HTMLInputElement> = React.createRef();
public commentsRef: React.RefObject<HTMLInputElement> = React.createRef();
// general lead fields
public linkRef: React.RefObject<HTMLInputElement> = React.createRef();
// market maker lead fields
public countryRef: React.RefObject<HTMLInputElement> = React.createRef();
public fundSizeRef: React.RefObject<HTMLInputElement> = React.createRef();
// Explore lead fields
public themeColorRef: React.RefObject<HTMLInputElement> = React.createRef();
public constructor(props: Props) {
super(props);
}
public render(): React.ReactNode {
const { isOpen, onDismiss } = this.props;
const { isSuccessful, errors } = this.state;
return (
<>
<DialogOverlay
style={{ background: 'rgba(0, 0, 0, 0.75)', zIndex: 30 }}
isOpen={isOpen}
onDismiss={onDismiss}
>
<StyledDialogContent>
<Form onSubmit={this._onSubmitAsync.bind(this)} isSuccessful={isSuccessful}>
<Heading color={colors.textDarkPrimary} size={34} asElement="h2">
Contact the 0x Core Team
</Heading>
{this._renderFormContent(errors)}
<ButtonRow>
<Button
color="#5C5C5C"
isNoBorder={true}
isTransparent={true}
type="button"
onClick={this.props.onDismiss}
>
Back
</Button>
<Button color={colors.white}>Submit</Button>
</ButtonRow>
</Form>
<Confirmation isSuccessful={isSuccessful}>
<Icon name="rocketship" size="large" margin={[0, 0, 'default', 0]} />
<Heading color={colors.textDarkPrimary} size={34} asElement="h2">
Thanks for contacting us.
</Heading>
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
We'll get back to you soon. If you need quick support in the meantime, reach out to the
0x team on Discord.
</Paragraph>
<Button color={colors.white} onClick={this.props.onDismiss}>
Done
</Button>
</Confirmation>
</StyledDialogContent>
</DialogOverlay>
</>
);
}
public _renderFormContent(errors: ErrorProps): React.ReactNode {
switch (this.props.modalContactType) {
case ModalContactType.MarketMaker:
return this._renderMarketMakerFormContent(errors);
case ModalContactType.Credits:
return this._renderCreditsFormContent(errors);
case ModalContactType.Explore:
return this._renderExploreFormContent(errors);
case ModalContactType.General:
default:
return this._renderGeneralFormContent(errors);
}
}
private _renderMarketMakerFormContent(errors: ErrorProps): React.ReactNode {
return (
<>
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
If youre considering market making on 0x, were happy to answer your questions. Fill out the form
so we can connect you with the right person to help you get started.
</Paragraph>
<InputRow>
<Input
name="name"
label="Your name"
type="text"
width={InputWidth.Half}
ref={this.nameRef}
required={true}
errors={errors}
/>
<Input
name="email"
label="Your email"
type="email"
ref={this.emailRef}
required={true}
errors={errors}
width={InputWidth.Half}
/>
</InputRow>
<InputRow>
<Input
name="country"
label="Country of Location"
type="text"
ref={this.countryRef}
required={true}
errors={errors}
/>
</InputRow>
<InputRow>
<Input
name="fundSize"
label="Fund Size"
type="text"
ref={this.fundSizeRef}
required={true}
errors={errors}
/>
</InputRow>
<InputRow>
<Input
name="companyOrProject"
label="Name of your project / company"
type="text"
ref={this.companyProjectRef}
required={false}
errors={errors}
/>
</InputRow>
<InputRow>
<Input
name="comments"
label="What is prompting you to reach out?"
type="textarea"
ref={this.commentsRef}
required={false}
errors={errors}
/>
</InputRow>
</>
);
}
private _renderExploreFormContent(errors: ErrorProps): React.ReactNode {
return (
<>
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
If youre working on an awesome 0x project, we would love to share it on our explore page. Fill out
the form so we can connect you with the right person to help you get started.
</Paragraph>
<InputRow>
<Input
name="name"
label="Your name"
type="text"
width={InputWidth.Half}
ref={this.nameRef}
required={true}
errors={errors}
/>
<Input
name="email"
label="Your email"
type="email"
ref={this.emailRef}
required={true}
errors={errors}
width={InputWidth.Half}
/>
</InputRow>
<InputRow>
<Input
name="companyOrProject"
label="Name of your project / company"
type="text"
ref={this.companyProjectRef}
required={true}
errors={errors}
/>
</InputRow>
<InputRow>
<Input
name="comments"
label="Description of your project / company"
type="textarea"
ref={this.commentsRef}
required={true}
errors={errors}
/>
</InputRow>
<InputRow>
<Input
name="link"
label="Project / Company link"
type="text"
ref={this.linkRef}
required={true}
errors={errors}
/>
</InputRow>
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
Details for 0x Explore page:
</Paragraph>
<InputRow>
<Input
name="color"
label="Theme Color (in hex)"
type="text"
ref={this.themeColorRef}
required={true}
errors={errors}
/>
</InputRow>
<InputRow>
<OptionSelector
isFlex={true}
name="instant"
label="Does your project support instant?"
errors={errors}
>
{[{ label: 'Yes', name: 'yes' }, { label: 'No', name: 'no' }].map(
(metadata: OptionMetadata) => {
return (
<CheckBoxInput
onClick={this._handleCheckBoxInput.bind(this, metadata.name)}
key={`checkbox-${metadata.name}`}
isSelected={
(this.state.exploreSupportInstant && metadata.name === 'yes') ||
(!this.state.exploreSupportInstant && metadata.name === 'no')
}
label={metadata.label}
/>
);
},
)}
</OptionSelector>
</InputRow>
</>
);
}
private _renderCreditsFormContent(errors: ErrorProps): React.ReactNode {
return (
<>
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
If you are building on top of 0x full time, please fill out this form and our Relayer Success
Manager will be in touch.
</Paragraph>
<InputRow>
<Input
name="name"
label="Your name"
type="text"
width={InputWidth.Half}
ref={this.nameRef}
required={true}
errors={errors}
/>
<Input
name="email"
label="Your email"
type="email"
ref={this.emailRef}
required={true}
errors={errors}
width={InputWidth.Half}
/>
</InputRow>
<InputRow>
<Input
name="companyOrProject"
label="Name of your project / company"
type="text"
ref={this.companyProjectRef}
required={false}
errors={errors}
/>
</InputRow>
<InputRow>
<Input
name="comments"
label="Brief project description"
type="textarea"
ref={this.commentsRef}
required={false}
errors={errors}
/>
</InputRow>
<InputRow>
<OptionSelector
isFlex={true}
name="services"
label="Which credits are you interested in?"
errors={errors}
>
{CREDIT_SERVICES_OPTIONS.map((metadata: OptionMetadata) => {
return (
<CheckBoxInput
onClick={this._handleCheckBoxInput.bind(this, metadata.name)}
key={`checkbox-${metadata.name}`}
isSelected={_.includes(this.state.creditLeadsServices, metadata.name)}
label={metadata.label}
/>
);
})}
</OptionSelector>
</InputRow>
</>
);
}
private _handleCheckBoxInput(checkBoxName: string): void {
if (this.props.modalContactType === ModalContactType.Credits) {
const newCreditLeadsServices = _.includes(this.state.creditLeadsServices, checkBoxName)
? _.pull(this.state.creditLeadsServices, checkBoxName)
: _.concat(this.state.creditLeadsServices, checkBoxName);
this.setState({ creditLeadsServices: newCreditLeadsServices });
} else if (this.props.modalContactType === ModalContactType.Explore) {
this.setState({ exploreSupportInstant: checkBoxName === 'no' ? false : true });
}
}
private _renderGeneralFormContent(errors: ErrorProps): React.ReactNode {
return (
<>
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
If you're considering building on 0x, we're happy to answer your questions. Fill out the form so we
can connect you with the right person to help you get started.
</Paragraph>
<InputRow>
<Input
name="name"
label="Your name"
type="text"
width={InputWidth.Half}
ref={this.nameRef}
required={true}
errors={errors}
/>
<Input
name="email"
label="Your email"
type="email"
ref={this.emailRef}
required={true}
errors={errors}
width={InputWidth.Half}
/>
</InputRow>
<InputRow>
<Input
name="companyOrProject"
label="Name of your project / company"
type="text"
ref={this.companyProjectRef}
required={true}
errors={errors}
/>
</InputRow>
<InputRow>
<Input
name="link"
label="Do you have any documentation or a website?"
type="text"
ref={this.linkRef}
errors={errors}
/>
</InputRow>
<InputRow>
<Input
name="comments"
label="Anything else?"
type="textarea"
ref={this.commentsRef}
errors={errors}
/>
</InputRow>
</>
);
}
private async _onSubmitAsync(e: React.FormEvent): Promise<void> {
e.preventDefault();
let jsonBody;
if (this.props.modalContactType === ModalContactType.MarketMaker) {
jsonBody = {
name: this.nameRef.current.value,
email: this.emailRef.current.value,
country: this.countryRef.current.value,
fundSize: this.fundSizeRef.current.value,
projectOrCompany: this.companyProjectRef.current.value,
comments: this.commentsRef.current.value,
};
} else if (this.props.modalContactType === ModalContactType.Credits) {
jsonBody = {
name: this.nameRef.current.value,
email: this.emailRef.current.value,
project_name: this.companyProjectRef.current.value,
comments: this.commentsRef.current.value,
services: this.state.creditLeadsServices,
};
} else if (this.props.modalContactType === ModalContactType.Explore) {
jsonBody = {
name: this.nameRef.current.value,
email: this.emailRef.current.value,
project_name: this.companyProjectRef.current.value,
project_description: this.commentsRef.current.value,
link: this.linkRef.current.value,
theme_color: this.themeColorRef.current.value,
supports_instant: this.state.exploreSupportInstant,
};
} else {
jsonBody = {
name: this.nameRef.current.value,
email: this.emailRef.current.value,
projectOrCompany: this.companyProjectRef.current.value,
link: this.linkRef.current.value,
comments: this.commentsRef.current.value,
};
}
this.setState({ ...this.state, errors: [], isSubmitting: true });
let endpoint;
switch (this.props.modalContactType) {
case ModalContactType.Explore:
endpoint = '/explore_leads';
break;
case ModalContactType.Credits:
endpoint = '/credit_leads';
break;
case ModalContactType.MarketMaker:
endpoint = '/market_maker_leads';
break;
default:
endpoint = '/leads';
}
try {
// Disabling no-unbound method b/c no reason for _.isEmpty to be bound
// tslint:disable:no-unbound-method
const response = await fetch(`${utils.getBackendBaseUrl()}${endpoint}`, {
method: 'post',
mode: 'cors',
credentials: 'same-origin',
headers: {
'content-type': 'application/json; charset=utf-8',
},
body: JSON.stringify(_.omitBy(jsonBody, _.isEmpty)),
});
if (!response.ok) {
const errorResponse: ErrorResponse = await response.json();
const errors = this._parseErrors(errorResponse.errors);
this.setState({ ...this.state, isSubmitting: false, errors });
throw new Error('Request failed');
}
this.setState({ ...this.state, isSuccessful: true });
} catch (e) {
// Empty block
}
}
private _parseErrors(errors: ErrorResponseProps[]): ErrorProps {
const initialValue: {} = {};
return _.reduce(
errors,
(hash: ErrorProps, error: ErrorResponseProps) => {
const { param, msg } = error;
const key = param;
hash[key] = msg;
return hash;
},
initialValue,
);
}
}
// Handle errors: {"errors":[{"location":"body","param":"name","msg":"Invalid value"},{"location":"body","param":"email","msg":"Invalid value"}]}
const InputRow = styled.div`
width: 100%;
flex: 0 0 auto;
@media (min-width: 768px) {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
}
`;
const ButtonRow = styled(InputRow)`
@media (max-width: 768px) {
display: flex;
flex-direction: column;
button:nth-child(1) {
order: 2;
}
button:nth-child(2) {
order: 1;
margin-bottom: 10px;
}
}
`;
const StyledDialogContent = styled(DialogContent)`
position: relative;
max-width: 800px;
background-color: #f6f6f6 !important;
padding: 60px 60px !important;
@media (max-width: 768px) {
width: calc(100vw - 40px) !important;
margin: 40px auto !important;
padding: 30px 30px !important;
}
`;
const Form = styled.form<FormProps>`
position: relative;
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
opacity: ${props => props.isSuccessful && `0`};
visibility: ${props => props.isSuccessful && `hidden`};
`;
const Confirmation = styled.div<FormProps>`
position: absolute;
top: 50%;
text-align: center;
width: 100%;
left: 0;
transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
transition-delay: 0.4s;
padding: 60px 60px;
transform: translateY(-50%);
opacity: ${props => (props.isSuccessful ? `1` : `0`)};
visibility: ${props => (props.isSuccessful ? 'visible' : `hidden`)};
p {
max-width: 492px;
margin-left: auto;
margin-right: auto;
}
`;
// tslint:disable:max-file-line-count