diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..2199c54e --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +run-dev: + docker-compose up \ No newline at end of file diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 00000000..c4ad5211 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,20 @@ +# Dockerfile for Node Express Backend + +FROM node:14-alpine + +# Create App Directory +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +# Install Dependencies +COPY package*.json ./ + +RUN npm install + +# Copy app source code +COPY . . + +# Exports +EXPOSE 3500 + +CMD ["npm","start"] \ No newline at end of file diff --git a/backend/Makefile b/backend/Makefile new file mode 100644 index 00000000..69e0fedd --- /dev/null +++ b/backend/Makefile @@ -0,0 +1,5 @@ +# "make build" will give us the backend image +build: + docker build -t api-server . + + diff --git a/backend/package.json b/backend/package.json index c570aa8a..0e3708de 100644 --- a/backend/package.json +++ b/backend/package.json @@ -67,6 +67,6 @@ "husky": "^4.3.8", "lint-staged": "^10.5.3", "nodemon": "^2.0.6", - "prettier": "2.2.1" + "prettier": "2.2.1" } } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..f31ea9f4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,58 @@ +version: "3.7" + +services: + api-server: + build: + context: ./backend/ + dockerfile: Dockerfile + image: api-server + container_name: myapp-node-server + command: /usr/src/app/node_modules/.bin/nodemon index.js + volumes: + - ./backend/:/usr/src/app + - /usr/src/app/node_modules + ports: + - "3500:3500" + depends_on: + - mongo + env_file: ./backend/.env + environment: + - NODE_ENV=development + networks: + - community-app + mongo: + image: mongo:4.4-bionic + volumes: + - mongo-data:/data/db + ports: + - "27017:27017" + networks: + - community-app + react-app: + build: + context: ./frontend/ + dockerfile: Dockerfile + image: react-app + stdin_open: true + container_name: myapp-react-frontend + command: npm start + volumes: + - ./frontend/:/usr/src/app + - /usr/src/app/node_modules + depends_on: + - api-server + ports: + - "3000:3000" + networks: + - community-app + +networks: + community-app: + driver: bridge + +volumes: + mongo-data: + driver: local + node_modules: + web-root: + driver: local diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 00000000..154e9cd1 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,20 @@ +# Dockerfile for React client + +# Build react client +FROM node:14-alpine + +# Working directory be app +WORKDIR /usr/src/app + +COPY package*.json ./ + +### Installing dependencies + +RUN npm install + +# copy local files to app folder +COPY . . + +EXPOSE 3000 + +CMD ["npm","start"] \ No newline at end of file diff --git a/frontend/Makefile b/frontend/Makefile new file mode 100644 index 00000000..d908ff7c --- /dev/null +++ b/frontend/Makefile @@ -0,0 +1,5 @@ +# "make build" will give us the frontend image +build: + docker build -t react-app . + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2ff9d840..73bf69b6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2019,6 +2019,31 @@ "prop-types": "^15.6.2" } }, + "@sideway/address": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.2.tgz", + "integrity": "sha512-idTz8ibqWFrPU8kMirL0CoPH/A29XOzzAzpyN3zQ4kAWnzmNfFmRaoMNN6VI8ske5M73HZyhIaW4OuSFIdM4oA==", + "requires": { + "@hapi/hoek": "^9.0.0" + }, + "dependencies": { + "@hapi/hoek": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", + "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==" + } + } + }, + "@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "@sinonjs/commons": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", @@ -7727,6 +7752,11 @@ "resolved": "https://registry.npmjs.org/goober/-/goober-2.0.33.tgz", "integrity": "sha512-Wy9HYzmmqmB1PTMQCY3+hlREZ4Lk7kaac6PZeWlJxOM6lIO9lsrU2yBu33TiougO0AC6bBxxIVueDP+nEGNWJw==" }, + "google-libphonenumber": { + "version": "3.2.17", + "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.17.tgz", + "integrity": "sha512-T1fBQ3ujlpo4VUe0palZVHxBkY1zsfCShkS3l1rNq/d5C6C1SIijo8aXzgpJeGQFB8Bk+C36o6jhLl05NtfQ3w==" + }, "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -10280,11 +10310,46 @@ "supports-color": "^7.0.0" } }, + "joi": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.4.0.tgz", + "integrity": "sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg==", + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.0", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + }, + "dependencies": { + "@hapi/hoek": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", + "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==" + }, + "@hapi/topo": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", + "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + } + } + }, "joi-browser": { "version": "13.4.0", "resolved": "https://registry.npmjs.org/joi-browser/-/joi-browser-13.4.0.tgz", "integrity": "sha512-TfzJd2JaJ/lg/gU+q5j9rLAjnfUNF9DUmXTP9w+GfmG79LjFOXFeM7hIFuXCBcZCivUDFwd9l1btTV9rhHumtQ==" }, + "joi-phone-number": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/joi-phone-number/-/joi-phone-number-5.0.1.tgz", + "integrity": "sha512-gVzKD2WETDxAONmdt79tF9LHEfr2M6AtSyZ8/FnkQkF9qln6PF5wuzjrr9555uB7R+LbAQdFNgYN2UETzPn5ew==", + "requires": { + "google-libphonenumber": "3.2.17" + } + }, "jquery": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index eace0db2..75a50a47 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,7 +12,9 @@ "bootstrap": "^4.5.3", "bootstrap-social": "^5.1.1", "font-awesome": "^4.7.0", + "joi": "^17.4.0", "joi-browser": "^13.4.0", + "joi-phone-number": "^5.0.1", "jwt-decode": "^3.1.2", "mdbreact": "^5.0.1", "node-sass": "^4.14.1", diff --git a/frontend/src/pages/Home/components/JoinUsForm/Form.jsx b/frontend/src/pages/Home/components/JoinUsForm/Form.jsx index d01fcf1e..c4ef88fe 100644 --- a/frontend/src/pages/Home/components/JoinUsForm/Form.jsx +++ b/frontend/src/pages/Home/components/JoinUsForm/Form.jsx @@ -1,9 +1,87 @@ -import React from "react"; +import React, { useState } from "react"; +import Joi from "joi-browser"; import styles from "./form.module.scss"; import { Button2 } from "../../../../components/util/Button/index"; + export const JoinUsForm = (props) => { let dark = props.theme; + const [formData, setFormData] = useState({ + name: "", + email: "", + link: "", + desc: "", + dept: null, + college: "", + }); + + const [formerrors, setFormErrors] = useState({}); + + const schema = { + name: Joi.string().min(3).required(), + email: Joi.string() + .email({ tlds: { allow: false } }) + .required(), + link: Joi.string().uri().required(), + desc: Joi.string().required(), + dept: Joi.required(), + college: Joi.string().required(), + }; + + const validate = () => { + const result = Joi.validate(formData, schema, { abortEarly: false }); + if (!result.error) return null; + const errors = {}; + for (let item of result.error.details) { + errors[item.path[0]] = item.message; + } + return errors; + }; + + const validateProperty = (input) => { + const { name, value } = input; + const obj = { [name]: value }; + const obj_schema = { [name]: schema[name] }; + const result = Joi.validate(obj, obj_schema); + return result.error ? result.error.details[0].message : null; + }; + + const handleSubmit = (e) => { + e.preventDefault(); + const errors = validate(); + Object.keys(formData).map((key) => { + if (formData[key] === "" || formData[key] === null) { + errors[key] = `${key} is not allowed to be empty`; + } + return 0; + }); + if (errors["info"]) { + delete errors["info"]; + } + if (Object.keys(errors).length !== 0) { + setFormErrors(errors); + } + if (Object.keys(errors).length !== 0) { + console.log(errors); + } else { + //Call the Server + console.log("Submitted"); + } + }; + + const handleChange = (e) => { + const { currentTarget: input } = e; + const errors = { ...formerrors }; + const errorMessage = validateProperty(input); + if (errorMessage) errors[input.name] = errorMessage; + else delete errors[input.name]; + + const data = { ...formData }; + data[input.name] = input.value; + setFormData({ ...data, [input.name]: input.value }); + setFormErrors(errors); + }; + return (