diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..083ffc7 --- /dev/null +++ b/.babelrc @@ -0,0 +1,35 @@ +{ + "env": { + "development": { + "presets": [ + "next/babel" + ], + "plugins": [ + "inline-react-svg" + ] + }, + "production": { + "presets": [ + "next/babel" + ], + "plugins": [ + "inline-react-svg" + ] + }, + "test": { + "presets": [ + [ + "next/babel", + { + "preset-env": { + "modules": "commonjs" + } + } + ] + ], + "plugins": [ + "inline-react-svg" + ] + } + } +} diff --git a/.eslintignore b/.eslintignore index 5be912f..f1d08f7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,5 +8,7 @@ /tests/fixtures/** /tests/performance/** /tmp/** +next.config.js + # Add any other files or folders that you want eslint to ignore diff --git a/.eslintrc.json b/.eslintrc.json index 73d11d8..8fb3f49 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,7 +8,8 @@ "browser": true, "commonjs": true, "es6": true, - "node": true + "node": true, + "jest": true }, "parser": "babel-eslint", "parserOptions":{ diff --git a/.gitignore b/.gitignore index 188d7f4..65611f7 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,7 @@ build/Release node_modules/ jspm_packages/ package-lock.json - +.DS_Store # TypeScript v1 declaration files typings/ diff --git a/.hound.yml b/.hound.yml deleted file mode 100644 index cd9b2c9..0000000 --- a/.hound.yml +++ /dev/null @@ -1,4 +0,0 @@ -eslint: - enabled: true - config_file: .eslintrc - ignore_file: .eslintignore diff --git a/.travis.yml b/.travis.yml index 2197832..dff50cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,6 @@ language: node_js node_js: - "node" +script: + - npm run lint + - npm test diff --git a/README.md b/README.md index 26f035b..2d2df88 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,13 @@ -# project-template -Document your applications API and general usage +# Project Help Me # + +Help Me is an app that connects Depressed people to each other, essentially, it is a social network for depressed people + +## Development ## + +* clone the repo * +* ```cd helpme``` +* ```npm install``` +* ```npm run dev``` + + +Antdesign is also used for the components, check diff --git a/components/Authentication/components/Authentication.css b/components/Authentication/components/Authentication.css new file mode 100644 index 0000000..b37fad3 --- /dev/null +++ b/components/Authentication/components/Authentication.css @@ -0,0 +1,118 @@ +.login-form , .password-form{ + max-width: 300px; + margin: 1em auto; + height: 60vh; + display: grid; + place-content: center; +} + +.login-form-forgot, +.login-form-register,.password-form-register { + color: #1890ff; +} + +.login-form-button, .password-form-button { + width: 100%; +} + +.login-image-section, +.registration-image-section, +.password-image-section { + background: #1890ff; + /**fallback for old browsers*/ + background: -webkit-linear-gradient(to right, #1890ff, #40a9ff); + /**Chrome 10-25, Safari 5.1-6*/ + background: linear-gradient(to top right, #1890ff, #f6f7f8fb); + height: 40vh; +} + +.login-image, +.registration-image, +.password-image { + width: 100%; + height: 80%; +} + +.registration-image-section { + height: 20vh; +} + +.registration-image { + width: 100%; + height: 120%; +} + +.registration-form-section { + height: 80vh; + margin: 0 auto; +} + +.registration-form { + width: 90%; + margin: 1em auto; +} + +.form_icon{ + color: rgba(0,0,0,.25) +} + +@media screen and (max-width: 798px) { + .login-image-section, + .password-image-section { + position: relative; + } + + .login-image, + .password-image { + position: absolute; + bottom: -33px; + } + + .registration-form { + max-width: 300px; + margin: 0 auto + } +} + +@media screen and (min-width: 799px) { + .registration-form { + max-width: 300px; + margin: 0 auto + } + + .Login-Section, + .Registration-Section, + .password-Section { + display: flex; + } + + .Login-Form-section, + .password-Form-section { + display: grid; + place-items: center; + width: 50vw; + } + + section .login-form, + section .password-form { + max-width: 50vw; + place-content: center; + display: grid; + } + + .login-image, + .password-image, + .registration-image { + padding: 0 2em; + max-width: 35vw; + height: 80% + } + + .login-image-section,.password-image-section, + .registration-image-section { + height: 100vh; + width: 50vw; + display: grid; + place-items: center; + } +} diff --git a/components/Authentication/components/Authentication.test.jsx b/components/Authentication/components/Authentication.test.jsx new file mode 100644 index 0000000..5382b5d --- /dev/null +++ b/components/Authentication/components/Authentication.test.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Login, SignUp } from './index'; +import LoginInputItemGenerator from './LoginInputItemGenerator'; + +describe('Authenticcation', () => { + it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + }); + + it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + }); +}); diff --git a/components/Authentication/components/Login.jsx b/components/Authentication/components/Login.jsx new file mode 100644 index 0000000..054f93f --- /dev/null +++ b/components/Authentication/components/Login.jsx @@ -0,0 +1,90 @@ +/* eslint-disable react/jsx-no-literals */ +import React from 'react'; +import { Form, Button, Typography } from 'antd'; +import 'antd/dist/antd.css'; +import './Authentication.css'; +import Router from 'next/router'; +import LoginImage from '../../../static/login.svg'; +import LoginInputItemGenerator from './LoginInputItemGenerator'; + +const { Title, Paragraph } = Typography; + +/** + * function that is used to display the Login Page + * @function + * @return {Object} the login page + */ +class NormalLoginForm extends React.Component { + state = { + iconLoading: false, // loading icon when code is accessing network + loading: false, + } + + /** + * function that is used to animate signup loading + * @function + * @return {Object} sets loading state to true + */ + enterLoading = () => { + this.setState({ loading: true }); + } + + /** + * function that is used to handle submit + * @function + * @return {Object} returns the user values + */ + handleSubmit = e => { + const { validateFields } = this.props.form; + e.preventDefault(); + + /** + * function that is used to handle submit, This function helps to Validate the + * specified fields and get theirs values and errors., if the target field is not in + * visible area of form, form will be automatically scrolled to the target field area. + * @function + * @return {Object} returns the values of the form + */ + validateFields((err, values) => { + if (!err) { + setTimeout(() => { + Router.push('/timeline'); + }, 1000); + } + }); + } + + render() { + const { getFieldDecorator } = this.props.form; + const { loading } = this.state; + + return ( +
+
+ +
+ +
+
+ Welcome + Login to continue + {/* inputs for username and password */} + {LoginInputItemGenerator(getFieldDecorator)} + {/* buttons */} + + Forgot password + + or + register now! + +
+
+
+ ); + } +} + +const Login = Form.create({ name: 'normal_login' })(NormalLoginForm); +export default Login; diff --git a/components/Authentication/components/LoginInputItemGenerator.jsx b/components/Authentication/components/LoginInputItemGenerator.jsx new file mode 100644 index 0000000..f2e04a4 --- /dev/null +++ b/components/Authentication/components/LoginInputItemGenerator.jsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { + Form, Icon, Input, message +} from 'antd'; +import PropTypes from 'prop-types'; + +import { + LOGIN_INPUTS +} from '../constants'; + +/** +* function that is used to also handle password validation, this compares the two password field; +* @function +* @param {string} className class of the input field +* @param {string} id id of the input field +* @param {string} iconType preceding icon of the input field +* @param {function} decorator a Two-way binding for form +* @param {string} placeholder placeholder of the input field +* @param {Object[]} rules rules for input validation +* @param {string} inputType type of the input element +* @return {function} input item of the form +*/ + +const LoginInputItemGenerator = decorator => LOGIN_INPUTS.map(input => { + const { + className, + id, + iconType, + placeholder, + rules, + inputType, + } = input; + + return ( + + { + decorator(id, { + rules, + })( + } placeholder={placeholder} type={inputType} /> + ) + } + + ); +}); + +export default LoginInputItemGenerator; + +LoginInputItemGenerator.propTypes = { + className: PropTypes.string, + field: PropTypes.string, + iconType: PropTypes.string, + inputType: PropTypes.string, + placeholder: PropTypes.string, + rules: PropTypes.arrayOf(PropTypes.shape({ + message: PropTypes.string.isRequired, + required: PropTypes.bool.isRequired, + })).isRequired, +}; diff --git a/components/Authentication/components/SignUp.jsx b/components/Authentication/components/SignUp.jsx new file mode 100644 index 0000000..6cb0d24 --- /dev/null +++ b/components/Authentication/components/SignUp.jsx @@ -0,0 +1,124 @@ +import React from 'react'; +import { + Form +} from 'antd'; +import 'antd/dist/antd.css'; +import './Authentication.css'; +import Router from 'next/router'; +import RegistrationImage from '../../../static/register.svg'; +import { + SIGNUP_INPUTS +} from '../constants'; +import SignupInputGenerator from './SignupInputItemGenerator'; + +/** + * function that is used to display the registration Page + * @function + * @return {Object} the registration page + */ +class RegistrationForm extends React.Component { + state = { + confirmDirty: false, + autoCompleteResult: [], + iconLoading: false, + loading: false, + isAgreementChecked: false, + }; + + /** + * function that is used to animate signup loading + * @function + * @return {Object} sets loading state to true + */ + enterLoading = () => { + this.setState({ loading: true }); + } + + /** + * function that is used to handle submit + * @function + * @return {Object} returns the user values + */ + handleSubmit = e => { + e.preventDefault(); + const { validateFieldsAndScroll } = this.props.form; + + /** + * function that is used to handle submit, This function helps to Validate the specified + * fields and get theirs values and errors., if the target field is not in visible area of form, form will be automatically scrolled to the target field area. + * @function + * @return {Object} returns the values of the form + */ + validateFieldsAndScroll((err, values) => { + if (!err && values.agreement) { + this.enterLoading(); + setTimeout(() => { + Router.push('/timeline'); + }, 1000); + } + }); + } + + /** + * function that is used to handle password validation, this fires when the first password field has been filled and has lost focus. this will help in comparing the password in that field to the next input field; + * @function + * @return {Object} sets the state of isConfirmedDirty + */ + handleConfirmBlur = e => { + const { value } = e.target; + this.setState({ confirmDirty: this.state.confirmDirty || !!value }); + } + + /** + * function that is used to also handle password validation, this compares the two password field; + * @function + * @param {Array} rule the validation rule for the input field + * @param {String} value the value passed on the input field + * @param {function} callback error message to display + * @return {function} error message to display + */ + compareToFirstPassword = (rule, value, callback) => { + const { form } = this.props; + if (value && value !== form.getFieldValue('password')) { + callback('The Two passwords that you enter is inconsistent!'); + } else { + callback(); + } + } + + /** +* function that is used to handle checkbox agreement +* @function +* @return {Boolean} controls the state of of the checkbox +*/ + handleCheckBox = e => { + this.setState({ + isAgreementChecked: e.target.checked, + }); + } + + render() { + const { getFieldDecorator } = this.props.form; + return ( +
+
+ +
+ +
+
+ { + SIGNUP_INPUTS.map(input => { + const { actions = {}, items } = input; + return SignupInputGenerator(actions, items, getFieldDecorator); + }) + } +
+
+
+ ); + } +} + +const Signup = Form.create({ name: 'register' })(RegistrationForm); +export default Signup; diff --git a/components/Authentication/components/SignupInputItemGenerator.jsx b/components/Authentication/components/SignupInputItemGenerator.jsx new file mode 100644 index 0000000..1cb35e9 --- /dev/null +++ b/components/Authentication/components/SignupInputItemGenerator.jsx @@ -0,0 +1,104 @@ +import React from 'react'; +import { + Form, Input, Checkbox, Button +} from 'antd'; +import PropTypes from 'prop-types'; + +/** +* function that is used to also handle password validation, this compares the two password field; +* @function +* @param {function} actions actions attached to the input field for validation +* @param {Object} items the values passed on to the input field +* @param {function} decorator a Two-way binding for form +* @param {string} label label for the input field +* @param {string} id id of the input field +* @param {Object[]} rules rules for input validation +* @param {Boolean} hasOnBlur check if the input has an onChange function attached ot it +* @param {Boolean} hasOnChange check if the input has an onChange function attached ot it +* @param {function} valuePropName Props of checkbox +* @param {Boolean} isButton check if the form element is a button +* @param {Boolean} hasFieldChildren check if the input children +* @param {function} FieldType type of html form element +* @return {function} input item of the form +*/ +const SignupInputGenerator = (actions, items, decorator) => { + const { + label, id, rules, hasOnBlur, hasOnChange, valuePropName, isButton, hasFieldChildren, FieldType, + } = items; + const { onBlur, onChange } = actions; + const actionProps = { + onBlur: hasOnBlur && onBlur, + onChange: hasOnChange && onChange, + }; + + let Field; + let fieldChildren; + + switch (FieldType) { + case 'input': + Field = Input; + break; + case 'checkbox': + Field = Checkbox; + break; + case 'button': + Field = Button; + break; + default: + Field = null; + break; + } + + if (isButton && hasFieldChildren) { + fieldChildren = ( +
+ already have an account, please + login +
+ ); + } else if (FieldType === 'checkbox' && hasFieldChildren) { + fieldChildren = ( + + I have read and accepted the + agreement + + ); + } + + return ( + + { + isButton ? ( + <> + Register + {fieldChildren} + + ) + : decorator(id, { rules }, { valuePropName })( + + {hasFieldChildren ? fieldChildren : null} + + ) + } + + ); +}; + +export default SignupInputGenerator; + +SignupInputGenerator.propTypes = { + FieldType: PropTypes.elementType, + hasFieldChildren: PropTypes.bool, + hasOnBlur: PropTypes.bool, + hasOnChange: PropTypes.bool, + id: PropTypes.string, + rules: PropTypes.arrayOf(PropTypes.shape({ + message: PropTypes.string.isRequired, + required: PropTypes.bool.isRequired, + type: PropTypes.string.isRequired, + whitespace: PropTypes.bool.isRequired, + })).isRequired, + isButton: PropTypes.bool, + label: PropTypes.string, + valuePropName: PropTypes.string, +}; diff --git a/components/Authentication/components/index.js b/components/Authentication/components/index.js new file mode 100644 index 0000000..f1e8d33 --- /dev/null +++ b/components/Authentication/components/index.js @@ -0,0 +1,4 @@ +import Login from './Login'; +import SignUp from './SignUp'; + +export { Login, SignUp }; diff --git a/components/Authentication/constants.js b/components/Authentication/constants.js new file mode 100644 index 0000000..adf355c --- /dev/null +++ b/components/Authentication/constants.js @@ -0,0 +1,97 @@ +const LOGIN_INPUTS = [ + { + className: 'form_icon', + iconType: 'user', + id: 'username', + placeholder: 'Username', + rules: [{ + message: 'Please input your username!', + required: true, + }], + }, { + className: 'form_icon', + iconType: 'lock', + id: 'password', + inputType: 'password', + placeholder: 'Password', + rules: [{ + message: 'Please input your Password!', + required: true, + }], + }, +]; + +const SIGNUP_INPUTS = [ + { + items: { + FieldType: 'input', + hasFieldChildren: false, + id: 'name', + label: 'Name', + rules: [{ message: 'Please input your name!', required: true, whitespace: true }], + }, + }, + + { + items: { + FieldType: 'input', + hasFieldChildren: false, + id: 'email', + label: 'E-mail', + rules: [{ message: 'The input is not a valid E-mail!', type: 'email' }, { + message: 'Please input your E-mail!', required: true, + }], + }, + }, + + { + items: { + FieldType: 'input', + hasFieldChildren: false, + id: 'password', + label: 'Password', + rules: [{ message: 'The input is not a valid E-mail!', required: true }, + // { validator: validateToNextPassword } + ], + }, + }, + + { + items: { + FieldType: 'input', + hasFieldChildren: false, + id: 'confirm', + label: 'Confirm Password', + rules: [{ message: 'Please confirm your password!', required: true }, + // { validator: compareToFirstPassword }, + ], + }, + }, + + { + items: { + FieldType: 'checkbox', + hasFieldChildren: true, + id: 'agreement', + rules: [{ message: 'Please accept the agreement ', required: true }, + ], + valuePropName: 'checked', + + }, + }, + + { + items: { + FieldType: 'button', + hasFieldChildren: true, + id: 'submit', + isButton: true, + }, + }, + +]; + +export { + LOGIN_INPUTS, + SIGNUP_INPUTS +}; diff --git a/components/Authentication/index.js b/components/Authentication/index.js new file mode 100644 index 0000000..76dd08f --- /dev/null +++ b/components/Authentication/index.js @@ -0,0 +1,3 @@ +import { Login, SignUp } from './components'; + +export { Login, SignUp }; diff --git a/components/LandingPage/components/LandingPage.css b/components/LandingPage/components/LandingPage.css new file mode 100644 index 0000000..46ec66d --- /dev/null +++ b/components/LandingPage/components/LandingPage.css @@ -0,0 +1,123 @@ +.LandingPage_footer { + background: #001529; + color: #ffffff; + text-align: center; +} + +.LandingPage_footer > img { + width: 50%; + height: 50%; + margin-bottom: 1em; +} + +.LandingPage_footer > ul { + list-style-type: none; + padding: 0; +} + +.LandingPage_hero { + padding: 24px; + background: #ffffff; + display: flex; + flex-direction: column; + justify-content: center; +} + +.LandingPage_hero > img { + width: 100%; + margin: 2em 0; +} +.LandingPage_content__text { + text-align: center; +} + +.LandingPage_button { + margin-bottom: 2em; +} + +.column-section > img { + width: 50%; + margin: 0 auto; +} + +@media screen and (min-width: 425px) and (max-width: 767px) { + .LandingPage_hero > img { + width: 65%; + margin: 2em auto; + } + + .column-section > img { + width: 30%; + margin-top: 0; + } +} + +@media screen and (min-width: 768px) { + + + .LandingPage_hero { + flex-direction: row; + align-items: center; + } + .LandingPage_content__text { + margin: 2em; + } + .LandingPage_hero > img { + width: 40%; + } + + .LandingPage_body > section { + margin: 0 6em; + } + + .reverse { + flex-direction: row-reverse; + } + + .column-section { + flex-direction: column; + } + + .column-section > div { + margin-bottom: 0; + } + + .column-section > img { + width: 20%; + margin-top: 0; + } + + + + .LandingPage_footer { + text-align: left; + display: flex; + justify-content: space-evenly; + padding: 5em 50px; + } + + .LandingPage_content { + height: 100%; + margin-top: 0; + } + + .LandingPage_footer > img { + width: 120px; + height: 30px; + margin-bottom: 0; + } + + +} + +@media screen and (min-width: 1024px) { + .LandingPage_body > section { + /* padding: 0 3em; */ + } + + .layout_header-desktop { + padding: 0 6em; + } + + +} diff --git a/components/LandingPage/components/LandingPage.jsx b/components/LandingPage/components/LandingPage.jsx new file mode 100644 index 0000000..ea0a9dc --- /dev/null +++ b/components/LandingPage/components/LandingPage.jsx @@ -0,0 +1,59 @@ +import React from 'react'; +import 'antd/dist/antd.css'; +import { Layout, Divider } from 'antd'; +import './LandingPage.css'; +import Paragraph from 'antd/lib/typography/Paragraph'; +import LandingPageContent from './LandingPageContent'; +import PageLayout from '../../Layout'; +import { LANDING_PAGE_CONTENTS, pageTitle } from '../constants'; + +/** + * Function for displaying the landing page + * + * @function + * @return {Object} The landing page + */ + +const LandingPage = () => ( + + { + LANDING_PAGE_CONTENTS.map(landingPageContent => { + const { + paragraphText, + isButtonPresent, + columnSection, + isImagePresent, + imageLink, + level, + title, + reverseSection, + buttonText, + buttonLink, + } = landingPageContent; + + return ( + + ); + }) + } + +); + +export default LandingPage; diff --git a/components/LandingPage/components/LandingPage.test.jsx b/components/LandingPage/components/LandingPage.test.jsx new file mode 100644 index 0000000..23bbbe2 --- /dev/null +++ b/components/LandingPage/components/LandingPage.test.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import LandingPage from './index'; + +describe('LandingPage', () => { + it('should renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + }); +}); diff --git a/components/LandingPage/components/LandingPageContent.jsx b/components/LandingPage/components/LandingPageContent.jsx new file mode 100644 index 0000000..0cbcb9e --- /dev/null +++ b/components/LandingPage/components/LandingPageContent.jsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { Button, Typography } from 'antd'; +import PropTypes from 'prop-types'; +import { normalize } from 'path'; +import Link from 'next/link'; + +const { Title, Paragraph } = Typography; + +/** + * Function used to generate section layout content for landing page + * @function + * @param {Number} level - The Number from 1-5 representing the header level h1-h5 + * @param {String} title- The Title of that Section + *@param {String} paragraphText- The Text of that section + * @param {Boolean} isButtonPresent- If true, a button is shown on that section + * @param {String} buttonText- the text on the button + * @param {String} buttonLink- the link that the button leads to + * @param {String} imageLink- the link to an image in the section + * @param {Boolean} reverseSection - if true, the image and section position is swapped + * @param {Boolean} isImagePresent - if true, the image is shown + * @param {Boolean} columnSection - if true, the section will be stacked + * @return {Object} The landing page content component which is used to populate the landing page + */ + +export default function LandingPageContent(props) { + const { + level, + title, + paragraphText, + isButtonPresent, + buttonText, + buttonLink, + imageLink, + isImagePresent, + reverseSection, + columnSection, + } = props; + + let className; + + // this helps to structure the section, the section can be normalize, reversed or columnized + if (!reverseSection && !columnSection) { + className = 'LandingPage_hero'; + } else if (reverseSection && !columnSection) { + className = 'LandingPage_hero reverse'; + } else if (columnSection) { + className = 'LandingPage_hero column-section'; + } + + return ( +
+
+ {title} + {paragraphText} + {/* displays button in a section */} + { + isButtonPresent ? ( + + ) : null + } +
+ + {/* displays image in a section */} + {isImagePresent ? {`${title} : null} +
+ ); +} +LandingPageContent.propTypes = { + level: PropTypes.number, + title: PropTypes.string, + paragraphText: PropTypes.string, + isButtonPresent: PropTypes.bool, + buttonText: PropTypes.string, + buttonLink: PropTypes.string, + isImagePresent: PropTypes.bool, + imageLink: PropTypes.string, + reverseSection: PropTypes.bool, + columnSection: PropTypes.bool, +}; diff --git a/components/LandingPage/components/index.js b/components/LandingPage/components/index.js new file mode 100644 index 0000000..7b923f9 --- /dev/null +++ b/components/LandingPage/components/index.js @@ -0,0 +1,4 @@ + +import LandingPage from './LandingPage'; + +export default LandingPage; diff --git a/components/LandingPage/constants.js b/components/LandingPage/constants.js new file mode 100644 index 0000000..4044417 --- /dev/null +++ b/components/LandingPage/constants.js @@ -0,0 +1,81 @@ +/* eslint-disable max-len */ +const PAGE_TITLE = 'Home | Welcome to Help me'; // the title of the landing page + +const LANDING_PAGE_MAIN_CONTENT_TITLE = 'Help me Title'; + +const LANDING_PAGE_MAIN_CONTENT_PARAGRAPH_TEXT = 'Lorem ipsum dolor sit amet consectetur adipisicing elit.Deleniti porro vero'; + +const LANDING_PAGE_MAIN_CONTENT_BUTTON_TEXT = 'Lets begin this Journey'; + +const LANDING_PAGE_LEVEL_2_PARAGRAPH_TEXT = 'Lorem ipsum dolor sit amet consectetur adipisicing elit.Deleniti porro veroDeleniti porro vero'; + +const LANDING_PAGE_LEVEL_2_BUTTON_TEXT = 'Lets begin this Journey'; + +const LANDING_PAGE_LEVEL_3_CONTENT_TITLE = 'Lorem Ipsum dolor sit a '; + +const LANDING_PAGE_LEVEL_3_PARAGRAPH_TEXT = 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Deleniti porro vero'; + +const LANDING_PAGE_LEVEL_4_CONTENT_TITLE = 'Title 2'; + +const LANDING_PAGE_LEVEL_4_PARAGRAPH_TEXT = 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Deleniti Lorem ipsum dolor sit amet consectetur adipisicing elit. Deleniti Lorem ipsum dolor sit amet consectetur adipisicing elit. Deleniti porro vero'; + +const LANDING_PAGE_LEVEL_5_CONTENT_TITLE = 'Lorem Ipsum dolor sit a '; + +const LANDING_PAGE_LEVEL_5_PARAGRAPH_TEXT = 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Deleniti porro vero Lorem ipsum dolor sit amet consectetur adipisicing elit. Deleniti porro vero Lorem ipsum dolor sit amet consectetur adipisicing elit. Deleniti porro vero Lorem ipsum dolor sit amet consectetur adipisicing elit. Deleniti porro vero Lorem ipsum dolor sit amet consectetur adipisicing elit. Deleniti porro vero v Lorem ipsum dolor sit amet consectetur adipisicing elit. Deleniti porro vero'; + +const LANDING_PAGE_LEVEL_5_BUTTON_TEXT = 'Create an Account'; + +const LANDING_PAGE_CONTENTS = [ + { + buttonLink: '/signup', + buttonText: LANDING_PAGE_MAIN_CONTENT_BUTTON_TEXT, + imageLink: '../../../static/connected.svg', + isButtonPresent: true, + isImagePresent: true, + level: 1, + paragraphText: LANDING_PAGE_MAIN_CONTENT_PARAGRAPH_TEXT, + title: LANDING_PAGE_MAIN_CONTENT_TITLE, + }, + { + columnSection: true, + imageLink: '../../../static/smile.svg', + isButtonPresent: false, + isImagePresent: true, + level: 2, + paragraphText: LANDING_PAGE_LEVEL_2_PARAGRAPH_TEXT, + title: LANDING_PAGE_LEVEL_2_BUTTON_TEXT, + + }, + { + imageLink: '../../../static/community.svg', + isButtonPresent: false, + isImagePresent: true, + level: 3, + paragraphText: LANDING_PAGE_LEVEL_3_PARAGRAPH_TEXT, + reverseSection: true, + title: LANDING_PAGE_LEVEL_3_CONTENT_TITLE, + }, + { + isButtonPresent: false, + isImagePresent: false, + level: 2, + paragraphText: LANDING_PAGE_LEVEL_4_PARAGRAPH_TEXT, + title: LANDING_PAGE_LEVEL_4_CONTENT_TITLE, + }, + { + buttonLink: '/signup', + buttonText: LANDING_PAGE_LEVEL_5_BUTTON_TEXT, + imageLink: '../../../static/hangout.svg', + isButtonPresent: true, + isImagePresent: true, + level: 3, + paragraphText: LANDING_PAGE_LEVEL_5_PARAGRAPH_TEXT, + reverseSection: false, + title: LANDING_PAGE_LEVEL_5_CONTENT_TITLE, + }, +]; + +export { + PAGE_TITLE, + LANDING_PAGE_CONTENTS +}; diff --git a/components/LandingPage/index.js b/components/LandingPage/index.js new file mode 100644 index 0000000..e5e516f --- /dev/null +++ b/components/LandingPage/index.js @@ -0,0 +1,3 @@ +import LandingPage from './components/index'; + +export default LandingPage; diff --git a/components/Layout/components/FooterListCreator.jsx b/components/Layout/components/FooterListCreator.jsx new file mode 100644 index 0000000..042c6bf --- /dev/null +++ b/components/Layout/components/FooterListCreator.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import Link from 'next/link'; +import PropTypes from 'prop-types'; + +const FooterListCreator = props => { + const { list } = props; + return ( +
    + { + list.map(link => { + const { href, text } = link; + return ( + +
  • + {text} +
  • + + ); + }) + } +
+ ); +}; +export default FooterListCreator; +FooterListCreator.propTypes = { + list: PropTypes.arrayOf(PropTypes.shape({ + href: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + })).isRequired, +}; diff --git a/components/Layout/components/NavHeader.jsx b/components/Layout/components/NavHeader.jsx new file mode 100644 index 0000000..5e88ca9 --- /dev/null +++ b/components/Layout/components/NavHeader.jsx @@ -0,0 +1,116 @@ +import React from 'react'; +import Head from 'next/head'; +import 'antd/dist/antd.css'; +import { + Layout, Menu, Button, Input +} from 'antd'; +import Link from 'next/link'; +import PropTypes from 'prop-types'; +import { HEADER_TITLE, MENU_ITEMS } from '../constants'; + +const { Header } = Layout; +const { Search } = Input; + +/** + * Head function that is infused into all pages and controls page's title + * @function + * @param {String} title - The title of the currently viewed page + * @return {Object} head metadata which is inserted in every page + */ +function NavHeader(props) { + const { title } = props; + let isAuthenticated; + // fake Authentication for development + if (global.location !== undefined && global.location.pathname === '/') { + isAuthenticated = false; + } else { + isAuthenticated = true; + } + + return ( + <> + {/* head parametes */} + + + + + + + + {!title ? HEADER_TITLE : title} + + {/* navheader for mobile */} +
+ + + helpme logo + + + {/* hide when authenticated */} + {isAuthenticated ? null : ( + + )} +
+ {/* header for desktop */} +
+ + + helpme logo + + + {isAuthenticated ? ( + <> + {/* search */} + console.log(value)} + style={{ width: 200 }} + /> + {/* navbar for authenticated desktop */} + + { + MENU_ITEMS.map(menuItem => { + const { key, href, text } = menuItem; + return ( + + + {text} + + + ); + }) + } + + + + ) : ( + + )} +
+ + ); +} +export default NavHeader; +Head.propTypes = { + title: PropTypes.string, +}; diff --git a/components/Layout/components/PageFooter.jsx b/components/Layout/components/PageFooter.jsx new file mode 100644 index 0000000..6aa9689 --- /dev/null +++ b/components/Layout/components/PageFooter.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import Link from 'next/link'; +import { Layout } from 'antd'; +import { FOOTER_FIRST_COLUMN, FOOTER_SECOND_COLUMN } from '../constants'; +import FooterListCreator from './FooterListCreator'; + +const { Content, Footer } = Layout; + +/** + * footer function that is infused into all pages + * @function + * @return {Object} footer + */ +export default function PageFooter() { + return ( + + + + + + ); +} diff --git a/components/Layout/components/PageLayout.css b/components/Layout/components/PageLayout.css new file mode 100644 index 0000000..4bc2549 --- /dev/null +++ b/components/Layout/components/PageLayout.css @@ -0,0 +1,93 @@ +.PageLayout_content { + height: 100%; + margin-top: 64px; +} + +.layout_header-mobile { + display: flex; + justify-content: space-between; + background-color: #ffffff; + border-bottom: 0.4px solid #e8e8e8; + position: fixed; + width: 100%; + z-index: 1; + align-items: center; +} + +.layout_header-desktop { + display: none; +} + +.layout_sider { + position: fixed; + height: calc(100vh - 64px); + z-index: 1; + margin-top: 64px; +} + +.ant-layout { + background: #ffffff; +} + +.ant-layout-sider-zero-width-trigger { + top: 12px; + position: fixed; + background-color: #1890ff; + left: 0; +} + +.ant-layout-sider-zero-width-trigger:hover { + background-color: #1890ff; +} + +a { + color: #ffffff; +} + +.ant-divider-horizontal { + margin: 0.2px; +} + +.logo { + width: 120px; + height: 30px; +} + +@media screen and (min-width: 768px) { + .PageLayout_body { + /* overflow: auto; */ +background: #E6ECF0; + } + .PageLayout_content { + margin-top: 63px; + background-color: #e8e8e8 +} +.layout_sider { + display: none; + } + +.layout_header-mobile { + display: none; + } + .layout_header-desktop { + background-color: #ffffff; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #e8e8e8; + position: fixed; + width: 100%; + z-index: 3; + padding: 0 1em; + } + + .layout_header-list { + line-height: 63px; + } +} + +@media screen and (min-width: 1024px) { + .layout_header-desktop { + padding: 0 6em; + } +} diff --git a/components/Layout/components/PageLayout.jsx b/components/Layout/components/PageLayout.jsx new file mode 100644 index 0000000..b8c2b22 --- /dev/null +++ b/components/Layout/components/PageLayout.jsx @@ -0,0 +1,47 @@ +/* eslint-disable react/require-default-props */ +import React from 'react'; +import { Layout } from 'antd'; +import PropTypes from 'prop-types'; +import NavHeader from './NavHeader'; +import Sidebar from './Sidebar'; +import PageFooter from './PageFooter'; +import './PageLayout.css'; +import { headerTitle } from '../constants'; + +const { Content } = Layout; +/** + * Function for displaying the landing page + * @function + * @param {Function} title controls the title of the page + * @param {Function} isAuthenticated controls if user is authrnticated or not + * @param {Function} children other pages who are children of this layout + * @param {Function} isFooterPresent displays footer if true + * @param {Function} isSiderPresent displays side for mobile pages + * @return {Object} control the over all layout of the webpage + */ +export default function PageLayout(props) { + const { + title, isAuthenticated, children, isFooterPresent, isSiderPresent, + } = props; + return ( + <> + + + + + + {children} + + + + {isFooterPresent ? : null} + + ); +} +PageLayout.propTypes = { + children: PropTypes.node, + isAuthenticated: PropTypes.bool, + isFooterPresent: PropTypes.bool, + isSiderPresent: PropTypes.bool, + title: PropTypes.string, +}; diff --git a/components/Layout/components/PageLayout.test.jsx b/components/Layout/components/PageLayout.test.jsx new file mode 100644 index 0000000..075aa0d --- /dev/null +++ b/components/Layout/components/PageLayout.test.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import NavHeader from './NavHeader'; +import PageFooter from './PageFooter'; +import Sidebar from './Sidebar'; +import PageLayout from './index'; + +describe('PageLayout , NavHeader, PageFooter,Sidebar and PageLayout', () => { + it('NavHeader should render without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + }); + + it('PageFooter should render without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + }); + + it('Sidebar should render without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + }); + + it('PageLayout should render without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + }); +}); diff --git a/components/Layout/components/Sidebar.jsx b/components/Layout/components/Sidebar.jsx new file mode 100644 index 0000000..e1d164a --- /dev/null +++ b/components/Layout/components/Sidebar.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Layout, Menu, Icon } from 'antd'; +import Link from 'next/link'; +import PropTypes from 'prop-types'; +import { sideBarMenuItems } from '../constants'; + +const { Sider } = Layout; +/** + * Function that controls the sidebar which displays on mobile + * @function + * @param {boolean} IsSiderPresent shows sidebar if true + * @return {Object} Side Bar + */ +export default function Sidebar(props) { + const { IsSiderPresent } = props; + return IsSiderPresent ? ( + +
+ + { + sideBarMenuItems.map(sideBarItem => { + const { + key, href, type, text, + } = sideBarItem; + return ( + + + + + {text} + + + + ); + }) + } + + + ) : null; +} + +Sider.propTypes = { + IsSiderPresent: PropTypes.bool, +}; diff --git a/components/Layout/components/index.js b/components/Layout/components/index.js new file mode 100644 index 0000000..6b80de7 --- /dev/null +++ b/components/Layout/components/index.js @@ -0,0 +1,3 @@ +import PageLayout from './PageLayout'; + +export default PageLayout; diff --git a/components/Layout/constants.js b/components/Layout/constants.js new file mode 100644 index 0000000..2919101 --- /dev/null +++ b/components/Layout/constants.js @@ -0,0 +1,64 @@ +// eslint-disable-next-line import/prefer-default-export +const HEADER_TITLE = 'Helpme | Connect with Friends'; // title of the header + +const MENU_ITEMS = [ + { + href: '/', + key: 1, + text: 'Home', + }, { + href: '/forum', + key: 2, + text: 'Forum', + }, { + href: '/Dairy', + key: 3, + text: 'Dairy', + }, +]; + +const FOOTER_FIRST_COLUMN = [ + { + href: '/#', + text: 'Home', + }, { + href: '/contact', + text: 'Contact us', + }, { + href: '/about-us', + text: 'About Helpme', + }, +]; + +const FOOTER_SECOND_COLUMN = [ + { + href: '/about-us', + text: 'Security & Privacy', + }, { + href: '/terms', + text: 'Terms Of Service', + }, +]; + +const SIDEBAR_MENU_ITEMS = [ + { + href: '/#', + key: 1, + text: 'Home', + type: 'user', + }, { + href: '/forum', + key: 2, + text: 'Forum', + type: 'video-camera', + }, { + href: '/dairy', + key: 3, + text: 'Dairy', + type: 'upload', + }, +]; + +export { + HEADER_TITLE, MENU_ITEMS, FOOTER_FIRST_COLUMN, FOOTER_SECOND_COLUMN, SIDEBAR_MENU_ITEMS +}; diff --git a/components/Layout/index.js b/components/Layout/index.js new file mode 100644 index 0000000..0728d99 --- /dev/null +++ b/components/Layout/index.js @@ -0,0 +1,3 @@ +import PageLayout from './components/index'; + +export default PageLayout; diff --git a/config/keys.js b/config/keys.js new file mode 100644 index 0000000..ea0b581 --- /dev/null +++ b/config/keys.js @@ -0,0 +1,3 @@ +module.exports = { + mongoURI: 'mongodb://enyehelpme:enyehelpme123@ds147946.mlab.com:47946/helpme_db', +}; diff --git a/empty.js b/empty.js new file mode 100644 index 0000000..16f0846 --- /dev/null +++ b/empty.js @@ -0,0 +1 @@ +// used to map css files so that jest test can ingnore css; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..0367f00 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,22 @@ +const TEST_REGEX = '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|js?|tsx?|ts?)$'; + +module.exports = { + collectCoverage: true, + globals: { + jest: { + preset: '@shelf/jest-mongodb', + useBabelrc: true, + }, + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + moduleNameMapper: { + '\\.(css|jpg|png|scss|less|sass)$': '/empty.js', + }, + setupFiles: ['/jest.setup.js'], + testPathIgnorePatterns: ['/.next/', '/node_modules/'], + testRegex: TEST_REGEX, + transform: { + '^.+\\.jsx?$': 'babel-jest', + }, +}; + diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 0000000..135148a --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,4 @@ +const Enzyme = require('enzyme'); +const Adapter = require('enzyme-adapter-react-16'); + +Enzyme.configure({ adapter: new Adapter() }); diff --git a/models/User.js b/models/User.js new file mode 100644 index 0000000..8c9b3da --- /dev/null +++ b/models/User.js @@ -0,0 +1,29 @@ +const mongoose = require('mongoose'); + +const { Schema } = mongoose; +const UserSchema = new Schema({ + // If there is an associated avatar it will show a placeholder avatar for images + avatar: { + type: String, + }, + date: { + default: Date.now, + type: Date, + }, + email: { + required: true, + type: String, + }, + + name: { + required: true, + type: String, + }, + + password: { + required: true, + type: String, + }, +}); + +module.exports = mongoose.model('users', UserSchema); diff --git a/models/profile.model.js b/models/profile.model.js new file mode 100644 index 0000000..14a3a48 --- /dev/null +++ b/models/profile.model.js @@ -0,0 +1,46 @@ +const mongoose = require('mongoose'); + +const { Schema } = mongoose; + +const profileSchema = new Schema({ + city: { + required: true, + type: String, + }, + country: { + required: true, + type: String, + }, + email: { + lowercase: true, + required: true, + type: String, + unique: true, + }, + firstName: { + required: true, + type: String, + }, + followers: { + default: 0, + type: Number, + }, + following: { + default: 0, + type: Number, + }, + groups: { + default: 0, + type: Number, + }, + image: { + contentType: String, + data: Buffer, + }, + lastName: { + required: true, + type: String, + }, +}); + +module.exports = mongoose.model('Profile', profileSchema); diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..c576301 --- /dev/null +++ b/next.config.js @@ -0,0 +1,3 @@ +const withCSS = require('@zeit/next-css'); + +module.exports = withCSS(); diff --git a/package.json b/package.json index bcf782f..e317d20 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,32 @@ { - "name": "project-template", + "name": "HelpMe", "version": "1.0.0", - "description": "Template for starting a project", + "description": "An App That connects depressed people to each other for comfort", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 0", - "lint": "eslint pages" + "test": "jest", + "lint": "eslint . --fix", + "dev": "node server/index.js", + "build": "next build", + "start": "next start" }, "repository": { "type": "git", - "url": "git+https://github.com/enyeInc/project-template.git" + "url": "git+https://github.com/team-helpme/helpme.git" }, - "author": "Uche Nnadi", + "author": "Justice Otuya, Marshall Akpan, Daniel Damilare", "license": "ISC", "bugs": { - "url": "https://github.com/enyeInc/project-template/issues" + "url": "https://github.com/team-helpme/helpme/issues" }, - "homepage": "https://github.com/enyeInc/project-template#readme", + "homepage": "https://github.com/team-helpme/helpme#readme", "devDependencies": { + "@babel/core": "^7.4.4", + "@babel/preset-env": "^7.4.4", + "@babel/register": "^7.4.4", "babel-eslint": "^10.0.1", "chai": "^4.2.0", - "enzyme": "^3.9.0", - "eslint": "^5.8.0", + "eslint": "^5.16.0", "eslint-config-airbnb": "^17.1.0", "eslint-config-standard": "^12.0.0", "eslint-plugin-import": "^2.14.0", @@ -30,16 +35,32 @@ "eslint-plugin-promise": "^4.0.1", "eslint-plugin-react": "^7.11.1", "eslint-plugin-standard": "^4.0.0", - "jest": "^24.7.1", + "jest": "^24.8.0", "mocha": "^6.1.2", "sinon": "^7.3.1" }, "dependencies": { + "@babel/core": "^7.4.3", + "@shelf/jest-mongodb": "^1.0.1", + "@zeit/next-css": "^1.0.1", + "antd": "^3.16.3", + "auth0-js": "^9.10.2", + "babel-jest": "^24.7.1", + "babel-plugin-inline-react-svg": "^1.1.0", + "bcryptjs": "^2.4.3", + "body-parser": "^1.19.0", "dotenv": "^7.0.0", + "enzyme": "^3.9.0", + "enzyme-adapter-react-16": "^1.12.1", "express": "^4.16.4", + "gravatar": "^1.8.0", + "mongoose": "^5.5.4", "next": "^8.0.4", + "prop-types": "^15.7.2", "react": "^16.8.6", + "react-addons-test-utils": "^15.6.2", "react-dom": "^16.8.6", + "react-test-renderer": "^16.8.6", "redux": "^4.0.1", "redux-saga": "^1.0.2" } diff --git a/pages/index.js b/pages/index.js index e69de29..d7c5b25 100644 --- a/pages/index.js +++ b/pages/index.js @@ -0,0 +1,3 @@ +import LandingPage from '../components/LandingPage/index'; + +export default LandingPage; diff --git a/pages/login.js b/pages/login.js new file mode 100644 index 0000000..34ebdaf --- /dev/null +++ b/pages/login.js @@ -0,0 +1,3 @@ +import { Login } from '../components/Authentication'; + +export default Login; diff --git a/pages/signup.js b/pages/signup.js new file mode 100644 index 0000000..2bed551 --- /dev/null +++ b/pages/signup.js @@ -0,0 +1,3 @@ +import { SignUp } from '../components/Authentication'; + +export default SignUp; diff --git a/routes/api/profile.js b/routes/api/profile.js new file mode 100644 index 0000000..5f5e994 --- /dev/null +++ b/routes/api/profile.js @@ -0,0 +1,122 @@ +const express = require('express'); + +const router = express.Router(); +const Profile = require('../../models/profile.model'); + +// Create a new Profile +router.post('/new', async (req, res) => { + const { + city, country, email, firstName, lastName, + } = req.body; + + // Validate request + if (!city || !country || !email || !firstName || !lastName) { + return res.json({ + message: 'Please ensure you fill all fields', + }); + } + // create new profile + const profile = new Profile({ + city, + country, + email, + firstName, + lastName, + }); + // Save Profile in the database + try { + const newProfile = await profile.save(); + return res.json({ + message: 'profile successfully created', + profile: newProfile, + }); + } catch (err) { + return err; + } +}); + +// Retrieve all profiles +router.get('/all', async (req, res) => { + try { + const profiles = await Profile.find(); + if (!profiles) { + return res.status(404).json({ + message: 'No Profile found', + }); + } + return res.json(profiles); + } catch (err) { + return err; + } +}); + +// Retrieve a single Profile with profileId +router.get('/:profileId', async (req, res) => { + const { profileId } = req.params; + try { + const profile = await Profile.findById(profileId); + if (!profile) { + return res.status(404).json({ + message: `Profile not found with id ${profileId}`, + }); + } + return res.json(profile); + } catch (err) { + if (err.kind === 'ObjectId') { + return res.json({ + message: `Profile not found with id ${profileId}`, + }); + } + } + return null; +}); +// Update a Profile with profileId +router.put('/:profileId', async (req, res) => { + const { + city, country, email, firstName, lastName, + } = req.body; + const { profileId } = req.params; + const profile = new Profile({ + city, + country, + email, + firstName, + lastName, + }); + try { + const updatedProfile = await Profile.findByIdAndUpdate(profileId, profile, { new: true }); + if (!updatedProfile) { + return res.json({ + message: `Profile not found with id ${profileId}`, + }); + } + return res.json(updatedProfile); + } catch (err) { + if (err.kind === 'ObjectId') { + return res.status(404).json({ + message: `Profile not found with id ${profileId}`, + }); + } + return res.status(500).json({ + message: `Error updating profile with id ${profileId}`, + }); + } +}); + +// Delete a Profile with profileId +router.delete('/:profileId', async (req, res) => { + const { profileId } = req.params; + try { + const profile = await Profile.findByIdAndDelete(profileId); + if (!profile) { + return res.json({ + message: `Profile not found with id ${profileId}`, + }); + } + return res.json({ message: 'Profile deleted successfully!' }); + } catch (err) { + return err; + } +}); + +module.exports = router; diff --git a/routes/api/users.js b/routes/api/users.js new file mode 100644 index 0000000..6c44f99 --- /dev/null +++ b/routes/api/users.js @@ -0,0 +1,47 @@ +const express = require('express'); +const gravatar = require('gravatar'); +const bcrypt = require('bcryptjs'); + +const router = express.Router(); + +// Bring in the User Model +const User = require('../../models/User'); + +// User Registration Route +router.post('/register', async (req, res, next) => { + try { + const { email, name, password } = req.body; + // Check if the email coming in matches what is in the DB + const user = await User.findOne({ email }); + if (user) { + return res.status(400).json({ message: `${email} already exist` }); + } + const avatar = gravatar.url(email, { + d: 'mm', // Default + r: 'pg', // Rating + s: '200', // Size + }); + + const newUser = new User({ + // create new user + avatar, + email, + name, + password, + }); + + // Hash Password + bcrypt.genSalt(10, (err, salt) => { + bcrypt.hash(newUser.password, salt, hash => { + if (err) throw err; + newUser.password = hash; + }); + }); + const userCreated = await newUser.save(); + return res.json(userCreated); + } catch (err) { + return next(err); + } +}); + +module.exports = router; diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..922cf8e --- /dev/null +++ b/server/index.js @@ -0,0 +1,42 @@ +const express = require('express'); +const mongoose = require('mongoose'); +const next = require('next'); +const bodyParser = require('body-parser'); +const users = require('../routes/api/users'); +const profile = require('../routes/api/profile'); + +const PORT = process.env.PORT || 3000; +const dev = process.env.NODE_DEV !== 'production'; +const nextApp = next({ dev }); +const handle = nextApp.getRequestHandler(); + +// i need to comment the db declaration below else lint will not pass my code, +// i don't want to delete it as i am not the one who coded it comment by @justiceotuya + +// Configure DB +const db = require('../config/keys').mongoURI; +// Connect to MongoDB +mongoose.connect(db, { useNewUrlParser: true }); + +mongoose.connect(db, { useNewUrlParser: true }); + +nextApp.prepare().then(() => { + // express code here + const app = express(); + // bodyParser Middleware + app.use(bodyParser.json()); + app.use(bodyParser.urlencoded({ extended: false })); + + // Routes Middleware + app.use('/api/users', users); + app.use('/api/profile', profile); + + // next should handle all other routes except the ones specified. + app.get('*', (req, res) => handle(req, res)); + + app.listen(PORT, err => { + if (err) throw err; + // eslint-disable-next-line no-console + console.log(`Server ready at http://localhost:${PORT}`); + }); +}); diff --git a/static/community.svg b/static/community.svg new file mode 100644 index 0000000..6835f53 --- /dev/null +++ b/static/community.svg @@ -0,0 +1 @@ +community \ No newline at end of file diff --git a/static/connected.svg b/static/connected.svg new file mode 100644 index 0000000..79b1740 --- /dev/null +++ b/static/connected.svg @@ -0,0 +1 @@ +connected \ No newline at end of file diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..974a52a Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/hangout.svg b/static/hangout.svg new file mode 100644 index 0000000..ecec6ed --- /dev/null +++ b/static/hangout.svg @@ -0,0 +1 @@ +hang out \ No newline at end of file diff --git a/static/login.svg b/static/login.svg new file mode 100644 index 0000000..956800b --- /dev/null +++ b/static/login.svg @@ -0,0 +1 @@ +press play \ No newline at end of file diff --git a/static/logo-light.png b/static/logo-light.png new file mode 100644 index 0000000..3286fe0 Binary files /dev/null and b/static/logo-light.png differ diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000..a0f6e59 Binary files /dev/null and b/static/logo.png differ diff --git a/static/register.svg b/static/register.svg new file mode 100644 index 0000000..cfe3649 --- /dev/null +++ b/static/register.svg @@ -0,0 +1 @@ +mobile login \ No newline at end of file diff --git a/static/smile.svg b/static/smile.svg new file mode 100644 index 0000000..7b9925c --- /dev/null +++ b/static/smile.svg @@ -0,0 +1 @@ +smiley face \ No newline at end of file