diff --git a/src/actions/login.js b/src/actions/login.js new file mode 100644 index 0000000..42852aa --- /dev/null +++ b/src/actions/login.js @@ -0,0 +1,121 @@ +import { + LOGIN_START, + LOGIN_SUCCESS, + LOGIN_FAILURE, + REGISTRATION_START, + REGISTRATION_SUCCESS, + REGISTRATION_FAILURE, + apiURL, + ALERT_SHOW, + ALERT_HIDE, +} from '../constants'; +import fetchAsync from '../utils/fetch'; +import createExpiresCookie from '../utils/create-expires-cookie'; + +const cookie = require('isomorphic-cookie'); + +const minutesOfCookieLive = 60; +export function loginStart() { + return { + type: LOGIN_START, + }; +} +export function loginSuccess(data) { + cookie.save('token', data.data.token, { + expires: createExpiresCookie(minutesOfCookieLive), + secure: false, + }); + return { + type: LOGIN_SUCCESS, + data, + }; +} +export function alertShow(text) { + return { + type: ALERT_SHOW, + text, + }; +} +export function alertHide() { + return { + type: ALERT_HIDE, + }; +} +export function alertCreator(text) { + const millisecondsToAlertDisapear = 3000; + return dispatch => { + dispatch(alertShow(text)); + setTimeout(() => { + dispatch(alertHide()); + }, millisecondsToAlertDisapear); + }; +} + +export function loginFailure(data) { + return { + type: LOGIN_FAILURE, + data, + }; +} +export function login(loginValue) { + return async dispatch => { + dispatch(loginStart()); + try { + const payload = await fetchAsync(`${apiURL}/login`, 'POST', loginValue); + if (!payload.data) { + dispatch(alertCreator(payload.message)); + return dispatch(loginFailure(payload)); + } + dispatch(alertCreator(payload.message)); + return dispatch(loginSuccess(payload)); + } catch (error) { + dispatch(alertCreator('Wow, some error appear')); + return dispatch(loginFailure(error)); + } + }; +} + +export function registrationStart() { + return { + type: REGISTRATION_START, + }; +} +export function registrationSuccess(data) { + cookie.save('token', data.data.token, { + expires: createExpiresCookie(minutesOfCookieLive), + secure: false, + }); + return { + type: REGISTRATION_SUCCESS, + data, + }; +} + +export function registrationFailure(data) { + return { + type: REGISTRATION_FAILURE, + data, + }; +} + +export function registration(regitrationValue) { + return async dispatch => { + dispatch(registrationStart()); + try { + const payload = await fetchAsync( + `${apiURL}/registration`, + 'POST', + regitrationValue, + ); + if (!payload.data) { + dispatch(alertCreator(payload.message)); + return dispatch(registrationFailure(payload)); + } + dispatch(alertCreator(payload.message)); + return dispatch(registrationSuccess(payload)); + } catch (error) { + dispatch(alertCreator('Wow, some error appear')); + return dispatch(registrationFailure(error)); + } + }; +} diff --git a/src/constants/index.js b/src/constants/index.js index 7262845..5d2aede 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -18,6 +18,17 @@ export const GET_ALL_MESSAGES = 'GET_ALL_MESSAGES'; export const CHAT_ADD_MESSAGE = 'CHAT_ADD_MESSAGE'; export const CHAT_MESSAGE_IS_TYPING = 'CHAT_MESSAGE_IS_TYPING'; export const CHAT_REMOVE_MESSAGE = 'CHAT_REMOVE_MESSAGE'; +// login +export const LOGIN_START = 'LOGIN_START'; +export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; +export const LOGIN_FAILURE = 'LOGIN_FAILURE'; +export const REGISTRATION_SUCCESS = 'REGISTRATION_SUCCESS'; +export const REGISTRATION_FAILURE = 'REGISTRATION_FAILURE'; +export const REGISTRATION_START = 'REGISTRATION_START'; +export const ALERT_SHOW = 'ALERT_SHOW'; +export const ALERT_HIDE = 'ALERT_HIDE'; + +// login end export const CHAT_UPDATE_MESSAGE_BY_ID = 'CHAT_UPDATE_MESSAGE_BY_ID'; export const EDIT_MESSAGE = 'EDIT_MESSAGE'; diff --git a/src/reducers/index.js b/src/reducers/index.js index 2578c57..5ccdc75 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -2,6 +2,7 @@ import { combineReducers } from 'redux'; import { reducer as formReducer } from 'redux-form'; import movementDetection from './movement-detection'; import chat from './chat'; +import login from './login'; import windowSensors from './window-sensors'; export default combineReducers({ @@ -9,4 +10,5 @@ export default combineReducers({ windowSensors, movementDetection, form: formReducer, + login, }); diff --git a/src/reducers/login.js b/src/reducers/login.js new file mode 100644 index 0000000..571c3d1 --- /dev/null +++ b/src/reducers/login.js @@ -0,0 +1,70 @@ +import { + LOGIN_START, + LOGIN_SUCCESS, + LOGIN_FAILURE, + REGISTRATION_START, + REGISTRATION_FAILURE, + REGISTRATION_SUCCESS, + ALERT_HIDE, + ALERT_SHOW, +} from '../constants'; + +const initialState = { + loading: false, + isLogedIn: false, + alert: null, +}; +export default function login(state = initialState, action) { + switch (action.type) { + case LOGIN_START: + return { + ...state, + loading: true, + }; + case LOGIN_SUCCESS: + return { + ...state, + loading: false, + ...action.data, + isLogedIn: true, + }; + case LOGIN_FAILURE: + return { + ...state, + loading: false, + ...action.data, + isLogedIn: false, + }; + case REGISTRATION_START: + return { + ...state, + loading: true, + }; + case REGISTRATION_FAILURE: + return { + ...state, + loading: false, + ...action.data, + isLogedIn: false, + }; + case REGISTRATION_SUCCESS: + return { + ...state, + loading: false, + ...action.data, + isLogedIn: true, + }; + case ALERT_HIDE: + return { + ...state, + alert: null, + }; + case ALERT_SHOW: + return { + ...state, + alert: action.text, + }; + default: + return state; + } +} diff --git a/src/routes/index.js b/src/routes/index.js index e7318f8..79de8b2 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,18 +1,30 @@ import invoke from 'lodash/invoke'; import nth from 'lodash/nth'; +// import cookie from 'isomorphic-cookie'; const routes = [ { action({ next }) { return next(); }, - children: [], + children: [ + { + load: () => import('./login'), + path: '/login', + }, + ], path: '', }, { async action({ next }) { // Execute each child route until one of them return the result const route = await next(); + if ( + // !cookie.load('token') + false // at this time false!!! + ) { + route.redirect = '/login'; + } // Provide default values for title, description etc. route.title = `${route.title || 'Untitled Page'}`; diff --git a/src/routes/login/Login.css b/src/routes/login/Login.css index f8cb29e..1aa894d 100644 --- a/src/routes/login/Login.css +++ b/src/routes/login/Login.css @@ -28,22 +28,13 @@ } .formContainer button { - border: none; - border-radius: 5px; - height: 25px; - background: #eee; - margin-right: 10px; - outline: none; width: 50%; align-self: center; } -.formContainer button:hover { - cursor: pointer; -} - .textLogin { color: #cecece; + margin-top: 15px; display: flex; width: 100%; justify-content: center; diff --git a/src/routes/login/Login.js b/src/routes/login/Login.js index 97776e7..c335951 100644 --- a/src/routes/login/Login.js +++ b/src/routes/login/Login.js @@ -1,70 +1,92 @@ -import React, { memo, useCallback, useState } from 'react'; +import React, { useCallback } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; import useStyles from 'isomorphic-style-loader/useStyles'; +import { Field, reduxForm } from 'redux-form'; +import CustomFieldLogin from './components/CustomFieldLogin/CustomFieldLogin'; import s from './Login.css'; -import Input from './components/Input'; -import LoginButton from './components/LoginButton'; +import LoginButton from './components/LoginButton/LoginButton'; +import { login, registration, alertCreator } from '../../actions/login'; +import Alert from './components/Alert/Alert'; + +const validate = value => { + const errors = {}; + const minLengthPassword = 5; + if (!value.email) { + errors.email = 'Required'; + } else if (value.email.length < minLengthPassword) { + errors.email = `Bro, not enough characters for a valid email`; + } else if (!value.email.match(/^[\d.a-z-]+@[\da-z-]{2,}.[a-z]{2,}$/i)) { + errors.email = 'Please, check your email, it looks invalid '; + } + if (!value.password) { + errors.password = 'Required'; + } else if (value.password.length < minLengthPassword) { + errors.password = `Password must have at least 5 characters, you typed only ${value.password.length}`; + } + + return errors; +}; function Login() { + const formLoginState = useSelector(state => state.form.Login); + const formLoginValue = useSelector(state => state.form.Login.values); + const loginState = useSelector(state => state.login); + const dispatch = useDispatch(); useStyles(s); - const [inputValue, setInputValue] = useState({ - password: '', - email: '', - }); - const inputChangeHandler = useCallback(event => { - const { name } = event.target; - const { value } = event.target; - setInputValue(previous => { - return { - ...previous, - [name]: value, - }; - }); - }, []); const buttonLoginHandler = useCallback( event => { event.preventDefault(); const { name } = event.target; - switch (name) { - case 'logIn': - console.info('What we do now? :', name); - console.info('What mail of user is? :', inputValue.email); - console.info('What password is? :', inputValue.password); - break; - case 'registration': - console.info('What we do now? :', name); - console.info('What mail of user is? :', inputValue.email); - console.info('What password is? :', inputValue.password); - break; - default: - console.info('something whent wrong'); + if (formLoginState.syncErrors) { + return dispatch(alertCreator('Fields valuse are invalid')); + } + if (name === 'login') { + return dispatch(login(formLoginValue)); + } + if (name === 'registration') { + return dispatch(registration(formLoginValue)); } + return true; }, - [inputValue], + [dispatch, formLoginValue], ); + // console.info(formLoginState); return (

