Commit 951826ad authored by Wietse Kuipers's avatar Wietse Kuipers
Browse files

Merge branch 'feature/error-screens' into 'master'

Add nicer error screens/handling

Closes #18

See merge request !71
parents a516fa8b ff1f1041
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Text, View, SectionList } from 'react-native'; import { Text, View, SectionList, ScrollView, RefreshControl } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Moment from 'moment'; import Moment from 'moment';
import 'moment/locale/nl'; import 'moment/locale/nl';
import * as calendarActions from '../actions/calendar'; import * as calendarActions from '../actions/calendar';
import EventCard from './EventCard'; import EventCard from './EventCard';
import LoadingScreen from './LoadingScreen';
import ErrorScreen from './ErrorScreen';
import styles from './style/calendar'; import styles from './style/calendar';
...@@ -123,17 +125,39 @@ class Calendar extends Component { ...@@ -123,17 +125,39 @@ class Calendar extends Component {
}; };
render() { render() {
if (this.props.eventList.length === 0 && !this.props.loading) { if (this.props.status === 'initial') {
return <LoadingScreen />;
} else if (this.props.status === 'failure') {
return ( return (
<View> <ScrollView
<Text> contentContainerStyle={styles.content}
No events found! refreshControl={(
</Text> <RefreshControl
</View> onRefresh={this.handleRefresh}
refreshing={this.props.loading}
/>
)}
>
<ErrorScreen message="Sorry! We couldn't load any data." />
</ScrollView>
);
} else if (this.props.eventList.length === 0) {
return (
<ScrollView
contentContainerStyle={styles.content}
refreshControl={(
<RefreshControl
onRefresh={this.handleRefresh}
refreshing={this.props.loading}
/>
)}
>
<ErrorScreen message="No events found!" />
</ScrollView>
); );
} }
return ( return (
<View> <View style={styles.content}>
<SectionList <SectionList
style={styles.sectionList} style={styles.sectionList}
renderItem={renderItem} renderItem={renderItem}
...@@ -166,12 +190,14 @@ Calendar.propTypes = { ...@@ -166,12 +190,14 @@ Calendar.propTypes = {
url: PropTypes.string, url: PropTypes.string,
})).isRequired, })).isRequired,
loading: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
refresh: PropTypes.func.isRequired, refresh: PropTypes.func.isRequired,
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
eventList: state.calendar.eventList, eventList: state.calendar.eventList,
loading: state.calendar.loading, loading: state.calendar.loading,
status: state.calendar.status,
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
......
import React from 'react';
import { Image, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import styles from './style/errorScreen';
const smiley = require('../img/smiley.png');
const ErrorScreen = props => (
<View
style={styles.content}
>
<Image
source={smiley}
style={styles.image}
/>
<Text style={styles.text}>{props.message}</Text>
<Text style={styles.text}>Try again later.</Text>
</View>
);
ErrorScreen.propTypes = {
message: PropTypes.string.isRequired,
};
export default ErrorScreen;
...@@ -8,6 +8,7 @@ import 'moment/locale/nl'; ...@@ -8,6 +8,7 @@ import 'moment/locale/nl';
import styles from './style/event'; import styles from './style/event';
import MemberView from './MemberView'; import MemberView from './MemberView';
import LoadingScreen from './LoadingScreen'; import LoadingScreen from './LoadingScreen';
import ErrorScreen from './ErrorScreen';
import { colors } from '../style'; import { colors } from '../style';
class Event extends Component { class Event extends Component {
...@@ -252,9 +253,7 @@ class Event extends Component { ...@@ -252,9 +253,7 @@ class Event extends Component {
); );
} }
return ( return (
<ScrollView backgroundColor={colors.background} contentContainerStyle={styles.eventView}> <ErrorScreen message="Could not load the event..." />
<Text>Kon het evenement niet laden...</Text>
</ScrollView>
); );
} }
} }
......
...@@ -6,19 +6,13 @@ import Icon from 'react-native-vector-icons/MaterialIcons'; ...@@ -6,19 +6,13 @@ import Icon from 'react-native-vector-icons/MaterialIcons';
import Moment from 'moment'; import Moment from 'moment';
import 'moment/locale/nl'; import 'moment/locale/nl';
import LoadingScreen from './LoadingScreen'; import LoadingScreen from './LoadingScreen';
import ErrorScreen from './ErrorScreen';
import { retrievePizzaInfo, cancelOrder, orderPizza } from '../actions/pizza'; import { retrievePizzaInfo, cancelOrder, orderPizza } from '../actions/pizza';
import styles from './style/pizza'; import styles from './style/pizza';
import { colors } from '../style'; import { colors } from '../style';
class Pizza extends Component { class Pizza extends Component {
constructor(props) {
super(props);
this.state = {
refreshing: false,
};
}
getProductFromList = (pk, pizzaList) => { getProductFromList = (pk, pizzaList) => {
for (let i = 0; i < pizzaList.length; i += 1) { for (let i = 0; i < pizzaList.length; i += 1) {
if (pizzaList[i].pk === pk) { if (pizzaList[i].pk === pk) {
...@@ -122,9 +116,7 @@ class Pizza extends Component { ...@@ -122,9 +116,7 @@ class Pizza extends Component {
); );
handleRefresh = () => { handleRefresh = () => {
this.setState({ refreshing: true });
this.props.retrievePizzaInfo(this.props.token); this.props.retrievePizzaInfo(this.props.token);
this.setState({ refreshing: false });
}; };
render() { render() {
...@@ -135,16 +127,13 @@ class Pizza extends Component { ...@@ -135,16 +127,13 @@ class Pizza extends Component {
<ScrollView <ScrollView
refreshControl={ refreshControl={
<RefreshControl <RefreshControl
refreshing={this.state.refreshing} refreshing={this.props.loading}
onRefresh={this.handleRefresh} onRefresh={this.handleRefresh}
/> />
} }
contentContainerStyle={styles.content}
> >
<View style={styles.content}> <ErrorScreen message="Sorry! We couldn't load any data." />
<Text
style={styles.title}
>Something went wrong while retrieving pizza info.</Text>
</View>
</ScrollView> </ScrollView>
); );
} else if (!this.props.event) { } else if (!this.props.event) {
...@@ -152,16 +141,15 @@ class Pizza extends Component { ...@@ -152,16 +141,15 @@ class Pizza extends Component {
<ScrollView <ScrollView
refreshControl={ refreshControl={
<RefreshControl <RefreshControl
refreshing={this.state.refreshing} refreshing={this.props.loading}
onRefresh={this.handleRefresh} onRefresh={this.handleRefresh}
/> />
} }
contentContainerStyle={styles.content}
> >
<View style={styles.content}> <Text
<Text style={styles.title}
style={styles.title} >There is currently no event for which you can order food.</Text>
>There is currently no event for which you can order food.</Text>
</View>
</ScrollView> </ScrollView>
); );
} }
...@@ -186,7 +174,7 @@ class Pizza extends Component { ...@@ -186,7 +174,7 @@ class Pizza extends Component {
<ScrollView <ScrollView
refreshControl={ refreshControl={
<RefreshControl <RefreshControl
refreshing={this.state.refreshing} refreshing={this.props.loading}
onRefresh={this.handleRefresh} onRefresh={this.handleRefresh}
/> />
} }
...@@ -205,6 +193,7 @@ class Pizza extends Component { ...@@ -205,6 +193,7 @@ class Pizza extends Component {
Pizza.propTypes = { Pizza.propTypes = {
success: PropTypes.bool.isRequired, success: PropTypes.bool.isRequired,
loading: PropTypes.bool.isRequired,
hasLoaded: PropTypes.bool.isRequired, hasLoaded: PropTypes.bool.isRequired,
event: PropTypes.shape({ event: PropTypes.shape({
start: PropTypes.string.isRequired, start: PropTypes.string.isRequired,
...@@ -239,7 +228,8 @@ Pizza.defaultProps = { ...@@ -239,7 +228,8 @@ Pizza.defaultProps = {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
success: state.pizza.success, success: state.pizza.success,
hasLoaded: state.pizza.success, loading: state.pizza.loading,
hasLoaded: state.pizza.hasLoaded,
event: state.pizza.event, event: state.pizza.event,
order: state.pizza.order, order: state.pizza.order,
pizzaList: state.pizza.pizzaList, pizzaList: state.pizza.pizzaList,
......
...@@ -7,6 +7,7 @@ import LinearGradient from 'react-native-linear-gradient'; ...@@ -7,6 +7,7 @@ import LinearGradient from 'react-native-linear-gradient';
import Moment from 'moment'; import Moment from 'moment';
import LoadingScreen from './LoadingScreen'; import LoadingScreen from './LoadingScreen';
import ErrorScreen from './ErrorScreen';
import { back } from '../actions/navigation'; import { back } from '../actions/navigation';
...@@ -108,11 +109,7 @@ class Profile extends Component { ...@@ -108,11 +109,7 @@ class Profile extends Component {
this.scrollY = new Animated.Value(0); this.scrollY = new Animated.Value(0);
} }
render() { getAppbar = () => {
if (!this.props.hasLoaded) {
return <LoadingScreen />;
}
const headerHeight = this.props.success ? this.scrollY.interpolate({ const headerHeight = this.props.success ? this.scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE], inputRange: [0, HEADER_SCROLL_DISTANCE],
outputRange: [HEADER_MAX_HEIGHT, HEADER_MIN_HEIGHT], outputRange: [HEADER_MAX_HEIGHT, HEADER_MIN_HEIGHT],
...@@ -147,6 +144,52 @@ class Profile extends Component { ...@@ -147,6 +144,52 @@ class Profile extends Component {
extrapolate: 'clamp', extrapolate: 'clamp',
}) : (HEADER_MIN_HEIGHT - 24) / 2; }) : (HEADER_MIN_HEIGHT - 24) / 2;
return (
<Animated.View style={[styles.header, { height: headerHeight }]}>
<Animated.Image
style={[
styles.backgroundImage,
{
opacity: imageOpacity,
transform: [{ translateY: imageTranslate }],
},
]}
source={{ uri: this.props.profile.photo }}
>
<LinearGradient colors={['#55000000', '#000000']} style={styles.overlayGradient} />
</Animated.Image>
<Animated.View style={styles.appBar}>
<TouchableOpacity
onPress={this.props.back}
>
<Icon
name="arrow-back"
style={styles.icon}
size={24}
/>
</TouchableOpacity>
<Animated.Text
style={[styles.title, {
left: textPosLeft,
bottom: textPosBottom,
fontSize: textSize,
}]}
>{this.props.success ? this.props.profile.display_name : 'Profiel'}</Animated.Text>
</Animated.View>
</Animated.View>
);
};
render() {
if (!this.props.hasLoaded) {
return (
<View style={styles.container}>
<LoadingScreen />
{this.getAppbar()}
</View>
);
}
return ( return (
<View style={styles.container}> <View style={styles.container}>
{ {
...@@ -164,44 +207,11 @@ class Profile extends Component { ...@@ -164,44 +207,11 @@ class Profile extends Component {
</ScrollView> </ScrollView>
) : ( ) : (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.errorText}> <ErrorScreen message="Sorry! We couldn't load any data." />
Ophalen profiel mislukt.
</Text>
</View> </View>
) )
} }
<Animated.View style={[styles.header, { height: headerHeight }]}> {this.getAppbar()}
<Animated.Image
style={[
styles.backgroundImage,
{
opacity: imageOpacity,
transform: [{ translateY: imageTranslate }],
},
]}
source={{ uri: this.props.profile.photo }}
>
<LinearGradient colors={['#55000000', '#000000']} style={styles.overlayGradient} />
</Animated.Image>
<Animated.View style={styles.appBar}>
<TouchableOpacity
onPress={this.props.back}
>
<Icon
name="arrow-back"
style={styles.icon}
size={24}
/>
</TouchableOpacity>
<Animated.Text
style={[styles.title, {
left: textPosLeft,
bottom: textPosBottom,
fontSize: textSize,
}]}
>{this.props.success ? this.props.profile.display_name : 'Profiel'}</Animated.Text>
</Animated.View>
</Animated.View>
</View> </View>
); );
} }
......
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { View, Text, SectionList, TouchableOpacity } from 'react-native'; import { View, Text, SectionList, TouchableOpacity, ScrollView, RefreshControl } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Moment from 'moment'; import Moment from 'moment';
import 'moment/locale/nl'; import 'moment/locale/nl';
import EventDetailCard from './EventDetailCard'; import EventDetailCard from './EventDetailCard';
import LoadingScreen from './LoadingScreen';
import ErrorScreen from './ErrorScreen';
import * as welcomeActions from '../actions/welcome'; import * as welcomeActions from '../actions/welcome';
import { navigate } from '../actions/navigation'; import { navigate } from '../actions/navigation';
...@@ -67,17 +70,39 @@ class Welcome extends Component { ...@@ -67,17 +70,39 @@ class Welcome extends Component {
}; };
render() { render() {
if (this.props.eventList.length === 0) { if (this.props.status === 'initial') {
return <LoadingScreen />;
} else if (this.props.status === 'failure') {
return (
<ScrollView
contentContainerStyle={styles.content}
refreshControl={(
<RefreshControl
onRefresh={this.handleRefresh}
refreshing={this.props.loading}
/>
)}
>
<ErrorScreen message="Sorry! We couldn't load any data." />
</ScrollView>
);
} else if (this.props.eventList.length === 0) {
return ( return (
<View> <ScrollView
<Text> contentContainerStyle={styles.content}
No events found! refreshControl={(
</Text> <RefreshControl
</View> onRefresh={this.handleRefresh}
refreshing={this.props.loading}
/>
)}
>
<ErrorScreen message="No events found!" />
</ScrollView>
); );
} }
return ( return (
<View> <View style={styles.content}>
<SectionList <SectionList
style={styles.sectionList} style={styles.sectionList}
renderItem={item => <EventDetailCard event={item.item} />} renderItem={item => <EventDetailCard event={item.item} />}
...@@ -110,11 +135,13 @@ Welcome.propTypes = { ...@@ -110,11 +135,13 @@ Welcome.propTypes = {
})).isRequired, })).isRequired,
refresh: PropTypes.func.isRequired, refresh: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
eventList: state.welcome.eventList, eventList: state.welcome.eventList,
loading: state.welcome.loading, loading: state.welcome.loading,
status: state.welcome.status,
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
......
...@@ -4,6 +4,10 @@ import { TOTAL_BAR_HEIGHT } from './navigator'; ...@@ -4,6 +4,10 @@ import { TOTAL_BAR_HEIGHT } from './navigator';
import { colors } from '../../style'; import { colors } from '../../style';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
content: {
flex: 1,
backgroundColor: colors.background,
},
day: { day: {
flex: 1, flex: 1,
flexDirection: 'row', flexDirection: 'row',
......
import { StyleSheet } from 'react-native';
import { colors } from '../../style';
const styles = StyleSheet.create({
content: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.background,
},
image: {
width: 200,
height: 200,
marginBottom: 30,
},
text: {
fontFamily: 'sans-serif-regular',
fontSize: 18,
color: colors.textColour,
textAlign: 'center',
},
});
export default styles;
...@@ -4,6 +4,10 @@ import { TOTAL_BAR_HEIGHT } from './navigator'; ...@@ -4,6 +4,10 @@ import { TOTAL_BAR_HEIGHT } from './navigator';
import { colors } from '../../style'; import { colors } from '../../style';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
content: {
flex: 1,
backgroundColor: colors.background,
},
sectionList: { sectionList: {
backgroundColor: colors.background, backgroundColor: colors.background,
height: Dimensions.get('window').height - TOTAL_BAR_HEIGHT, height: Dimensions.get('window').height - TOTAL_BAR_HEIGHT,
......
...@@ -3,6 +3,7 @@ import * as calendarActions from '../actions/calendar'; ...@@ -3,6 +3,7 @@ import * as calendarActions from '../actions/calendar';
const initialState = { const initialState = {
eventList: [], eventList: [],
loading: true, loading: true,
status: 'initial',
}