diff --git a/.eslintrc.json b/.eslintrc.json index 3e9ce49a7..cfe7863e5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,34 +1,35 @@ { - "extends": [ - "airbnb", - "prettier", - "prettier/react", - "jest-enzyme", - "plugin:jest/recommended" - ], - "parser": "babel-eslint", - "plugins": ["prettier", "jest"], - "parserOptions": { - "ecmaVersion": 2016, - "sourceType": "module", - "ecmaFeatures": { - "jsx": true - } - }, - "env": { - "es6": true, - "browser": true, - "node": true - }, - "rules": { - "array-bracket-spacing": [2, "never"], - "semi": [2, "always"], - "react/prefer-stateless-function": [0], - "react/jsx-filename-extension": [ - 1, - { - "extensions": [".js", ".jsx"] - } - ] - } + "extends": [ + "airbnb", + "prettier", + "prettier/react", + "jest-enzyme", + "plugin:jest/recommended" + ], + "parser": "babel-eslint", + "plugins": ["prettier", "jest"], + "parserOptions": { + "ecmaVersion": 2016, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "env": { + "es6": true, + "browser": true, + "node": true + }, + "rules": { + "array-bracket-spacing": [2, "never"], + "semi": [2, "always"], + "jsx-a11y/label-has-for": 0, + "react/prefer-stateless-function": [0], + "react/jsx-filename-extension": [ + 1, + { + "extensions": [".js", ".jsx"] + } + ] + } } diff --git a/client/assets/default/index.css b/client/assets/default/index.css new file mode 100644 index 000000000..16c2c3224 --- /dev/null +++ b/client/assets/default/index.css @@ -0,0 +1 @@ +/* */ diff --git a/client/components/login/index.css b/client/components/login/index.css new file mode 100644 index 000000000..350d3a3d1 --- /dev/null +++ b/client/components/login/index.css @@ -0,0 +1,213 @@ +.owisp-login-container { + flex-grow: 1; + display: flex; + flex-direction: row; + justify-content: center; + align-items: flex-start; + background: #f2f2f2; + width: 100%; +} +.owisp-login-form { + font-family: sans-serif; + background: #ffffff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 24px 50px; +} +.owisp-login-form.success .owisp-login-input:invalid { + box-shadow: none; +} +.owisp-login-header-content { + text-transform: capitalize; + color: #444; + font-size: 28px; + margin-bottom: 4px; +} +.owisp-login-fieldset { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + align-self: flex-start; +} +.owisp-login-label { + margin-top: 15px; + margin-bottom: 5px; +} +.owisp-login-label-text { + color: #6b6b6b; + text-transform: capitalize; +} +.owisp-login-input { + padding: 10px; + border-radius: 3px; + font-size: 18px; + border: 2px solid #e7e7e7; + margin-top: 5px; + width: 380px; + box-sizing: border-box; +} +.owisp-login-input:focus { + border-color: #a7a7a7; +} +.owisp-login-input.error { + border-color: #d93025; +} +.owisp-login-form-btn { + width: 100%; + font-size: 18px; + height: 50px; + background: #293b52; + color: #ffffff; + text-transform: capitalize; + border: 0; + border-radius: 3px; + cursor: pointer; +} +.owisp-login-form-btn:focus { + outline: dotted 1px #444; + outline-offset: 0.5px; +} +.owisp-login-login-btn { + margin-top: 4px; +} +.owisp-login-form-btn:hover { + background: #223144; +} +.owisp-login-error { + font-size: 12px; + color: #d93025; + width: 324px; + display: flex; + flex-direction: row; + justify-content: flex-start; + flex-wrap: nowrap; +} +.owisp-login-error-icon { + display: inline-block; + background: #d93025; + color: #ffffff; + min-width: 16px; + align-self: center; + height: 16px; + border-radius: 50%; + text-align: center; + margin-right: 8px; + font-weight: 600; +} +.owisp-login-register-link { + text-decoration: none; + width: 100%; +} +.owisp-login-add-info { + margin-top: 15px; + color: #444; + max-width: 380px; +} +.owisp-login-additional-link { + color: #444; +} +.owisp-login-additional-link:hover, +.owisp-login-additional-link:focus { + outline: dotted 1px #444; + outline-offset: 0.5px; +} +.owisp-login-social-hr { + outline: 0; + border: 0; + text-align: center; + height: 1.5em; + line-height: 1em; + position: relative; + width: 100%; +} +.owisp-login-social-hr:before { + content: ""; + background: #e6e6e6; + position: absolute; + left: 0; + top: 48%; + width: 100%; + height: 2px; +} +.owisp-login-social-hr:after { + content: attr(data-content); + position: relative; + display: inline-block; + padding: 0 0.5em; + line-height: 1.5em; + color: #798993; + background-color: #fcfcfa; +} +.owisp-login-divider-description { + color: #798993; + text-transform: uppercase; + font-size: 0.9rem; + width: 100%; + text-align: center; +} +.owisp-login-social-links-div { + width: 100%; + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; + margin-top: 8px; +} +.owisp-login-social-link { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + padding: 4px 8px; + align-items: center; + text-decoration: none; + color: #798993; + border-radius: 3px; + margin-right: 12px; + box-shadow: 1px 1px 1px 1px hsla(0, 0%, 53%, 0.33); +} +.owisp-login-social-link:hover { + background: #efefef; +} +.owisp-login-social-link:focus { + outline: dotted 1px #444; + outline-offset: 0.5px; +} +.owisp-login-social-link-icon { + width: 28px; + height: auto; +} +.owisp-login-social-link-text { + margin-left: 3px; + text-transform: capitalize; +} +.owisp-login-links-div { + display: flex; + flex-direction: column; + width: 100%; + justify-content: flex-start; + align-items: center; + margin-top: 20px; +} +.owisp-login-link { + line-height: 1.4; + text-decoration: none; + text-transform: capitalize; + color: #444; +} +.owisp-login-link:focus { + outline: dotted 1px #444; + outline-offset: 0.5px; +} +.owisp-login-link:hover { + text-decoration: underline; +} +.owisp-login-error-non-field { + box-sizing: border-box; + font-size: 16px; + width: 100%; + max-width: 380px; + margin-top: 15px; +} diff --git a/client/components/login/index.js b/client/components/login/index.js new file mode 100644 index 000000000..0094357b5 --- /dev/null +++ b/client/components/login/index.js @@ -0,0 +1,18 @@ +import {connect} from "react-redux"; + +import Component from "./login"; + +const mapStateToProps = state => { + return { + loginForm: state.organization.configuration.components.login_form, + privacyPolicy: state.organization.configuration.privacy_policy, + termsAndConditions: state.organization.configuration.terms_and_conditions, + language: state.language, + orgSlug: state.organization.configuration.slug, + }; +}; + +export default connect( + mapStateToProps, + null, +)(Component); diff --git a/client/components/login/login.js b/client/components/login/login.js new file mode 100644 index 000000000..67d8f18b6 --- /dev/null +++ b/client/components/login/login.js @@ -0,0 +1,363 @@ +/* eslint-disable camelcase */ +import "./index.css"; + +import axios from "axios"; +import PropTypes from "prop-types"; +import qs from "qs"; +import React from "react"; +import {Link} from "react-router-dom"; + +import {loginApiUrl} from "../../constants"; +import getAssetPath from "../../utils/get-asset-path"; +import getText from "../../utils/get-text"; +import renderAdditionalInfo from "../../utils/render-additional-info"; + +export default class Login extends React.Component { + constructor(props) { + super(props); + this.state = { + username: "", + password: "", + errors: {}, + }; + this.handleSubmit = this.handleSubmit.bind(this); + this.handleChange = this.handleChange.bind(this); + } + + handleChange(event) { + this.setState({[event.target.name]: event.target.value}); + } + + handleSubmit(event) { + event.preventDefault(); + const {orgSlug} = this.props; + const {username, password, errors} = this.state; + const url = loginApiUrl.replace("{orgSlug}", orgSlug); + this.setState({ + errors: {}, + }); + axios({ + method: "post", + headers: { + "content-type": "application/x-www-form-urlencoded", + }, + url, + data: qs.stringify({ + username, + password, + }), + }) + .then(() => { + this.setState({ + errors: {}, + username: "", + password: "", + }); + }) + .catch(error => { + const {data} = error.response; + this.setState({ + errors: { + ...errors, + ...(data.non_field_errors + ? {nonField: data.non_field_errors[0]} + : {nonField: ""}), + ...(data.detail ? {nonField: data.detail} : {}), + ...(data.username ? {username: data.username} : {username: ""}), + ...(data.password ? {password: data.password} : {password: ""}), + }, + }); + }); + } + + render() { + const {errors, username, password} = this.state; + const { + language, + loginForm, + orgSlug, + termsAndConditions, + privacyPolicy, + } = this.props; + const { + links, + buttons, + input_fields, + social_login, + header, + additional_info_text, + } = loginForm; + return ( + + + + + + {getText(header, language)} + + + + {errors.nonField && ( + + ! + + {errors.nonField} + + + )} + {input_fields.username ? ( + <> + + + {getText(input_fields.username.label, language)} + + + + {errors.username && ( + + ! + + {errors.username} + + + )} + > + ) : null} + {input_fields.password ? ( + <> + + + {getText(input_fields.password.label, language)} + + + + {errors.password && ( + + ! + + {errors.password1} + + + )} + > + ) : null} + + {additional_info_text ? ( + + {renderAdditionalInfo( + additional_info_text, + language, + termsAndConditions, + privacyPolicy, + orgSlug, + )} + + ) : null} + {buttons.login ? ( + <> + {buttons.login.label ? ( + + + {getText(buttons.login.label, language)} + + + ) : null} + + > + ) : null} + {buttons.register ? ( + <> + {buttons.register.label ? ( + + + {getText(buttons.register.label, language)} + + + ) : null} + + + + > + ) : null} + {social_login ? ( + <> + {social_login.divider_text ? ( + + ) : null} + {social_login.description ? ( + + {getText(social_login.description, language)} + + ) : null} + {social_login.links.length ? ( + <> + + {social_login.links.map(link => { + if (link.url) + return ( + + {link.icon ? ( + + ) : null} + {link.text ? ( + + {getText(link.text, language)} + + ) : null} + + ); + return null; + })} + + > + ) : null} + > + ) : null} + {links ? ( + + {links.forget_password ? ( + + {getText(links.forget_password, language)} + + ) : null} + {links.register ? ( + + {getText(links.register, language)} + + ) : null} + + ) : null} + + + + ); + } +} + +Login.propTypes = { + loginForm: PropTypes.shape({ + header: PropTypes.object, + social_login: PropTypes.shape({ + divider_text: PropTypes.object, + description: PropTypes.object, + link: PropTypes.arrayOf(PropTypes.object), + }), + input_fields: PropTypes.shape({ + username: PropTypes.object, + password: PropTypes.object, + }), + additional_info_text: PropTypes.object, + buttons: PropTypes.object, + }).isRequired, + language: PropTypes.string.isRequired, + orgSlug: PropTypes.string.isRequired, + privacyPolicy: PropTypes.shape({ + title: PropTypes.object, + content: PropTypes.object, + }).isRequired, + termsAndConditions: PropTypes.shape({ + title: PropTypes.object, + content: PropTypes.object, + }).isRequired, +}; diff --git a/client/components/organization-wrapper/index.js b/client/components/organization-wrapper/index.js index 8c9997b5c..93f78e3fc 100644 --- a/client/components/organization-wrapper/index.js +++ b/client/components/organization-wrapper/index.js @@ -1,11 +1,13 @@ +import {withCookies} from "react-cookie"; import {connect} from "react-redux"; import setOrganization from "../../actions/set-organization"; import Component from "./organization-wrapper"; -const mapStateToProps = state => { +const mapStateToProps = (state, ownProps) => { return { organization: state.organization, + cookies: ownProps.cookies, }; }; @@ -16,7 +18,9 @@ const mapDispatchToProps = dispatch => { }, }; }; -export default connect( - mapStateToProps, - mapDispatchToProps, -)(Component); +export default withCookies( + connect( + mapStateToProps, + mapDispatchToProps, + )(Component), +); diff --git a/client/components/organization-wrapper/organization-wrapper.js b/client/components/organization-wrapper/organization-wrapper.js index c8aa31b10..d5c074246 100644 --- a/client/components/organization-wrapper/organization-wrapper.js +++ b/client/components/organization-wrapper/organization-wrapper.js @@ -9,6 +9,7 @@ import getAssetPath from "../../utils/get-asset-path"; import DoesNotExist from "../404"; import Footer from "../footer"; import Header from "../header"; +import Login from "../login"; import PasswordConfirm from "../password-confirm"; import PasswordReset from "../password-reset"; import Registration from "../registration"; @@ -16,14 +17,20 @@ import Registration from "../registration"; export default class OrganizationWrapper extends React.Component { constructor(props) { super(props); - const {match, setOrganization} = this.props; + const {match, setOrganization} = props; const organizationSlug = match.params.organization; if (organizationSlug) setOrganization(organizationSlug); } + componentDidUpdate(prevProps) { + const {setOrganization, match} = this.props; + if (prevProps.match.params.organization !== match.params.organization) { + if (match.params.organization) setOrganization(match.params.organization); + } + } + render() { - const {match} = this.props; - const {organization} = this.props; + const {organization, match} = this.props; const {title, favicon} = organization.configuration; const orgSlug = organization.configuration.slug; const cssPath = organization.configuration.css_path; @@ -46,6 +53,7 @@ export default class OrganizationWrapper extends React.Component { exact render={() => } /> + } /> } /> diff --git a/client/constants/index.js b/client/constants/index.js index 7849d989c..f65bcfa78 100644 --- a/client/constants/index.js +++ b/client/constants/index.js @@ -2,3 +2,4 @@ export const passwordConfirmError = "The two password fields didn't match."; export const registerApiUrl = "/api/v1/{orgSlug}/account/"; export const resetApiUrl = "/api/v1/{orgSlug}/account/password/reset/"; export const confirmApiUrl = "/api/v1/{orgSlug}/account/password/reset/confirm"; +export const loginApiUrl = "/api/v1/{orgSlug}/account/token"; diff --git a/client/index.js b/client/index.js index 6febd9b68..3ebe011b6 100644 --- a/client/index.js +++ b/client/index.js @@ -2,6 +2,7 @@ import "./index.css"; import PropTypes from "prop-types"; import React from "react"; +import {CookiesProvider} from "react-cookie"; import {render} from "react-dom"; import {Provider, connect} from "react-redux"; import {BrowserRouter} from "react-router-dom"; @@ -45,8 +46,10 @@ const App = connect( )(BaseApp); render( - - - , + + + + + , document.getElementById("root"), ); diff --git a/client/utils/render-additional-info.js b/client/utils/render-additional-info.js new file mode 100644 index 000000000..96abd39bf --- /dev/null +++ b/client/utils/render-additional-info.js @@ -0,0 +1,96 @@ +import React from "react"; +import {Link} from "react-router-dom"; + +import getText from "./get-text"; + +const renderAdditionalInfo = ( + textObj, + language, + termsAndConditions, + privacyPolicy, + orgSlug, +) => { + const textNodes = []; + const text = getText(textObj, language); + const privacyPolicyTitle = getText(privacyPolicy.title, language); + const termsAndConditionsTitle = getText(termsAndConditions.title, language); + if (text.includes("{terms_and_conditions}")) { + const Array1 = text.split("{terms_and_conditions}"); + if (Array1[0].includes("{privacy_policy}")) { + const Array2 = Array1[0].split("{privacy_policy}"); + textNodes.push(Array2[0]); + textNodes.push( + + {privacyPolicyTitle} + , + ); + textNodes.push(Array2[1]); + textNodes.push( + + {termsAndConditionsTitle} + , + ); + textNodes.push(Array1[1]); + } else if (Array1[1].includes("{privacy_policy}")) { + const Array2 = Array1[1].split("{privacy_policy}"); + textNodes.push(Array1[0]); + textNodes.push( + + {termsAndConditionsTitle} + , + ); + textNodes.push(Array2[0]); + textNodes.push( + + {privacyPolicyTitle} + , + ); + textNodes.push(Array2[1]); + } else { + textNodes.push(Array1[0]); + textNodes.push( + + {termsAndConditionsTitle} + , + ); + textNodes.push(Array1[1]); + } + } else if (text.includes("{privacy_policy}")) { + const Array1 = text.split("{privacy_policy}"); + textNodes.push(Array1[0]); + textNodes.push( + + {privacyPolicyTitle} + , + ); + textNodes.push(Array1[1]); + } else { + textNodes.push(text); + } + return textNodes; +}; +export default renderAdditionalInfo; diff --git a/org-configurations/default-configuration.yml b/org-configurations/default-configuration.yml index 13833d00e..c6c10c192 100644 --- a/org-configurations/default-configuration.yml +++ b/org-configurations/default-configuration.yml @@ -21,12 +21,14 @@ client: # value title: "Wifi Login" + auto_login: True + # path of favicon favicon: null # path of the custom css file relative to organization's folder in # assets directory. - css_path: null + css_path: "index.css" languages: - text: "english" slug: "en" @@ -151,35 +153,56 @@ client: submit: en: 'change password' + en: "sign up" # text for register button login_form: - input_field: + header: + en: " sign in" + social_login: + divider_text: + en: "OR" + description: + en: "login with" + links: + - facebook: + text: null + url: null + icon: null #relative to organization's folder in assets directory. + input_fields: username: type: "text" + pattern: '[a-zA-Z@.+\-_]{1,150}' + pattern_description: + en: "only letters, numbers, and @/./+/-/_ characters" placeholder: - en: "Enter phone number or username" + en: "enter username" label: - en: "Phone number or username" # default username field label - pattern: null # pattern for validation + en: "username" password: type: "password" + pattern: ".{6,}" + pattern_description: + en: "password must be a minimum of 6 characters" placeholder: - en: "Enter password" + en: "password" label: - en: "Password" # default password field - pattern: null + en: "password" + additional_info_text: + en: "By logging in, you accept the {terms_and_conditions} and the {privacy_policy} of this WiFi service. " buttons: login: - label: - en: null # label field text for login button - value: - en: 'Login' # text for login button - register: - label: - en: null # label field text for register button - value: - en: 'Sign up for free' # text for register button + label: null + text: + en: "sign in" links: - - forget_password : - en: "Forgot your password?" - - account: - en: "Manage your account" + forget_password : + en: "Forgot your password?" + register: + en: "Not registered yet? Create an account" + privacy_policy: + title: + en: "privacy policy" + content: null + terms_and_conditions: + title: + en: "terms and conditions" + content: null diff --git a/package-lock.json b/package-lock.json index a20f38288..c888bd6f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1229,6 +1229,11 @@ "@babel/types": "^7.3.0" } }, + "@types/cookie": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", + "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -1246,6 +1251,15 @@ "@types/node": "*" } }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", @@ -1283,6 +1297,25 @@ "integrity": "sha512-b8bbUOTwzIY3V5vDTY1fIJ+ePKDUBqt2hC2woVGotdQQhG/2Sh62HOKHrT7ab+VerXAcPyAiTEipPu/FsreUtg==", "dev": true }, + "@types/object-assign": { + "version": "4.0.30", + "resolved": "https://registry.npmjs.org/@types/object-assign/-/object-assign-4.0.30.tgz", + "integrity": "sha1-iUk3HVqZ9Dge4PHfCpt6GH4H5lI=" + }, + "@types/prop-types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", + "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==" + }, + "@types/react": { + "version": "16.8.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.23.tgz", + "integrity": "sha512-abkEOIeljniUN9qB5onp++g0EY38h7atnDHxwKUFz1r3VH1+yG1OKi2sNPTyObL40goBmfKFpdii2lEzwLX1cA==", + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -3180,6 +3213,11 @@ "cssom": "0.3.x" } }, + "csstype": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz", + "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==" + }, "cyclist": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", @@ -4752,8 +4790,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -4771,13 +4808,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4790,18 +4825,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -4904,8 +4936,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -4915,7 +4946,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4928,20 +4958,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4958,7 +4985,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5031,8 +5057,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -5042,7 +5067,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5118,8 +5142,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -5149,7 +5172,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5167,7 +5189,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5206,13 +5227,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -8784,6 +8803,16 @@ "scheduler": "^0.13.6" } }, + "react-cookie": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.0.1.tgz", + "integrity": "sha512-h61qAtSXvfjNa81h3XCFdFoyFaF+nb7gjK0cxQuTiCPMPAe50D950FjLCFhaIfSpAesQFAmkxf5XFpWoEVBDAA==", + "requires": { + "@types/hoist-non-react-statics": "^3.0.1", + "hoist-non-react-statics": "^3.0.0", + "universal-cookie": "^4.0.0" + } + }, "react-dom": { "version": "16.8.6", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz", @@ -10720,6 +10749,17 @@ "crypto-random-string": "^1.0.0" } }, + "universal-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.2.tgz", + "integrity": "sha512-n14lhA//lQeYRweP9j9uXsshN9Cs4LunVSnvAGmnA69SofwsjpUU03geaCaPC9LlsH2rkBy99o3zxQyVOldGvA==", + "requires": { + "@types/cookie": "^0.3.3", + "@types/object-assign": "^4.0.30", + "cookie": "^0.4.0", + "object-assign": "^4.1.1" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index fb88e8866..72014cde9 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "prop-types": "^15.7.2", "qs": "^6.7.0", "react": "^16.8.6", + "react-cookie": "^4.0.1", "react-dom": "^16.8.6", "react-helmet": "^5.2.1", "react-redux": "^7.1.0", @@ -96,5 +97,10 @@ "\\.(css|less|sass|scss)$": "/__mocks__/styleMock.js", "\\.(gif|ttf|eot|svg)$": "/__mocks__/fileMock.js" } + }, + "nodemonConfig": { + "ignore": [ + "client/*" + ] } }