Skip to content
This repository was archived by the owner on Oct 11, 2024. It is now read-only.

Commit 921bc20

Browse files
authored
Merge pull request #462 from 0xProject/test/serverless
Add new Form integration and upgrade Node Version
2 parents efd3156 + b9e7702 commit 921bc20

File tree

11 files changed

+1469
-415
lines changed

11 files changed

+1469
-415
lines changed

api/_utils.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export function validateContactForm(entries: { [s: string]: string }): { [s: string]: string } {
2+
const newErrors: { [s: string]: string } = {};
3+
const requiredFields = [
4+
'email',
5+
'firstName',
6+
'lastName',
7+
'companyName',
8+
'linkToProductOrWebsite',
9+
'usageDescription',
10+
'referral',
11+
] as const;
12+
13+
for (const field of requiredFields) {
14+
if (entries[field] === '') {
15+
newErrors[field] = 'Field is required';
16+
}
17+
}
18+
19+
if (!newErrors.email && !/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(entries.email)) {
20+
newErrors.email = 'No valid email address';
21+
}
22+
23+
if (entries.chainOfInterest === 'Other' && entries.chainOfInterestOther === '') {
24+
newErrors.chainOfInterestOther = 'Field is required';
25+
}
26+
return newErrors;
27+
}

api/contact.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import type { VercelRequest, VercelResponse } from '@vercel/node';
2+
import fetch from 'node-fetch';
3+
4+
import { validateContactForm } from './_utils';
5+
6+
// tslint:disable-next-line:no-default-export
7+
export default async function handlerAsync(req: VercelRequest, res: VercelResponse): Promise<VercelResponse> {
8+
const { body } = req;
9+
10+
if (Object.keys(body || {}).length === 0) {
11+
return res.status(401).send('Body missing');
12+
}
13+
14+
const errors = validateContactForm(req.body);
15+
16+
if (Object.keys(errors).length > 0) {
17+
return res.status(422).send('Invalid payload');
18+
}
19+
20+
const {
21+
firstName,
22+
lastName,
23+
email,
24+
linkToProductOrWebsite,
25+
companyName,
26+
typeOfBusiness,
27+
role,
28+
currentTradingVolume,
29+
isApplicationLive,
30+
productOfInterest,
31+
chainOfInterest,
32+
// usageDescription,
33+
timelineForIntegration,
34+
chainOfInterestOther,
35+
isApiKeyRequired,
36+
referral,
37+
} = body;
38+
39+
const payload = {
40+
oid: process.env.SALESFORCE_OID,
41+
first_name: firstName,
42+
last_name: lastName,
43+
email,
44+
website: linkToProductOrWebsite,
45+
company: companyName,
46+
'00N8c00000drpGS': typeOfBusiness,
47+
'00N8c00000drpLI': role,
48+
'00N8c00000drpLS': currentTradingVolume,
49+
'00N8c00000drpLm': `${isApplicationLive}`,
50+
'00N8c00000drpLw': productOfInterest,
51+
'00N8c00000drr36': chainOfInterest,
52+
'00N8c00000ds8KX': timelineForIntegration,
53+
// '00N8c00000drpgB': usageDescription,
54+
...(chainOfInterest === 'Other' ? { '00N8c00000drr3B': chainOfInterestOther } : {}),
55+
'00N8c00000drpgL': `${isApiKeyRequired}`,
56+
'00N8c00000drvT8': referral,
57+
};
58+
59+
await fetch('https://webto.salesforce.com/servlet/servlet.WebToLead?encoding=UTF-8', {
60+
method: 'POST',
61+
headers: {
62+
'Content-Type': 'application/x-www-form-urlencoded',
63+
},
64+
body: new URLSearchParams(payload as { [s: string]: string }),
65+
});
66+
67+
return res.send('Created successfully');
68+
}

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
"moment-precise-range-plugin": "^1.3.0",
9292
"moment-timezone": "^0.5.33",
9393
"nice-color-palettes": "^3.0.0",
94+
"node-fetch": "^2.6.2",
9495
"numeral": "^2.0.6",
9596
"patch-package": "^6.4.7",
9697
"polished": "^1.9.2",
@@ -217,6 +218,7 @@
217218
"unist-util-select": "^2.0.2",
218219
"unist-util-visit": "^2.0.0",
219220
"unist-util-visit-parents": "^3.0.0",
221+
"vercel": "^27.0.1",
220222
"webpack": "^4.39.2",
221223
"webpack-cli": "3.3.7",
222224
"webpack-dev-server": "^3.8.0",