Want to be on safe? Please, log in or register

- } + - - +
); } + Login.whyDidYouRender = true; -export default memo(Login); +export default reduxForm({ + form: 'Login', + validate, +})(Login); diff --git a/src/routes/login/components/Alert/Alert.js b/src/routes/login/components/Alert/Alert.js new file mode 100644 index 0000000..8d2c6e8 --- /dev/null +++ b/src/routes/login/components/Alert/Alert.js @@ -0,0 +1,13 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export default function AlertMessage({ message }) { + return ( +
+ {message} +
+ ); +} +AlertMessage.propTypes = { + message: PropTypes.string.isRequired, +}; diff --git a/src/routes/login/components/CustomFieldLogin/CustomFieldLogin.css b/src/routes/login/components/CustomFieldLogin/CustomFieldLogin.css new file mode 100644 index 0000000..1713804 --- /dev/null +++ b/src/routes/login/components/CustomFieldLogin/CustomFieldLogin.css @@ -0,0 +1,7 @@ +.inputsError { + width: 100%; + margin-top: 4px; + margin-top: 0.25rem; + font-size: 80%; + color: #dc3545; +} diff --git a/src/routes/login/components/CustomFieldLogin/CustomFieldLogin.js b/src/routes/login/components/CustomFieldLogin/CustomFieldLogin.js new file mode 100644 index 0000000..dcbbc3b --- /dev/null +++ b/src/routes/login/components/CustomFieldLogin/CustomFieldLogin.js @@ -0,0 +1,55 @@ +import PropTypes from 'prop-types'; +import useStyles from 'isomorphic-style-loader/useStyles'; +import React from 'react'; +import s from './CustomFieldLogin.css'; + +export default function CustomFieldLogin({ + input: { name, onBlur, onChange, onDragStart, onDrop, onFocus }, + label, + type, + meta: { touched, error }, +}) { + useStyles(s); + + return ( +
+ +
+ ); +} +CustomFieldLogin.propTypes = { + input: PropTypes.shape({ + name: PropTypes.string.isRequired, + onBlur: PropTypes.func, + onChange: PropTypes.func, + onDragStart: PropTypes.func, + onDrop: PropTypes.func, + onFocus: PropTypes.func, + }).isRequired, + label: PropTypes.string, + meta: PropTypes.shape({ + error: PropTypes.string, + touched: PropTypes.bool.isRequired, + }).isRequired, + type: PropTypes.string, +}; +CustomFieldLogin.defaultProps = { + label: 'input', + type: 'text', +}; diff --git a/src/routes/login/components/Input.js b/src/routes/login/components/Input.js deleted file mode 100644 index 35aa9fd..0000000 --- a/src/routes/login/components/Input.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import capitalize from 'lodash/capitalize'; - -export default function Input({ name, inputChangeHandler, value }) { - return ( - - ); -} -Input.propTypes = { - inputChangeHandler: PropTypes.func.isRequired, - name: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, -}; diff --git a/src/routes/login/components/LoginButton.js b/src/routes/login/components/LoginButton/LoginButton.js similarity index 73% rename from src/routes/login/components/LoginButton.js rename to src/routes/login/components/LoginButton/LoginButton.js index 9014aa6..89878f4 100644 --- a/src/routes/login/components/LoginButton.js +++ b/src/routes/login/components/LoginButton/LoginButton.js @@ -4,7 +4,12 @@ import capitalize from 'lodash/capitalize'; export default function LoginButton({ name, buttonLoginHandler }) { return ( - ); diff --git a/src/utils/create-expires-cookie.js b/src/utils/create-expires-cookie.js new file mode 100644 index 0000000..949f269 --- /dev/null +++ b/src/utils/create-expires-cookie.js @@ -0,0 +1,7 @@ +export default function createExpiresCookie(minutes) { + const millisecondsInSeconds = 1000; + const secondsInMinutes = 60; + const milliseconds = minutes * millisecondsInSeconds * secondsInMinutes; + const date = new Date(Date.now() + milliseconds); + return date; +} diff --git a/src/utils/fetch.js b/src/utils/fetch.js new file mode 100644 index 0000000..c01ec66 --- /dev/null +++ b/src/utils/fetch.js @@ -0,0 +1,19 @@ +import nodeFetch from 'node-fetch'; + +const headersConfig = { + 'Content-Type': 'application/json', +}; +export default async function fetchAsync( + url, + method = 'GET', + body = null, + headers = headersConfig, +) { + const fetchingData = await nodeFetch(url, { + method, + body: JSON.stringify(body), + headers, + }); + const data = await fetchingData.json(); + return data; +} diff --git a/yarn.lock b/yarn.lock index 36bb516..afa473d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7191,6 +7191,11 @@ node-notifier@^6.0.0: which "^1.3.1" node-releases@^1.1.52: + version "1.1.52" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.52.tgz#bcffee3e0a758e92e44ecfaecd0a47554b0bcba9" + integrity sha512-snSiT1UypkgGt2wxPqS6ImEUICbNCMb31yaxWrOLXjhlt2z2/IBpaOxzONExqSm4y5oLnAqjjRWu+wsDzK5yNQ== + dependencies: + semver "^6.3.0" version "1.1.53" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==