├── client ├── public │ ├── robots.txt │ ├── favicon.png │ ├── images │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── 8.jpg │ │ ├── 9.jpg │ │ ├── 1.jpeg │ │ ├── 10.jpg │ │ └── 11.jpeg │ ├── manifest.json │ └── index.html ├── postcss.config.js ├── src │ ├── components │ │ ├── InlineError.js │ │ ├── Loading.js │ │ ├── Toast.js │ │ └── Validation.js │ ├── index.js │ ├── index.css │ ├── API │ │ ├── index.js │ │ └── Api.js │ └── App.js ├── .gitignore ├── tailwind.config.js ├── package.json └── README.md ├── server ├── .gitignore ├── package.json ├── server.js ├── sendEmail.js └── package-lock.json ├── video.txt └── index.html /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irenemmassy/send-email-using-nodejs/HEAD/client/public/favicon.png -------------------------------------------------------------------------------- /client/public/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irenemmassy/send-email-using-nodejs/HEAD/client/public/images/3.jpg -------------------------------------------------------------------------------- /client/public/images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irenemmassy/send-email-using-nodejs/HEAD/client/public/images/4.jpg -------------------------------------------------------------------------------- /client/public/images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irenemmassy/send-email-using-nodejs/HEAD/client/public/images/5.jpg -------------------------------------------------------------------------------- /client/public/images/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irenemmassy/send-email-using-nodejs/HEAD/client/public/images/8.jpg -------------------------------------------------------------------------------- /client/public/images/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irenemmassy/send-email-using-nodejs/HEAD/client/public/images/9.jpg -------------------------------------------------------------------------------- /client/public/images/1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irenemmassy/send-email-using-nodejs/HEAD/client/public/images/1.jpeg -------------------------------------------------------------------------------- /client/public/images/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irenemmassy/send-email-using-nodejs/HEAD/client/public/images/10.jpg -------------------------------------------------------------------------------- /client/public/images/11.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irenemmassy/send-email-using-nodejs/HEAD/client/public/images/11.jpeg -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/src/components/InlineError.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function InlineError({ error }) { 4 | return ( 5 |

{error}

6 | ); 7 | } 8 | 9 | export default InlineError; 10 | -------------------------------------------------------------------------------- /client/src/components/Loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Loading() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | 11 | export default Loading; 12 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import "react-toastify/dist/ReactToastify.css"; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById("root")); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /client/src/components/Toast.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ToastContainer } from 'react-toastify'; 3 | 4 | export default function Toast() { 5 | return ( 6 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /client/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./src/**/*.{js,jsx,ts,tsx}"], 3 | theme: { 4 | extend: { 5 | colors: { 6 | main: "#00EFBC", 7 | gray: "#C6C6C6", 8 | subMain: "#F3F9FF", 9 | border: "#F1F8FF", 10 | }, 11 | screens: { 12 | xs: "475px", 13 | }, 14 | fontFamily: { 15 | main: ["Roboto Slab", "serif"], 16 | subMain: ["Montserrat", "sans-serif"], 17 | }, 18 | }, 19 | }, 20 | plugins: [], 21 | }; 22 | -------------------------------------------------------------------------------- /video.txt: -------------------------------------------------------------------------------- 1 | CLIENT 2 | .env >>>{ 3 | REACT_APP_IP_ADRRESS_API_KEY = 4 | REACT_APP_NUMBER_VELIDATE_API_KEY = 5 | } 6 | 7 | 8 | SERVER 9 | .env >>>>>>>>>{ 10 | PORT= 11 | USER = 12 | PASSWORD= 13 | SEND_TO = 14 | CLIENT_URL= 15 | } 16 | 17 | 18 | VIDEO >>>>>>>>>>{ 19 | 00:00 INTRO 20 | 04:32 DESIGN FORM 21 | 10:56 ADD VALIDATION 22 | 24:27 DETECT USER LOCATION & GET COUNTRIES CODE 23 | 40:26 SETUP NODEMAILER 24 | 49:15 INTERGRATE API IN CLIENT SIDE 25 | } -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "start": "node server.js", 10 | "server": "nodemon server.js" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "cors": "^2.8.5", 16 | "dotenv": "^16.0.0", 17 | "express": "^4.18.1", 18 | "nodemailer": "^6.7.5", 19 | "nodemon": "^2.0.16" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Send Email Zpunet", 3 | "name": "Send Email Zpunet", 4 | "icons": [ 5 | { 6 | "src": "favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "favicon.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "favicon.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import dotenv from "dotenv"; 3 | import cors from "cors"; 4 | import EmailSender from "./sendEmail.js"; 5 | 6 | dotenv.config(); 7 | const app = express(); 8 | app.use(express.json()); 9 | app.use(cors({ origin: `${process.env.CLIENT_URL}` })); 10 | const port = process.env.PORT || 5000; 11 | 12 | // ****** SEND API 13 | app.post("/send", async (req, res) => { 14 | try { 15 | const { fullName,email,phone,message} = req.body 16 | EmailSender({fullName,email,phone,message}) 17 | res.json({ msg: "Your message sent successfully" }); 18 | } catch (error) { 19 | res.status(404).json({ msg: "Error ❌" }); 20 | } 21 | }); 22 | 23 | app.listen(port, () => { 24 | console.log(`http://localhost:${port}`); 25 | }); 26 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 17 | 18 | Send Email Using Nodemailer Project 19 | 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sendemail", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.2.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "axios": "^0.27.2", 10 | "react": "^18.1.0", 11 | "react-dom": "^18.1.0", 12 | "react-scripts": "5.0.1", 13 | "react-toastify": "^9.0.1", 14 | "web-vitals": "^2.1.4" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": [ 24 | "react-app", 25 | "react-app/jest" 26 | ] 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | }, 40 | "devDependencies": { 41 | "autoprefixer": "^10.4.7", 42 | "postcss": "^8.4.13", 43 | "tailwindcss": "^3.0.24" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Lobster&family=Montserrat:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto+Slab:wght@100;200;300;400;500;600;700;800;900&display=swap'); 2 | 3 | 4 | @tailwind base; 5 | @tailwind components; 6 | @tailwind utilities; 7 | 8 | body{ 9 | @apply bg-subMain font-main 10 | 11 | } 12 | h1{ 13 | font-family: 'Lobster', cursive; 14 | } 15 | .trans{ 16 | @apply transition duration-1000 ease-out 17 | } 18 | .social{ 19 | @apply hover:animate-spin 20 | } 21 | 22 | .flex-colo{ 23 | @apply flex justify-center items-center flex-col 24 | } 25 | .flex-rows{ 26 | @apply flex justify-center items-center flex-row gap-12 text-xs 27 | } 28 | input:focus,select:focus,textarea:focus{ 29 | outline: 0; 30 | } 31 | .box-1{ 32 | width: 36%; 33 | } 34 | .box-2{ 35 | width:64%; 36 | } 37 | .box-shadow{ 38 | box-shadow: 10px 10px 40px 0px #24252614; 39 | box-shadow: -10px -10px 40px 0px #24252614; 40 | 41 | } 42 | 43 | @media (max-width:640px) { 44 | .main-box{ 45 | flex-direction:column 46 | } 47 | .box-1,.box-2{ 48 | width: 100%; 49 | } 50 | label{ 51 | font-size: .9rem; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /client/src/components/Validation.js: -------------------------------------------------------------------------------- 1 | const validateEmail = ({ email, setEmailError }) => { 2 | const emailRegular = 3 | /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 4 | return email && !email.match(emailRegular) 5 | ? setEmailError('Email not valid') 6 | : setEmailError(''); 7 | }; 8 | 9 | const validatePhone = ({ phone, setPhoneError }) => { 10 | var phoneRegular = /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/; 11 | return phone && !phone.match(phoneRegular) 12 | ? setPhoneError('Phone Number not valid') 13 | : setPhoneError(''); 14 | }; 15 | 16 | const validateFullName = ({ fullName, setFullNameError }) => { 17 | return fullName && fullName.length < 5 18 | ? setFullNameError('Full name is too short') 19 | : fullName && fullName.length > 50 20 | ? setFullNameError('Try to make short and meanfull') 21 | : setFullNameError(''); 22 | }; 23 | 24 | const validateMessage = ({ message, setMessageError }) => { 25 | return message && message.length < 5 26 | ? setMessageError('Message is too short') 27 | : message && message.length > 200 28 | ? setMessageError('Try to make short and meanfull') 29 | : setMessageError(''); 30 | }; 31 | 32 | export { validateEmail, validateFullName, validateMessage, validatePhone }; 33 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 |
12 |
13 | 17 | 18 |
19 |
20 |

21 | Form Shoeshop Store 22 |

23 |
24 |

FullName: minah mmassy

25 |

Email: example.co.tz

26 |

Phone: 0754661424

27 |

Message: i like it

28 |
29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /client/src/API/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | // ****** Get IP adrress 4 | export const IpAddress = async ({ setLoading, setIpData }) => { 5 | try { 6 | let res = await axios.get( 7 | `http://api.ipstack.com/check?access_key=${process.env.REACT_APP_IP_ADRRESS_API_KEY}` 8 | ); 9 | if (res) { 10 | setLoading(false); 11 | setIpData(res.data.country_name); 12 | } 13 | } catch (error) { 14 | alert(`IP address Error: ${error}`); 15 | } 16 | }; 17 | 18 | // *********** Get Countries 19 | export const GetContries = async ({ setLoading, setCountries }) => { 20 | try { 21 | let res = await axios.get( 22 | `https://api.apilayer.com/number_verification/countries`, 23 | { 24 | headers: { 25 | apikey: process.env.REACT_APP_NUMBER_VELIDATE_API_KEY, 26 | }, 27 | } 28 | ); 29 | if (res) { 30 | setLoading(false); 31 | setCountries(res.data); 32 | } 33 | } catch (error) { 34 | alert(error.response.data.message); 35 | setLoading(false); 36 | } 37 | }; 38 | 39 | // *********** Send email 40 | export const SendEmail = async ({ 41 | fullName, 42 | email, 43 | phone, 44 | message, 45 | setSend, 46 | }) => { 47 | try { 48 | const datas = { fullName, email, phone, message }; 49 | let res = await axios.post(`http://localhost:5000/send`, datas); 50 | if (res) { 51 | setSend(res.data); 52 | } 53 | } catch (error) { 54 | alert(error.response.data.message); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /server/sendEmail.js: -------------------------------------------------------------------------------- 1 | import nodemailer from 'nodemailer'; 2 | 3 | const Email = (options) => { 4 | let transpoter = nodemailer.createTransport({ 5 | service: 'hotmail', //i use outlook 6 | auth: { 7 | user: process.env.USER, // email 8 | pass: process.env.PASSWORD, //password 9 | }, 10 | }); 11 | transpoter.sendMail(options, (err, info) => { 12 | if (err) { 13 | console.log(err); 14 | return; 15 | } 16 | }); 17 | }; 18 | 19 | // send email 20 | const EmailSender = ({ fullName, email, phone, message }) => { 21 | const options = { 22 | from: `ShoeShop 🛍️ <${process.env.USER}>`, 23 | to: process.env.SEND_TO, 24 | subject: 'Message From Shoeshop Store', 25 | html: ` 26 |
27 |
28 |
29 | 33 | 34 |
35 |
36 |

37 | Form Shoeshop Store 38 |

39 |
40 |

FullName: ${fullName}

41 |

Email: ${email}

42 |

Phone: ${phone}

43 |

Message: ${message}

44 |
45 |
46 |
47 |
48 | `, 49 | }; 50 | 51 | Email(options) 52 | }; 53 | 54 | export default EmailSender 55 | -------------------------------------------------------------------------------- /client/src/API/Api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const URL = process.env.REACT_APP_SERVER_URL; 4 | 5 | // ***** Get IP Adress 6 | export const IpAdrress = async ({ setLoading, setIPData }) => { 7 | try { 8 | let res = await axios.get( 9 | `http://api.ipstack.com/check?access_key=${process.env.REACT_APP_IP_ADRRSS_API_KEY}` 10 | ); 11 | if (res) { 12 | setLoading(false); 13 | setIPData(res.data.country_name); 14 | } 15 | } catch (error) { 16 | alert(`IP Adress Error: ${error}`); 17 | setLoading(false); 18 | } 19 | }; 20 | 21 | // ****** Get Countries 22 | export const GetCountries = async ({ setLoading, setCountries }) => { 23 | try { 24 | let res = await axios.get( 25 | `https://api.apilayer.com/number_verification/countries`, 26 | { 27 | headers: { 28 | apikey: process.env.REACT_APP_NUMBER_VALIDATE_API_KEY, 29 | }, 30 | } 31 | ); 32 | if (res) { 33 | setLoading(false); 34 | setCountries(res.data); 35 | } 36 | } catch (error) { 37 | alert(error.response.data.message); 38 | setLoading(false); 39 | } 40 | }; 41 | 42 | // ***** Validate number 43 | export const ValidateNumber = async ({ 44 | phoneFull, 45 | setButtonLoad, 46 | setValidate, 47 | }) => { 48 | try { 49 | setButtonLoad(true); 50 | let res = await axios.get( 51 | `https://api.apilayer.com/number_verification/validate?number=${phoneFull}`, 52 | { 53 | headers: { 54 | apikey: process.env.REACT_APP_NUMBER_VALIDATE_API_KEY, 55 | }, 56 | } 57 | ); 58 | if (res) { 59 | setButtonLoad(false); 60 | setValidate(res.data); 61 | } 62 | } catch (error) { 63 | alert(error.response.data.message); 64 | setButtonLoad(false); 65 | } 66 | }; 67 | 68 | // ******* Send message 69 | export const SendMessage = async ({ 70 | fullName, 71 | email, 72 | phone, 73 | message, 74 | setSend, 75 | }) => { 76 | try { 77 | const datas = { fullName, email, phone, message }; 78 | let res = await axios.post(`${URL}/send`, datas); 79 | if (res) { 80 | setSend(res.data); 81 | } 82 | } catch (error) { 83 | alert(error.response.data.msg); 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { GetContries, IpAddress, SendEmail } from './API'; 3 | import InlineError from './components/InlineError'; 4 | import Loading from './components/Loading'; 5 | import { 6 | validateEmail, 7 | validateFullName, 8 | validateMessage, 9 | validatePhone, 10 | } from './components/Validation'; 11 | import { toast } from 'react-toastify'; 12 | import Toast from './components/Toast'; 13 | 14 | const InputClass = 15 | 'w-full py-4 placeholder:text-gray px-6 text-main border-2 mt-2 border-border rounded-md'; 16 | 17 | function App() { 18 | const [fullName, setFullName] = useState(''); 19 | const [email, setEmail] = useState(''); 20 | const [phone, setPhone] = useState(); 21 | const [message, setMessage] = useState(''); 22 | const [fullNameError, setFullNameError] = useState(); 23 | const [emailError, setEmailError] = useState(); 24 | const [phoneError, setPhoneError] = useState(); 25 | const [messageError, setMessageError] = useState(); 26 | const [loading, setLoading] = useState(true); 27 | const [ipData, setIpData] = useState(); 28 | const [countries, setCountries] = useState(); 29 | const [country, setCountry] = useState('Tanzania'); 30 | const [buttonLoading, setButtonLoading] = useState(false); 31 | const [send, setSend] = useState(); 32 | 33 | let result = countries && Object.keys(countries).map((key) => countries[key]); 34 | let output = result && result.find((x) => x.country_name === country); 35 | let outputResult = output && output.dialling_code; 36 | let phoneFull = outputResult && outputResult.concat(phone); 37 | 38 | useEffect(() => { 39 | if (!ipData & !countries) { 40 | IpAddress({ setLoading, setIpData }); 41 | GetContries({ setLoading, setCountries }); 42 | } 43 | // *********** VALIDATION ********** 44 | validateFullName({ fullName, setFullNameError }); 45 | validateEmail({ email, setEmailError }); 46 | validatePhone({ phone, setPhoneError }); 47 | validateMessage({ message, setMessageError }); 48 | 49 | // *********** 50 | if (send) { 51 | toast.success(send.msg); 52 | setFullName("") 53 | setEmail("") 54 | setMessage("") 55 | setSend() 56 | setPhone("") 57 | } 58 | }, [fullName, email, phone, message, send, ipData, countries]); 59 | 60 | const submitHandler = (e) => { 61 | e.preventDefault(); 62 | setButtonLoading(true); 63 | if (!fullNameError & !emailError & !phoneError & !messageError) { 64 | SendEmail({ fullName, email, phone: phoneFull, message, setSend }).then( 65 | () => { 66 | setButtonLoading(false); 67 | } 68 | ); 69 | } 70 | }; 71 | 72 | return ( 73 | <> 74 | 75 |
76 | {loading ? ( 77 | 78 | ) : ( 79 |
80 |
81 | logo 86 |

ShoeShop

87 |

88 | We detected you are
current in{' '} 89 | ({ipData && ipData}) 90 |

91 |
92 |
96 |

97 | Contact Us 98 |

99 | {/* fullName */} 100 |
101 | 102 | setFullName(e.target.value)} 105 | required 106 | type="text" 107 | placeholder="User Doe" 108 | className={InputClass} 109 | /> 110 | {fullNameError && } 111 |
112 | {/* email */} 113 |
114 | 115 | setEmail(e.target.value)} 119 | type="email" 120 | placeholder="example@gmail.com" 121 | className={InputClass} 122 | /> 123 | {emailError && } 124 |
125 | {/* phone */} 126 |
127 | 128 |
129 | 141 |
142 | {outputResult} 143 |
144 | setPhone(e.target.value)} 148 | type="number" 149 | placeholder="0765452312" 150 | className="placeholder:text-gray text-main col-span-7 px-3" 151 | /> 152 |
153 | {phoneError && } 154 |
155 | {/* message */} 156 |
157 | 158 |