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 { Input, InputWidth } from 'ts/components/modals/input';
import { Heading, Paragraph } from 'ts/components/text';
import { GlobalStyle } from 'ts/constants/globalStyle';
export enum ModalContactType {
General = 'GENERAL',
MarketMaker = 'MARKET_MAKER',
}
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 = {
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();
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>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 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.General:
default:
return this._renderGeneralFormContent(errors);
}
}
private _renderMarketMakerFormContent(errors: ErrorProps): React.ReactNode {
return (
<>
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
If you’re considering market making 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="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 _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: Event): 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 {
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 });
const endpoint =
this.props.modalContactType === ModalContactType.MarketMaker ? '/market_maker_leads' : '/leads';
try {
// Disabling no-unbound method b/c no reason for _.isEmpty to be bound
// tslint:disable:no-unbound-method
const response = await fetch(`https://website-api.0xproject.com${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.30s ease-in-out, visibility 0.30s 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.30s ease-in-out, visibility 0.30s ease-in-out;
transition-delay: 0.40s;
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;
}
`;