ts/components/footer.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ const linkRows: LinkRows[] = [
3434
{ url: 'https://docs.0x.org/', text: 'Documentation', shouldOpenInNewTab: true },
3535
{ url: constants.URL_GITHUB_ORG, text: 'GitHub', shouldOpenInNewTab: true },
3636
{ url: 'https://docs.0x.org/protocol/docs', text: 'Protocol Spec', shouldOpenInNewTab: true },
37+
{ url: '/#contact', text: 'Contact Us' },
38+
{
39+
url: 'https://ethereum.stackexchange.com/questions/tagged/0x',
40+
text: 'Stack Exchange',
41+
shouldOpenInNewTab: true,
42+
},
3743
],
3844
},
3945
{

ts/components/header.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { FlexWrap } from 'ts/components/newLayout';
1515

1616
import { ThemeValuesInterface } from 'ts/style/theme';
1717
import { zIndex } from 'ts/style/z_index';
18-
import { constants } from 'ts/utils/constants';
1918

2019
import { WebsitePaths } from 'ts/types';
2120

@@ -101,13 +100,8 @@ export const HeaderBase: React.FC<HeaderProps> = React.memo((props) => {
101100
</NavLinks>
102101

103102
<MediaQuery minWidth={990}>
104-
<TradeButton
105-
bgColor={theme.headerButtonBg}
106-
color="#ffffff"
107-
href={constants.MATCHA_PRODUCTION_URL}
108-
target="_blank"
109-
>
110-
Trade on Matcha
103+
<TradeButton bgColor={theme.headerButtonBg} color="#ffffff" href={'#contact'}>
104+
Get in Touch
111105
</TradeButton>
112106
</MediaQuery>
113107

ts/components/modals/input.tsx

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,86 @@ export const CheckBoxInput = (props: CheckBoxProps) => {
6060
const { isSelected, label, onClick } = props;
6161
return (
6262
<Container onClick={onClick} className="flex items-center">
63-
<Container marginRight="10px">
63+
<Container marginRight="10px" minWidth={40}>
6464
<CheckMark isChecked={isSelected} color={colors.brandLight} />
6565
</Container>
6666
<Label style={{ marginBottom: '0' }}>{label}</Label>
6767
</Container>
6868
);
6969
};
7070

71+
interface WrappedReactNode {
72+
item: React.ReactNode[];
73+
value: string;
74+
}
75+
76+
interface GenericDropdownProps {
77+
items: string[] | readonly string[] | WrappedReactNode[];
78+
name: string;
79+
errors?: ErrorProps;
80+
label: string;
81+
defaultValue?: string;
82+
onItemSelected?: (item: string) => any;
83+
width?: InputWidth;
84+
}
85+
86+
export const GenericDropdown = ({
87+
items,
88+
defaultValue = typeof items[0] === 'string' ? items[0] : items[0].value,
89+
// tslint:disable-next-line:no-empty
90+
onItemSelected = () => {},
91+
width = InputWidth.Full,
92+
errors,
93+
name,
94+
label,
95+
}: GenericDropdownProps) => {
96+
const id = `input-${name}`;
97+
const [currentValue, setCurrentValue] = React.useState(defaultValue);
98+
const isErrors = errors && errors.hasOwnProperty(name) && errors[name] !== null;
99+
const errorMessage = isErrors ? errors[name] : null;
100+
101+
const handleChange = React.useCallback(
102+
(event) => {
103+
setCurrentValue(event.target.value);
104+
onItemSelected(event.target.value);
105+
},
106+
[onItemSelected],
107+
);
108+
109+
return (
110+
<InputWrapper width={width}>
111+
<Label htmlFor={id}>{label}</Label>
112+
<StyledDropdownContainer id={id} isErrors={isErrors}>
113+
<StyledDropdown
114+
value={currentValue}
115+
onChange={handleChange}
116+
isErrors={isErrors}
117+
style={{ color: currentValue === '' ? 'gray' : undefined }}
118+
>
119+
<option value="" disabled={true} selected={true} color="gray">
120+
Select
121+
</option>
122+
{items.map((item: string | WrappedReactNode) => {
123+
if (typeof item === 'string') {
124+
return (
125+
<option value={item} key={`item-${item}`}>
126+
{item}
127+
</option>
128+
);
129+
}
130+
return (
131+
<option value={item.value} key={`item-${item.value}`}>
132+
{item.item}
133+
</option>
134+
);
135+
})}
136+
</StyledDropdown>
137+
</StyledDropdownContainer>
138+
{isErrors && <Error>{errorMessage}</Error>}
139+
</InputWrapper>
140+
);
141+
};
142+
71143
export const Input = React.forwardRef((props: InputProps, ref?: React.Ref<HTMLInputElement>) => {
72144
const { name, label, type, errors, defaultValue, onChange, width, className, placeholder, value } = props;
73145
const id = `input-${name}`;
@@ -119,6 +191,29 @@ const StyledInput = styled.input`
119191
}
120192
`;
121193

194+
const StyledDropdown = styled.select`
195+
height: auto !important;
196+
outline: none;
197+
width: 100%;
198+
border: none;
199+
background-color: #fff;
200+
background-color: ${(props: InputProps) => props.isErrors && `#FDEDED`};
201+
font-size: 1.111111111rem !important;
202+
`;
203+
204+
const StyledDropdownContainer = styled.div`
205+
background-color: #fff;
206+
background-color: ${(props: InputProps) => props.isErrors && `#FDEDED`};
207+
border-color: ${(props: InputProps) => props.isErrors && `#FD0000`};
208+
border: 1px solid #d5d5d5;
209+
padding: 16px 15px 14px;
210+
color: #000;
211+
height: auto !important;
212+
font-size: 1.111111111rem !important;
213+
outline: none;
214+
width: 100%;
215+
`;
216+
122217
const InputWrapper = styled.div<InputProps>`
123218
position: relative;
124219
flex-grow: ${(props) => props.width === InputWidth.Full && 1};

0 commit comments

Comments
 (0)