Login: Misc Improvements

Restyle with NativeBase components
Use DUSKEN_API_URL
Move redux handling stuff to LoginContainer
Add redux-logger
This commit is contained in:
nikolark committed 2017-08-09 00:44:56 +02:00
1 parent aae1e982ed
commit 1c42c0c925
12 files changed
+163 -98

No files matched your search

+2 -1
View File
@@ -1 +1,2 @@
EVENT_API_BASE_URL=https://studentersamfundet.no
EVENT_API_URL=https://studentersamfundet.no
DUSKEN_API_URL=https://galtinn.neuf.no
+1
View File
@@ -18,6 +18,7 @@
"react-navigation": "^1.0.0-beta.11",
"react-redux": "^5.0.5",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.2.0"
},
"devDependencies": {
+2 -1
View File
@@ -2,10 +2,11 @@ import Dusken from "./Dusken";
import React, {Component} from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import duskenApp from "./reducers";
let store = createStore(duskenApp, applyMiddleware(thunk));
let store = createStore(duskenApp, applyMiddleware(...[logger, thunk]));
export default class App extends Component {
render() {
+2
View File
@@ -7,9 +7,11 @@ import EventsScreen from "./screens/EventScreen";
import EventDetailsScreen from "./screens/EventDetailsScreen";
import AboutScreen from "./screens/AboutScreen";
import MembershipScreen from "./screens/MembershipScreen";
import Login from "./modules/login/Login";
const tabBarRoutes = {
// Login: { screen: LoginScreen },
Events: { screen: EventsScreen },
Membership: { screen: MembershipScreen },
About: { screen: AboutScreen },
+12 -8
View File
@@ -1,3 +1,5 @@
import Config from 'react-native-config';
const LOGIN_REQUEST = 'LOGIN_REQUEST';
const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
const LOGIN_FAILURE = 'LOGIN_FAILURE';
@@ -20,10 +22,10 @@ function loginSuccess(username, token) {
}
}
function loginFailure(errorMessage) {
function loginFailure(loginError) {
return {
type: LOGIN_FAILURE,
errorMessage
loginError
}
}
@@ -66,9 +68,9 @@ export {
export function requestLogin(username, password) {
return (dispatch) => {
// We are now logging in
dispatch(loginRequest())
return fetch('http://10.0.0.10:3000/auth/obtain-token/', {
dispatch(loginRequest());
const apiURL = Config.DUSKEN_API_URL;
return fetch(`${apiURL}/auth/obtain-token/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
@@ -91,7 +93,8 @@ export function requestLogin(username, password) {
dispatch(loginSuccess(username, data.token))
},
data => {
dispatch(loginFailure(data.error || 'Log in failed'))
console.log(data);
dispatch(loginFailure(data || {'non_field_errors': ['Kunne ikke logge inn, prøv igjen']}))
}
)
}
@@ -100,9 +103,10 @@ export function requestLogin(username, password) {
export function requestUserData(token) {
return (dispatch) => {
// We are now fetching user data
dispatch(userDataRequest())
dispatch(userDataRequest());
return fetch('http://10.0.0.10:3000/api/me/', {
const apiURL = Config.DUSKEN_API_URL;
return fetch(`${apiURL}/api/me/`, {
method: 'GET',
headers: {
'Accept': 'application/json',
+2 -2
View File
@@ -2,9 +2,9 @@ import {connect} from "react-redux";
import EventList from "./EventList";
import React from 'react';
import Config from 'react-native-config';
import moment from "moment";
import 'moment/locale/nb';
import Config from 'react-native-config'
moment.locale('nb');
@@ -74,7 +74,7 @@ class EventListContainer extends React.Component {
fetchEvents() {
const { page } = this.state;
const url = `${Config.EVENT_API_BASE_URL}/wp-json/wp/v2/events?page=${page}&future=1`;
const url = `${Config.EVENT_API_URL}/wp-json/wp/v2/events?page=${page}&future=1`;
this.setState({ loading: true });
fetch(url)
+84 -69
View File
@@ -1,87 +1,104 @@
import React, { Component } from 'react';
import {Button, Image, StyleSheet, Text, TextInput, View} from 'react-native';
import { Spinner } from 'native-base';
// import { login } from "../../actions";
import {StyleSheet, View} from 'react-native';
import { Container, Header, Content, Form, Item, Input, Label, Spinner, Button, Text as NBText, Icon} from 'native-base';
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false,
isLoggingIn: false,
errorMessage: null,
email: '',
password: ''
}
}
componentWillReceiveProps(nextProps) {
if(this.state.isAuthenticated !== nextProps.isAuthenticated) {
this.setState({
isAuthenticated: nextProps.isAuthenticated
})
if (nextProps.isAuthenticated) {
this.onLogin();
}
}
if(this.state.isLoggingIn !== nextProps.isLoggingIn) {
this.setState({
isLoggingIn: nextProps.isLoggingIn
})
}
if(this.state.errorMessage !== nextProps.errorMessage) {
this.setState({
errorMessage: nextProps.errorMessage
})
}
}
render() {
const pic = {
uri: 'https://dusken.neuf.no/static/dist/images/logo.png'
};
return (
<View style={styles.container}>
<Text style={styles.text}>Det Norske Studentersamfund</Text>
<Text style={styles.text}>Chateau Neuf</Text>
<Image source={pic} style={{width: 200, height: 150, marginBottom: 40}} resizeMode='contain' />
<TextInput
placeholder="E-post"
style={styles.input}
emailInput() {
if(this.props.loginError && this.props.loginError.username) {
return <Item floatingLabel error>
<Label>E-post / Brukernavn</Label>
<Input
keyboardType="email-address"
autoCapitalize="none"
returnKeyType="next"
onChangeText={this.handleEmail}
onSubmitEditing={(event) => {
this.refs.passwordInput.focus();
this.passwordInputEl._root.focus();
}}
/>
<TextInput
placeholder="Passord"
style={styles.input}
</Item>;
}
return <Item floatingLabel>
<Label>E-post / Brukernavn</Label>
<Input
keyboardType="email-address"
autoCapitalize="none"
returnKeyType="next"
onChangeText={this.handleEmail}
onSubmitEditing={(event) => {
this.passwordInputEl._root.focus();
}}
/>
</Item>;
}
passwordInput() {
if(this.props.loginError && this.props.loginError.password) {
return <Item floatingLabel last error>
<Label>Passord</Label>
<Input
secureTextEntry={true}
autoCapitalize="none"
ref='passwordInput'
getRef={(input) => { this.passwordInputEl = input; }}
onChangeText={this.handlePassword}
onSubmitEditing={this.onLoginPress}
/>
<View style={styles.button}>
<Button
title="Logg inn"
onPress={this.onLoginPress}
/>
</View>
{this.spinIfLoggingIn}
</View>
</Item>
}
return <Item floatingLabel last>
<Label>Passord</Label>
<Input
secureTextEntry={true}
autoCapitalize="none"
getRef={(input) => { this.passwordInputEl = input; }}
onChangeText={this.handlePassword}
onSubmitEditing={this.onLoginPress}
/>
</Item>
}
render() {
return (
<Container style={styles.container}>
<Content>
<Form>
{this.emailInput()}
{this.passwordInput()}
{this.showError()}
<Button block onPress={this.onLoginPress} style={styles.loginButton}><NBText>Logg inn</NBText></Button>
{this.showSpinner()}
</Form>
</Content>
</Container>
);
}
spinIfLoggingIn = () => {
if (this.state.isLoggingIn) {
return <Spinner color="#f58220"></Spinner>
showError= () => {
if (this.props.loginError) {
const err = this.props.loginError.non_field_errors;
let errorFormatted = err ? err[0] : 'Feil brukernavn eller passord, prøv igjen...';
// TODO: Format these and highlight error fields
return <View style={styles.errorBox}><NBText style={styles.errorMessage}>{errorFormatted}</NBText></View>
}
}
else {
return <View style={styles.errorBox}/>
}
};
showSpinner = () => {
if (this.props.isLoggingIn) {
return <Spinner color="#f58220"/>
}
};
handleEmail = (text) => {
this.setState({ email: text })
@@ -95,30 +112,28 @@ export default class Login extends Component {
this.props.requestLogin(this.state.email, this.state.password)
};
onLogin = () => {
this.props.onLogin();
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
backgroundColor: 'white'
},
input: {
width: 320,
fontSize: 20,
height: 52,
},
button: {
marginTop: 20,
padding: 10,
width: 240,
loginButton: {
marginVertical: 16,
marginHorizontal: 16
},
text: {
fontSize: 18,
fontWeight: 'bold'
errorBox: {
marginVertical: 16,
},
errorMessage: {
textAlign: 'center',
color: 'red',
}
});
+38 -9
View File
@@ -1,13 +1,42 @@
import React, {Component} from 'react';
import {connect} from "react-redux";
import { bindActionCreators } from "redux";
import Login from "./Login";
import { requestLogin } from "../../actions";
import {requestLogin} from "../../actions";
class LoginContainer extends Component {
constructor(state) {
super(state);
this.state = {
isAuthenticated: false,
isLoggingIn: false,
loginError: null,
}
}
componentWillReceiveProps(nextProps) {
if (this.state.isAuthenticated !== nextProps.isAuthenticated) {
if (nextProps.isAuthenticated) {
this.props.onLogin();
}
}
if (this.state.isLoggingIn !== nextProps.isLoggingIn) {
this.setState({isLoggingIn: nextProps.isLoggingIn})
}
if (this.state.loginError !== nextProps.loginError) {
this.setState({loginError: nextProps.loginError})
}
}
render() {
return <Login {...this.state} requestLogin={this.props.requestLogin}/>
}
}
export default connect(
(state) => ({
isAuthenticated: state.isAuthenticated,
isLoggingIn: state.isLoggingIn,
errorMessage: state.errorMessage,
}),
{ requestLogin }
)(Login);
(state) => ({
isAuthenticated: state.isAuthenticated,
isLoggingIn: state.isLoggingIn,
loginError: state.loginError,
}),
{requestLogin}
)(LoginContainer);
+2 -2
View File
@@ -30,8 +30,8 @@ export default class Membership extends Component {
};
onPurchasePress() {
// FIXME: Move this to process.env.DUSKEN_URL and use babel thing to do search replace
Linking.openURL('https://medlem.neuf.no');
// FIXME: Move this to .env as DUSKEN_URL
Linking.openURL('https://galtinn.neuf.no');
};
onLoginPress = () => {
@@ -26,7 +26,7 @@ class MembershipContainer extends React.Component {
this.setState({isAuthenticated: nextProps.isAuthenticated})
}
if(this.state.userToken !== nextProps.userToken) {
this.setState({userToken: nextProps.userToken})
this.setState({userToken: nextProps.userToken});
this.fetchUser();
}
if(this.state.user !== nextProps.user) {
@@ -37,11 +37,11 @@ class MembershipContainer extends React.Component {
fetchUser = () => {
if (!this.state.userToken) { return; }
this.props.requestUserData(this.state.userToken)
}
};
onLoginPress = () => {
this.props.onLoginPress();
}
};
render() {
if (this.state.user) {
+5 -3
View File
@@ -14,19 +14,21 @@ const initialState = {
isFetchingUserData: false,
userToken: null,
user: null,
loginError: null,
};
export default function duskenApp(state = initialState, action) {
switch (action.type) {
case LOGIN_REQUEST:
return Object.assign({}, state, {
isLoggingIn: true
isLoggingIn: true,
loginError: null,
});
case LOGIN_FAILURE:
return Object.assign({}, state, {
isLoggingIn: false,
errorMessage: action.errorMessage
loginError: action.loginError
});
case LOGIN_SUCCESS:
@@ -37,7 +39,7 @@ export default function duskenApp(state = initialState, action) {
});
case LOGOUT:
return initialState
return initialState;
case USER_DATA_REQUEST:
return Object.assign({}, state, {
+10
View File
@@ -1377,6 +1377,10 @@ decamelize@^1.0.0, decamelize@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
deep-diff@^0.3.5:
version "0.3.8"
resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84"
deep-extend@~0.4.0:
version "0.4.2"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
@@ -3705,6 +3709,12 @@ rebound@^0.0.13:
version "0.0.13"
resolved "https://registry.yarnpkg.com/rebound/-/rebound-0.0.13.tgz#4a225254caf7da756797b19c5817bf7a7941fac1"
redux-logger@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf"
dependencies:
deep-diff "^0.3.5"
redux-thunk@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"