diff --git a/package.json b/package.json index 5dd9d997..c5d7aeef 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "react-test-renderer": "^15.4.2", "redux": "^3.6.0", "redux-form": "^6.5.0", + "redux-saga": "^0.14.3", "sanitize.css": "^4.1.0", "sharetribe-scripts": "0.8.6", "source-map-support": "^0.4.11" diff --git a/src/ducks/Auth.ducks.js b/src/ducks/Auth.ducks.js new file mode 100644 index 00000000..8a5be972 --- /dev/null +++ b/src/ducks/Auth.ducks.js @@ -0,0 +1,78 @@ +/** + * Authentication duck. + */ +import { delay } from 'redux-saga'; +import { call, put, takeEvery } from 'redux-saga/effects'; + +// ================ Action types ================ // + +export const LOGIN = 'app/Auth/LOGIN'; +export const LOGIN_SUCCESS = 'app/Auth/LOGIN_SUCCESS'; +export const LOGIN_FAILURE = 'app/Auth/LOGIN_FAILURE'; +export const LOGOUT = 'app/Auth/LOGOUT'; +export const LOGOUT_SUCCESS = 'app/Auth/LOGOUT_SUCCESS'; +export const LOGOUT_FAILURE = 'app/Auth/LOGOUT_FAILURE'; + +// ================ Reducer ================ // + +const initialState = { + loginInProgress: false, + logoutInProgress: false, + loginError: null, + logoutError: null, + isAuthenticated: false, + user: null, +}; + +export default function reducer(state = initialState, action = {}) { + const { type, payload } = action; + switch (type) { + case LOGIN: + return { ...state, loginInProgress: true }; + case LOGIN_SUCCESS: + return { ...state, loginInProgress: false, isAuthenticated: true, user: payload }; + case LOGIN_FAILURE: + return { ...state, loginInProgress: false, loginError: payload }; + case LOGOUT: + return { ...state, logoutInProgress: true }; + case LOGOUT_SUCCESS: + return { ...state, logoutInProgress: false, isAuthenticated: false, user: null }; + case LOGOUT_FAILURE: + return { ...state, logoutInProgress: false, logoutError: payload }; + default: + return state; + } +} + +// ================ Action creators ================ // + +export const login = (email, password) => ({ type: LOGIN, payload: { email, password } }); +export const loginSuccess = user => ({ type: LOGIN_SUCCESS, payload: user }); +export const loginFailure = error => ({ type: LOGIN_FAILURE, payload: error, error: true }); + +export const logout = () => ({ type: LOGOUT }); +export const logoutSuccess = () => ({ type: LOGOUT_SUCCESS }); +export const logoutFailure = error => ({ type: LOGOUT_FAILURE, payload: error, error: true }); + +// ================ Worker sagas ================ // + +function* callLogin(action) { + const { payload } = action; + yield call(delay, 1000); + yield put(loginSuccess(payload)); +} + +function* callLogout() { + yield call(delay, 1000); + yield put(logoutSuccess()); +} + +// ================ Watcher sagas ================ // + +export function* watchLogin() { + yield takeEvery(LOGIN, callLogin); +} + +export function* watchLogout() { + yield takeEvery(LOGOUT, callLogout); +} diff --git a/src/ducks/index.js b/src/ducks/index.js index 7a760a4d..2ee369e4 100644 --- a/src/ducks/index.js +++ b/src/ducks/index.js @@ -5,7 +5,8 @@ */ import { reducer as formReducer } from 'redux-form'; +import Auth from './Auth.ducks'; import FlashNotification from './FlashNotification.ducks'; import LocationFilter from './LocationFilter.ducks'; -export { formReducer as form, FlashNotification, LocationFilter }; +export { formReducer as form, Auth, FlashNotification, LocationFilter }; diff --git a/src/sagas.js b/src/sagas.js new file mode 100644 index 00000000..ae72fac3 --- /dev/null +++ b/src/sagas.js @@ -0,0 +1,5 @@ +import { watchLogin, watchLogout } from './ducks/Auth.ducks'; + +export default function* rootSaga() { + yield [watchLogin(), watchLogout()]; +} diff --git a/src/store.js b/src/store.js index 04929ca7..ad4f25f8 100644 --- a/src/store.js +++ b/src/store.js @@ -1,18 +1,29 @@ /* eslint-disable no-underscore-dangle */ -import { createStore } from 'redux'; +import { createStore, applyMiddleware, compose } from 'redux'; +import createSagaMiddleware from 'redux-saga'; import createReducer from './reducers'; +import rootSaga from './sagas'; /** - * configureStore creates a new store with initial state and possible enhancers - * (like redux-saga or redux-thunk middleware) + * Create a new store with the given initial state. Adds Redux + * middleware and enhancers. */ -export default function configureStore(initialState) { - const useReduxDevTools = process.env.NODE_ENV !== 'production' && - typeof window !== 'undefined' && - window.__REDUX_DEVTOOLS_EXTENSION__; +export default function configureStore(initialState = {}) { + const sagaMiddleware = createSagaMiddleware(); - if (useReduxDevTools) { - return createStore(createReducer(), initialState, window.__REDUX_DEVTOOLS_EXTENSION__()); - } - return createStore(createReducer(), initialState); + const middlewares = [sagaMiddleware]; + + // Enable Redux Devtools in client side dev mode. + const composeEnhancers = process.env.NODE_ENV !== 'production' && + typeof window === 'object' && + window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ + ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ + : compose; + + const enhancer = composeEnhancers(applyMiddleware(...middlewares)); + const store = createStore(createReducer(), initialState, enhancer); + + sagaMiddleware.run(rootSaga); + + return store; } diff --git a/yarn.lock b/yarn.lock index bdfbb57e..f4f3b313 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5129,6 +5129,10 @@ redux-form@^6.5.0: lodash "^4.17.3" lodash-es "^4.17.3" +redux-saga@^0.14.3: + version "0.14.3" + resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.14.3.tgz#28cd2c5b49cf780a1de25875765724150c630513" + redux@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/redux/-/redux-3.6.0.tgz#887c2b3d0b9bd86eca2be70571c27654c19e188d"