Commit 61b25262 authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Merge branch 'redux-saga-login' into 'master'

Refactor login to redux-saga

See merge request !57
parents 97c98a38 180802c4
# see http://editorconfig.org
root = true
[*]
end_of_line = lf
[*.{js,jsx}]
charset = utf-8
indent_style = space
indent_size = 2
export const NAVIGATE = 'NAVIGATE';
export const BACK = 'BACK';
export const LOGINPROGRESS = 'LOGINPROGRESS';
export const LOGINSUCCESS = 'LOGINSUCCESS';
export const LOGINFAILURE = 'LOGINFAILURE';
export const LOGOUT = 'LOGOUT';
export const OPENDRAWER = 'OPENDRAWER';
export const CALENDARRETREIVED = 'CALENDARRETREIVED';
export const CALENDARERROR = 'CALENDARERROR';
......
import { AsyncStorage } from 'react-native';
import * as types from './actionTypes';
import { url, apiUrl } from '../url';
export const LOGIN = 'LOGIN_LOGIN';
export const FETCHING = 'LOGIN_FETCHING';
export const SUCCESS = 'LOGIN_SUCCESS';
export const FAILURE = 'LOGIN_FAILURE';
export const LOGOUT = 'LOGIN_LOGOUT';
export const RESET = 'LOGIN_RESET';
const USERNAMEKEY = '@MyStore:username';
const TOKENKEY = '@MyStore:token';
const DISPLAYNAMEKEY = '@MyStore:displayName';
const PHOTOKEY = '@MyStore:photo';
const defaultAvatar = `${url}/static/members/images/default-avatar.jpg`;
export function resetLogin() {
return (dispatch) => {
setTimeout(() => {
dispatch({
type: types.RESETLOGINSTATE,
});
}, 2000);
};
}
export function loginSuccess(username, token, displayName, photo) {
return (dispatch) => {
dispatch(resetLogin());
return dispatch({
type: types.LOGINSUCCESS,
username,
token,
displayName,
photo,
});
};
export function reset() {
return { type: RESET };
}
export function loginProgress() {
return {
type: types.LOGINPROGRESS,
};
export function success(username, token, displayName, photo) {
return { type: SUCCESS, payload: { username, token, displayName, photo } };
}
export function loginFailure() {
return (dispatch) => {
dispatch(resetLogin());
return dispatch({
type: types.LOGINFAILURE,
});
};
export function fetching() {
return { type: FETCHING };
}
export function logoutSuccess() {
return (dispatch) => {
dispatch(resetLogin());
return dispatch({
type: types.LOGOUT,
});
};
}
function getUserInfo(token) {
const data = {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Token ${token}`,
},
};
return fetch(`${apiUrl}/members/me/`, data)
.then(
response => response.json())
.then(
(responseJson) => {
const avatar = responseJson.photo === null ? defaultAvatar : responseJson.photo;
return {
photo: avatar,
displayName: responseJson.display_name,
};
},
)
.catch(
() => ({
photo: defaultAvatar,
displayName: 'Naamloos',
}),
);
export function failure() {
return { type: FAILURE };
}
export function login(user, pass) {
return (dispatch) => {
dispatch(loginProgress());
const data = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: user,
password: pass,
}),
};
return fetch(`${apiUrl}/token-auth/`, data)
.then(
response => response.json())
.then(
(responseJson) => {
if (responseJson.token) {
const token = responseJson.token;
return getUserInfo(token)
.then(
userInfo => AsyncStorage.multiSet([
[USERNAMEKEY, user],
[TOKENKEY, token],
[DISPLAYNAMEKEY, userInfo.displayName],
[PHOTOKEY, userInfo.photo],
])
.then(() => dispatch(
loginSuccess(
user, token, userInfo.displayName, userInfo.photo,
)),
));
}
return dispatch(loginFailure());
})
.catch(() => dispatch(loginFailure()));
};
return { type: LOGIN, payload: { user, pass } };
}
export function logout() {
return dispatch => AsyncStorage.multiRemove([USERNAMEKEY, TOKENKEY])
.then(dispatch(logoutSuccess()));
return { type: LOGOUT };
}
......@@ -3,16 +3,20 @@ import { AsyncStorage } from 'react-native';
import { applyMiddleware, combineReducers, createStore } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import createSagaMiddleware from 'redux-saga';
import Moment from 'moment';
import 'moment/locale/nl';
import * as reducers from './reducers';
import sagas from './sagas';
import ReduxNavigator from './components/navigator';
import { loginSuccess } from './actions/login';
import { success } from './actions/login';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const sagaMiddleware = createSagaMiddleware();
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer);
const store = createStoreWithMiddleware(reducer, applyMiddleware(thunk, sagaMiddleware));
sagaMiddleware.run(sagas);
const USERNAMEKEY = '@MyStore:username';
const TOKENKEY = '@MyStore:token';
......@@ -38,7 +42,7 @@ class Main extends Component {
const photo = values[PHOTOKEY];
if (username !== null && token !== null) {
store.dispatch(loginSuccess(username, token, displayName, photo));
store.dispatch(success(username, token, displayName, photo));
}
});
}
......
import * as types from '../actions/actionTypes';
import * as loginActions from '../actions/login';
const initialState = {
previousScenes: [],
......@@ -11,7 +12,7 @@ const initialState = {
export default function navigate(state = initialState, action = {}) {
const { currentScene, previousScenes, drawerOpen } = state;
switch (action.type) {
case types.LOGINSUCCESS: {
case loginActions.SUCCESS: {
return {
...state,
loggedIn: true,
......@@ -61,7 +62,7 @@ export default function navigate(state = initialState, action = {}) {
drawerOpen: action.drawerOpen,
};
}
case types.LOGOUT: {
case loginActions.LOGOUT: {
return initialState;
}
default:
......
import * as types from '../actions/actionTypes';
import * as loginActions from '../actions/login';
const initialState = {
loginState: '',
......@@ -9,25 +9,25 @@ const initialState = {
};
export default function session(state = initialState, action = {}) {
console.log(state);
switch (action.type) {
case types.LOGINSUCCESS:
case loginActions.SUCCESS:
return {
...state,
loginState: 'success',
username: action.username,
token: action.token,
displayName: action.displayName,
photo: action.photo,
username: action.payload.username,
token: action.payload.token,
displayName: action.payload.displayName,
photo: action.payload.photo,
};
case types.LOGINFAILURE:
case loginActions.FAILURE:
return { ...state, loginState: 'failure' };
case types.LOGINPROGRESS:
case loginActions.FETCHING:
return { ...state, loginState: 'progress' };
case types.LOGOUT:
case loginActions.LOGOUT:
return { ...initialState, loginState: 'logout' };
case types.RESETLOGINSTATE:
case loginActions.RESET:
return { ...state, loginState: '' };
default:
return state;
}
......
import { all, fork } from 'redux-saga/effects';
import loginSaga from './login';
const sagas = function* sagas() {
yield all([
fork(loginSaga),
]);
};
export default sagas;
import { delay } from 'redux-saga';
import { call, takeEvery, put } from 'redux-saga/effects';
import { AsyncStorage } from 'react-native';
import { apiRequest, url } from '../url';
import * as loginActions from '../actions/login';
const USERNAMEKEY = '@MyStore:username';
const TOKENKEY = '@MyStore:token';
const DISPLAYNAMEKEY = '@MyStore:displayName';
const PHOTOKEY = '@MyStore:photo';
const defaultAvatar = `${url}/static/members/images/default-avatar.jpg`;
const login = function* login(action) {
const { user, pass } = action.payload;
yield put(loginActions.fetching());
let data = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: user,
password: pass,
}),
};
try {
let response = yield call(apiRequest, 'token-auth', data);
const { token } = response;
if (!token) {
throw Error();
}
data = {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Token ${token}`,
},
};
response = yield call(apiRequest, 'members/me', data);
const displayName = response.display_name;
const avatar = response.photo === null ? defaultAvatar : response.photo;
yield call(AsyncStorage.multiSet, [
[USERNAMEKEY, user],
[TOKENKEY, token],
[DISPLAYNAMEKEY, displayName],
[PHOTOKEY, avatar],
]);
yield put(loginActions.success(
user, token, displayName, avatar,
));
yield delay(2000);
yield put(loginActions.reset());
} catch (error) {
console.log(error);
yield put(loginActions.failure());
yield delay(2000);
yield put(loginActions.reset());
}
};
const logout = function* logout() {
yield call(AsyncStorage.multiRemove, [USERNAMEKEY, TOKENKEY]);
yield delay(2000);
yield put(loginActions.reset());
};
const loginSaga = function* loginSaga() {
yield takeEvery(loginActions.LOGIN, login);
yield takeEvery(loginActions.LOGOUT, logout);
};
export default loginSaga;
......@@ -6,3 +6,5 @@ if (__DEV__) { // eslint-disable-line no-undef
export const url = server;
export const apiUrl = `${server}/api/v1`;
export const pizzaUrl = 'https://pizza.thalia.nu';
export const apiRequest = (route, data) => fetch(`${apiUrl}/${route}/`, data).then(response => response.json());
......@@ -4069,9 +4069,9 @@ react-native-vector-icons@^4.0.1:
prop-types "^15.5.10"
yargs "^8.0.2"
react-native@0.48.1:
version "0.48.1"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.48.1.tgz#052ba5a86d3bdb748c288124248727d02b1c0939"
react-native@0.48.3:
version "0.48.3"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.48.3.tgz#ec17a66929eb292512b14c091cf260b25e2fba18"
dependencies:
absolute-path "^0.0.0"
art "^0.10.0"
......@@ -4278,6 +4278,10 @@ rechoir@^0.6.2:
dependencies:
resolve "^1.1.6"
redux-saga@^0.15.6:
version "0.15.6"
resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.15.6.tgz#8638dc522de6c6c0a496fe8b2b5466287ac2dc4d"
redux-thunk@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment