Skip to content

Commit 0279ab7

Browse files
committed
initial commit
0 parents  commit 0279ab7

17 files changed

+12004
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
build

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"npm-scripts.showStartNotification": false
3+
}

package.json

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"name": "rapid-widgets-now",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"start": "react-scripts start",
7+
"build": "react-scripts build",
8+
"build:lib": "tsc ./src/index.tsx --outDir ./lib --jsx react --allowJs && tsc -d ./src/index.tsx --outDir ./lib --jsx react",
9+
"test": "react-scripts test",
10+
"eject": "react-scripts eject",
11+
"storybook": "start-storybook -p 9001 -c .storybook",
12+
"deploy": "npm run build && ns build"
13+
},
14+
"dependencies": {
15+
"@types/jest": "24.0.25",
16+
"@types/node": "13.1.6",
17+
"@types/react": "16.9.17",
18+
"@types/react-dom": "16.9.4",
19+
"axios": "^0.19.1",
20+
"lodash": "^4.17.15",
21+
"react": "^16.12.0",
22+
"react-dom": "^16.12.0",
23+
"react-scripts": "3.3.0",
24+
"typescript": "3.7.4"
25+
},
26+
"devDependencies": {
27+
"@types/axios": "^0.14.0",
28+
"@types/lodash": "^4.14.149",
29+
"now": "^16.7.3",
30+
"now-serve": "^0.5.1"
31+
},
32+
"browserslist": {
33+
"production": [
34+
">0.2%",
35+
"not dead",
36+
"not op_mini all"
37+
],
38+
"development": [
39+
"last 1 chrome version",
40+
"last 1 firefox version",
41+
"last 1 safari version"
42+
]
43+
}
44+
}

public/index.html

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
7+
<!--
8+
Notice the use of %PUBLIC_URL% in the tag above.
9+
It will be replaced with the URL of the `public` folder during the build.
10+
Only files inside the `public` folder can be referenced from the HTML.
11+
Unlike "/favicon.ico" or "favico.ico", "%PUBLIC_URL%/favicon.ico" will
12+
work correctly both with client-side routing and a non-root public URL.
13+
Learn how to configure a non-root public URL by running `npm run build`.
14+
-->
15+
<title>React App</title>
16+
</head>
17+
<body>
18+
<div id="root"></div>
19+
<!--
20+
This HTML file is a template.
21+
If you open it directly in the browser, you will see an empty page.
22+
You can add webfonts, meta tags, or analytics to this file.
23+
The build step will place the bundled scripts into the <body> tag.
24+
To begin the development, run `npm start`.
25+
To create a production bundle, use `npm run build`.
26+
-->
27+
</body>
28+
</html>

src/.DS_Store

