diff --git a/env-example b/env-example index 41ab5ca..fd5771f 100644 --- a/env-example +++ b/env-example @@ -1 +1,2 @@ -EVENT_API_BASE_URL=https://studentersamfundet.no \ No newline at end of file +EVENT_API_URL=https://studentersamfundet.no +DUSKEN_API_URL=https://galtinn.neuf.no \ No newline at end of file diff --git a/package.json b/package.json index 9de2405..c4c5b9d 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/App.js b/src/App.js index d5c3b3a..df9d998 100644 --- a/src/App.js +++ b/src/App.js @@ -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() { diff --git a/src/Dusken.js b/src/Dusken.js index 88a3a5c..9b6c015 100644 --- a/src/Dusken.js +++ b/src/Dusken.js @@ -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 }, diff --git a/src/actions.js b/src/actions.js index 4c42f0b..d909ca1 100644 --- a/src/actions.js +++ b/src/actions.js @@ -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', diff --git a/src/modules/events/EventListContainer.js b/src/modules/events/EventListContainer.js index 87a3b02..6bcf938 100644 --- a/src/modules/events/EventListContainer.js +++ b/src/modules/events/EventListContainer.js @@ -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) diff --git a/src/modules/login/Login.js b/src/modules/login/Login.js index 7c15302..6f7d615 100644 --- a/src/modules/login/Login.js +++ b/src/modules/login/Login.js @@ -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 ( - - Det Norske Studentersamfund - Chateau Neuf - - + + { - this.refs.passwordInput.focus(); + this.passwordInputEl._root.focus(); }} /> - ; + } + + return + + { + this.passwordInputEl._root.focus(); + }} + /> + ; + } + + passwordInput() { + if(this.props.loginError && this.props.loginError.password) { + return + + { this.passwordInputEl = input; }} onChangeText={this.handlePassword} onSubmitEditing={this.onLoginPress} /> - - + {this.showSpinner()} + + + ); } - spinIfLoggingIn = () => { - if (this.state.isLoggingIn) { - return + 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 {errorFormatted} } - } + else { + return + } + }; + + showSpinner = () => { + if (this.props.isLoggingIn) { + return + } + }; 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', } }); \ No newline at end of file diff --git a/src/modules/login/LoginContainer.js b/src/modules/login/LoginContainer.js index 94f22af..55f61cb 100644 --- a/src/modules/login/LoginContainer.js +++ b/src/modules/login/LoginContainer.js @@ -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 + } +} 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); diff --git a/src/modules/membership/Membership.js b/src/modules/membership/Membership.js index 53dca0c..2a6f6d8 100644 --- a/src/modules/membership/Membership.js +++ b/src/modules/membership/Membership.js @@ -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 = () => { diff --git a/src/modules/membership/MembershipContainer.js b/src/modules/membership/MembershipContainer.js index 979ca82..51ef01e 100644 --- a/src/modules/membership/MembershipContainer.js +++ b/src/modules/membership/MembershipContainer.js @@ -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) { diff --git a/src/reducers.js b/src/reducers.js index e2539bd..9a7f84a 100644 --- a/src/reducers.js +++ b/src/reducers.js @@ -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, { diff --git a/yarn.lock b/yarn.lock index 7be9140..5100f45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"