diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 91aa02f..82b7a15 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -3,7 +3,8 @@ - + + @@ -15,45 +16,118 @@ + + + - + - - + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - + + + + + + + + + + + @@ -66,6 +140,9 @@ appName + signInWithEmailAndPassword + signUp + .reset @@ -78,22 +155,24 @@ @@ -109,8 +188,8 @@ - @@ -122,42 +201,42 @@ - + - + - - + + - - + + - - + + - - + + - + + @@ -27,5 +25,6 @@ class SignIn extends Component { } export default reduxForm({ - form: 'auth' + form: 'auth', + validate })(SignIn) \ No newline at end of file diff --git a/src/components/auth/SignUp.js b/src/components/auth/SignUp.js index 706f7ae..34841f9 100644 --- a/src/components/auth/SignUp.js +++ b/src/components/auth/SignUp.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import {reduxForm, Field} from 'redux-form' -import emailValidator from 'email-validator' +import {validate} from './utils' import ErrorField from '../common/ErrorField' class SignUp extends Component { @@ -24,18 +24,6 @@ class SignUp extends Component { } } -const validate = ({ email, password }) => { - const errors = {} - - if (!email) errors.email = 'email is a required field' - if (!password) errors.password = 'password is a required field' - - if (email && !emailValidator.validate(email)) errors.email = 'invalid email' - if (password && password.length < 8) errors.password = 'to short' - - return errors -} - export default reduxForm({ form: 'auth', validate diff --git a/src/components/auth/utils.js b/src/components/auth/utils.js new file mode 100644 index 0000000..3d22d95 --- /dev/null +++ b/src/components/auth/utils.js @@ -0,0 +1,13 @@ +import emailValidator from "email-validator/index"; + +export const validate = ({ email, password }) => { + const errors = {} + + if (!email) errors.email = 'email is a required field' + if (!password) errors.password = 'password is a required field' + + if (email && !emailValidator.validate(email)) errors.email = 'invalid email' + if (password && password.length < 8) errors.password = 'to short' + + return errors +} \ No newline at end of file diff --git a/src/components/routes/Auth.js b/src/components/routes/Auth.js index a183ff9..ec4898e 100644 --- a/src/components/routes/Auth.js +++ b/src/components/routes/Auth.js @@ -1,7 +1,7 @@ import React, { Component } from 'react' import {Route, NavLink} from 'react-router-dom' import {connect} from 'react-redux' -import {signUp} from '../../ducks/auth' +import {signUp, signIn} from '../../ducks/auth' import SignIn from '../auth/SignIn' import SignUp from '../auth/SignUp' @@ -22,8 +22,8 @@ class AuthPage extends Component { ) } - handleSignIn = (values) => console.log('---', 'sign in', values) + handleSignIn = ({email, password}) => this.props.signIn(email, password) handleSignUp = ({email, password}) => this.props.signUp(email, password) } -export default connect(null, { signUp })(AuthPage) \ No newline at end of file +export default connect(null, { signUp, signIn })(AuthPage) \ No newline at end of file diff --git a/src/config.js b/src/config.js index 2782a0b..161087f 100644 --- a/src/config.js +++ b/src/config.js @@ -1,12 +1,12 @@ import firebase from 'firebase' -export const appName = 'advreact-1610' +export const appName = 'react-course-f262b' firebase.initializeApp({ - apiKey: "AIzaSyB31xpTtp4Jln_hb2kAbE4PGf6Mi8EgLyA", authDomain: `${appName}.firebaseapp.com`, databaseURL: `https://${appName}.firebaseio.com`, projectId: appName, - storageBucket: "", - messagingSenderId: "397157634637" + apiKey: "AIzaSyAt-smU_1tqNdkj9pmSpVLxqaOjhDwf4dk", + storageBucket: "react-course-f262b.appspot.com", + messagingSenderId: "447165438476" }) \ No newline at end of file diff --git a/src/ducks/auth.js b/src/ducks/auth.js index 25f3bf7..a783566 100644 --- a/src/ducks/auth.js +++ b/src/ducks/auth.js @@ -3,6 +3,8 @@ import {Record} from 'immutable' import firebase from 'firebase' import {createSelector} from 'reselect' import {call, put, all, take} from 'redux-saga/effects' +import {reset} from 'redux-form' +import {push} from 'react-router-redux' /** * Constants @@ -14,7 +16,9 @@ export const SIGN_UP_START = `${prefix}/SIGN_UP_START` export const SIGN_UP_SUCCESS = `${prefix}/SIGN_UP_SUCCESS` export const SIGN_UP_ERROR = `${prefix}/SIGN_UP_ERROR` +export const SIGN_IN_START = `${prefix}/SIGN_IN_START` export const SIGN_IN_SUCCESS = `${prefix}/SIGN_IN_SUCCESS` +export const SIGN_IN_ERROR = `${prefix}/SIGN_IN_ERROR` /** * Reducer @@ -60,6 +64,13 @@ export function signUp(email, password) { } } +export function signIn(email, password) { + return { + type: SIGN_IN_START, + payload: { email, password } + } +} + firebase.auth().onAuthStateChanged(user => { if (!user) return @@ -81,12 +92,13 @@ export function * signUpSaga() { try { const user = yield call([auth, auth.createUserWithEmailAndPassword], payload.email, payload.password) - //const user = apply(auth, createUserWithEmailAndPassword, [email, password]) yield put({ type: SIGN_UP_SUCCESS, payload: {user} }) + + yield put(reset('auth')) } catch (error) { yield put({ type: SIGN_UP_ERROR, @@ -96,8 +108,42 @@ export function * signUpSaga() { } } +export function * signInSaga() { + const auth = firebase.auth() + + while (true) { + const {payload} = yield take(SIGN_IN_START) + + try { + const user = yield call([auth, auth.signInWithEmailAndPassword], payload.email, payload.password) + + yield put({ + type: SIGN_IN_SUCCESS, + payload: {user} + }) + + yield put(reset('auth')) + } catch (error) { + yield put({ + type: SIGN_IN_ERROR, + payload: {error} + }) + } + } +} + +export function * redirectSaga() { + while (true) { + yield take(SIGN_IN_SUCCESS) + + yield put(push('/people')) + } +} + export function * saga() { yield all([ - signUpSaga() + signUpSaga(), + signInSaga(), + redirectSaga() ]) } \ No newline at end of file diff --git a/src/ducks/auth.test.js b/src/ducks/auth.test.js new file mode 100644 index 0000000..56fd5e5 --- /dev/null +++ b/src/ducks/auth.test.js @@ -0,0 +1,146 @@ +import { + signIn, + signUp, + signInSaga, + signUpSaga, + redirectSaga, + SIGN_IN_START, + SIGN_IN_SUCCESS, + SIGN_IN_ERROR, + SIGN_UP_START, + SIGN_UP_SUCCESS, + SIGN_UP_ERROR +} from './auth' +import {call, put, take} from 'redux-saga/effects' +import firebase from 'firebase' +import {reset} from 'redux-form' +import {push} from 'react-router-redux' + +/** + * Sign in + * */ + +it('should sign in successfully', () => { + const authData = { + email: 'test@test.com', + password: '12345678' + } + + const mockUser = { + name: 'Ivan', + age: 22 + } + + const auth = firebase.auth() + + const requestAction = signIn(authData) + + const gen = signInSaga(requestAction) + + expect(gen.next().value).toEqual(take(SIGN_IN_START)) + + expect(gen.next({payload: authData}).value).toEqual(call([auth, auth.signInWithEmailAndPassword], authData.email, authData.password)) + + expect(gen.next(mockUser).value).toEqual(put({ + type: SIGN_IN_SUCCESS, + payload: {user: mockUser} + })) + + expect(gen.next().value).toEqual(put(reset('auth'))); +}) + +it('should sign in with error', () => { + const authData = { + email: 'test@test.com', + password: '12345678' + } + + const mockError = new Error('error'); + + const auth = firebase.auth() + + const requestAction = signIn(authData) + + const gen = signInSaga(requestAction) + + expect(gen.next().value).toEqual(take(SIGN_IN_START)) + + expect(gen.next({payload: authData}).value).toEqual(call([auth, auth.signInWithEmailAndPassword], authData.email, authData.password)) + + expect(gen.throw(mockError).value).toEqual(put({ + type: SIGN_IN_ERROR, + payload: {error: mockError} + })) +}) + +/** + * Sign up + * */ + +it('should sign up successfully', () => { + const authData = { + email: 'test@test.com', + password: '12345678' + } + + const mockUser = { + name: 'Ivan', + age: 22 + } + + const auth = firebase.auth() + + const requestAction = signUp(authData) + + const gen = signUpSaga(requestAction) + + expect(gen.next().value).toEqual(take(SIGN_UP_START)) + + expect(gen.next({payload: authData}).value).toEqual(call([auth, auth.createUserWithEmailAndPassword], authData.email, authData.password)) + + expect(gen.next(mockUser).value).toEqual(put({ + type: SIGN_UP_SUCCESS, + payload: {user: mockUser} + })) + + expect(gen.next().value).toEqual(put(reset('auth'))); +}) + +it('should sign up with error', () => { + const authData = { + email: 'test@test.com', + password: '12345678' + } + + const mockError = new Error('error'); + + const auth = firebase.auth() + + const requestAction = signUp(authData) + + const gen = signUpSaga(requestAction) + + expect(gen.next().value).toEqual(take(SIGN_UP_START)) + + expect(gen.next({payload: authData}).value).toEqual(call([auth, auth.createUserWithEmailAndPassword], authData.email, authData.password)) + + expect(gen.throw(mockError).value).toEqual(put({ + type: SIGN_UP_ERROR, + payload: {error: mockError} + })) +}) + + +/** + * Redirect + * */ + +it('should redirect after successful login', () => { + const gen = redirectSaga() + + expect(gen.next().value).toEqual(take(SIGN_IN_SUCCESS)); + + expect(gen.next().value).toEqual(put(push('/people'))) +}) + + diff --git a/src/ducks/people.js b/src/ducks/people.js index 9a2d814..ac0de77 100644 --- a/src/ducks/people.js +++ b/src/ducks/people.js @@ -2,6 +2,7 @@ import {appName} from '../config' import {Record, List} from 'immutable' import {put, call, takeEvery} from 'redux-saga/effects' import {generateId} from './utils' +import {reset} from 'redux-form'; /** * Constants @@ -66,6 +67,8 @@ export function * addPersonSaga(action) { console.log('---', effect) yield effect + + yield put(reset('person')) } export function * saga() {