6 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { useEffect, BaseSyntheticEvent } from "react";
2+
3+
import { useDebounce, useApi } from "../hooks";
4+
import { validateEmail } from "./emailValidator.service";
5+
import merge from "lodash/merge";
6+
import { ApiFactoryProps } from "../types";
7+
8+
export type State = {
9+
email?: string
10+
rapidKey: string
11+
isValid?: boolean
12+
isLoading?: boolean
13+
}
14+
15+
export type Api = {
16+
email: string
17+
isValid: boolean
18+
isLoading: boolean
19+
handleEmailChange: (e: BaseSyntheticEvent) => void
20+
validateEmailAndSetToState: (rapidKey: string, email: string) => void
21+
}
22+
23+
export const validateEmailApiFactory = ({ state, setState }: ApiFactoryProps<State>) => {
24+
25+
const validateEmailAndSetToState = (rapidKey: string, email: string) => {
26+
if (!email) return;
27+
validateEmail
28+
(rapidKey, email)
29+
.then(({ data = {} }: any) => {
30+
setState(prevState => {
31+
return {
32+
...prevState,
33+
isValid: data.status !== "invalid",
34+
isLoading: false
35+
};
36+
});
37+
})
38+
.catch((e: Error) => {
39+
setState(prevState => {
40+
return {
41+
...prevState,
42+
isValid: false,
43+
isLoading: false
44+
};
45+
});
46+
});
47+
};
48+
49+
const handleEmailChange = (e: BaseSyntheticEvent) => {
50+
e.persist();
51+
setState(prevState => {
52+
return {
53+
...prevState,
54+
isLoading: true,
55+
email: e.target && e.target.value
56+
};
57+
});
58+
};
59+
60+
const email = state.email;
61+
const isValid = state.isValid;
62+
const isLoading = state.isLoading;
63+
64+
return {
65+
email,
66+
handleEmailChange,
67+
isValid,
68+
isLoading,
69+
validateEmailAndSetToState
70+
};
71+
};
72+
73+
export const useValidateEmailApiFactory = ({ state, setState }: ApiFactoryProps<State>) => {
74+
const validateEmailApi = validateEmailApiFactory({ state, setState })
75+
76+
const validateEmailViaZeroBounce = useDebounce(validateEmailApi.validateEmailAndSetToState, 1000);
77+
useEffect(() => {
78+
validateEmailViaZeroBounce(state.rapidKey, state.email)
79+
}, [state.rapidKey, state.email, validateEmailViaZeroBounce])
80+
81+
return validateEmailApi
82+
}
83+
84+
export const DEFAULT_STATE = {
85+
email: '',
86+
isValid: false,
87+
isLoading: false,
88+
}
89+
90+
export const useValidateEmailApi = (initialState: State) => {
91+
const mergedInitialState = merge({}, DEFAULT_STATE, initialState)
92+
return useApi(useValidateEmailApiFactory, mergedInitialState);
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import axios from 'axios'
2+
3+
export function validateEmail(rapidKey: string, email: string, idAddress?: string) {
4+
return axios.get(
5+
`https://zerobounce1.p.rapidapi.com/v2/validate?ip_address=${idAddress}&email=${email}`,
6+
{
7+
headers: {
8+
"x-rapidapi-host": "zerobounce1.p.rapidapi.com",
9+
"x-rapidapi-key":
10+
rapidKey,
11+
}
12+
}
13+
)
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// import axios from 'axios';
2+
3+
// import { createTestApi } from '../utils'
4+
// import { DEFAULT_STATE, validateEmailApiFactory } from './emailValidator.api'
5+
6+
// jest.mock('axios');
7+
8+
// const RAPID_KEY = 'RAPID_KEY'
9+
// const EMAIL = '[email protected]'
10+
11+
// describe('validate email api', () => {
12+
13+
// it('fetches successfully data from API and returns invalid', async () => {
14+
// const testValidateEmail = createTestApi(validateEmailApiFactory, DEFAULT_STATE);
15+
// const data = {
16+
// data: {
17+
// status: 'invalid'
18+
// }
19+
// };
20+
// axios.get.mockImplementationOnce(() => Promise.resolve(data));
21+
22+
// await testValidateEmail.api.validateEmailAndSetToState(RAPID_KEY, EMAIL)
23+
24+
// expect(testValidateEmail.api.isValid).toEqual(false)
25+
// expect(testValidateEmail.api.isLoading).toEqual(false)
26+
// })
27+
28+
// it('fetches successfully data from API and returns valid', async () => {
29+
// const testValidateEmail = createTestApi(validateEmailApiFactory, DEFAULT_STATE);
30+
// const data = {
31+
// data: {
32+
// status: 'valid'
33+
// }
34+
// };
35+
// axios.get.mockImplementationOnce(() => Promise.resolve(data));
36+
37+
// await testValidateEmail.api.validateEmailAndSetToState(RAPID_KEY, EMAIL)
38+
39+
// expect(testValidateEmail.api.isValid).toEqual(true)
40+
// expect(testValidateEmail.api.isLoading).toEqual(false)
41+
// })
42+
43+
// it('fetches erroneously data from an API', async () => {
44+
// const testValidateEmail = createTestApi(validateEmailApiFactory, DEFAULT_STATE);
45+
// const errorMessage = 'Network Error';
46+
// axios.get.mockImplementationOnce(() =>
47+
// Promise.reject(new Error(errorMessage)),
48+
// );
49+
50+
// await testValidateEmail.api.validateEmailAndSetToState(RAPID_KEY, EMAIL)
51+
52+
// expect(testValidateEmail.api.isValid).toEqual(false)
53+
// expect(testValidateEmail.api.isLoading).toEqual(false)
54+
// });
55+
56+
// })
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React, { ReactComponentElement, ReactElement, ReactChildren } from "react";
2+
import { useValidateEmailApi, State, Api } from "./emailValidator.api";
3+
4+
export type Props = {
5+
rapidKey: string
6+
renderProps?: (props: Api) => ReactElement
7+
children?: (props: Api) => ReactElement
8+
}
9+
10+
function EmailValidator(props: Props) {
11+
const { rapidKey, renderProps, children } = props
12+
const validateEmailApi = useValidateEmailApi({ rapidKey });
13+
14+
if (renderProps) {
15+
return renderProps(validateEmailApi)
16+
}
17+
18+
if (children) {
19+
return children(validateEmailApi)
20+
}
21+
22+
return (
23+
<div className={'email-field-container' + (validateEmailApi.isValid ? ' valid' : ' invalid')} >
24+
<input onChange={validateEmailApi.handleEmailChange} value={validateEmailApi.email} />
25+
{!validateEmailApi.isLoading && <div> {validateEmailApi.isValid ? 'valid' : 'invalid'} </div>}
26+
</div>
27+
);
28+
}
29+
30+
export default EmailValidator

src/components/Twilio/Twilio.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react'
2+
3+
type Props = {
4+
rapidKey: string
5+
}
6+
7+
export default function Twilio(props: Props) {
8+
const { rapidKey } = props
9+
10+
return (
11+
<div className="twilio-container">
12+
test
13+
</div>
14+
)
15+
}

src/components/Twilio/style.css

Whitespace-only changes.

src/components/hooks.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useState, useRef, useEffect } from "react";
2+
import debounce from 'lodash/debounce'
3+
import { ApiFactory } from "./types";
4+
5+
export const useApi = (apiFactory: ApiFactory, initialState: any) => {
6+
let [state, setState] = useState(initialState);
7+
return apiFactory({ state, setState });
8+
};
9+
10+
export function useIsMounted() {
11+
const isMounted = useRef(true);
12+
useEffect(() => {
13+
isMounted.current = true;
14+
return () => {
15+
isMounted.current = false
16+
};
17+
}, []);
18+
return isMounted.current;
19+
}
20+
21+
export function useDebounce(func: (...args: any[]) => any, ...args: any[]) {
22+
const isMounted = useIsMounted();
23+
24+
const securedFunc = (...funcArgs: any[]) => {
25+
if (!isMounted) {
26+
return;
27+
}
28+
29+
return func(...funcArgs);
30+
};
31+
32+
return useRef(debounce(securedFunc, ...args)).current;
33+
}

src/components/types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { SetStateAction, Dispatch } from 'react'
2+
3+
export interface ApiFactoryProps<T = any> {
4+
state: T
5+
setState: Dispatch<SetStateAction<T>>
6+
}
7+
8+
export type ApiFactory<T = any> = (props: ApiFactoryProps<T>) => any

src/components/utils.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ApiFactory } from "./types";
2+
3+
// export const createTestApi = <T = any>(apiFactory: ApiFactory<T>, defaultValue: T) => {
4+
// let state = defaultValue;
5+
// let setState = (updater: T) => {
6+
// if (typeof updater === "function") {
7+
// state = updater(state);
8+
// } else {
9+
// state = updater;
10+
// }
11+
// ref.api = apiFactory({ state, setState });
12+
// };
13+
// let ref = {
14+
// api: apiFactory({ state, setState })
15+
// };
16+
// return ref;
17+
// };

src/index.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
3+
// export { default as Twilio } from './components/Twilio'
4+
import React from 'react'
5+
import ReactDOM from 'react-dom'
6+
7+
// import Twilio from './components/Twilio'
8+
import EmailValidator from './components/EmailValidator'
9+
10+
11+
function App() {
12+
const urlParams = new URLSearchParams(window.location.search);
13+
const rapidKey = urlParams.get('rapidKey') || '';
14+
15+
return <EmailValidator rapidKey={rapidKey} />
16+
}
17+
18+
ReactDOM.render(<App />, document.getElementById('root'))

0 commit comments

Comments
 (0)