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

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 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 Moment from 'moment';
import 'moment/locale/nl';
import * as calendarActions from '../actions/calendar';
import EventCard from './EventCard';
import LoadingScreen from './LoadingScreen';
import ErrorScreen from './ErrorScreen';
import styles from './style/calendar';
......@@ -123,17 +125,39 @@ class Calendar extends Component {
};
render() {
if (this.props.eventList.length === 0 && !this.props.loading) {
if (this.props.status === 'initial') {
return <LoadingScreen />;
} else if (this.props.status === 'failure') {
return (
<View>
<Text>
No events found!
</Text>
</View>
<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 (
<ScrollView
contentContainerStyle={styles.content}
refreshControl={(
<RefreshControl
onRefresh={this.handleRefresh}
refreshing={this.props.loading}
/>
)}
>
<ErrorScreen message="No events found!" />
</ScrollView>
);
}
return (
<View>
<View style={styles.content}>
<SectionList
style={styles.sectionList}
renderItem={renderItem}
......@@ -166,12 +190,14 @@ Calendar.propTypes = {
url: PropTypes.string,
})).isRequired,
loading: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
refresh: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
eventList: state.calendar.eventList,
loading: state.calendar.loading,
status: state.calendar.status,
});
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';
import styles from './style/event';
import MemberView from './MemberView';
import LoadingScreen from './LoadingScreen';
import ErrorScreen from './ErrorScreen';
import { colors } from '../style';
class Event extends Component {
......@@ -252,9 +253,7 @@ class Event extends Component {
);
}
return (
<ScrollView backgroundColor={colors.background} contentContainerStyle={styles.eventView}>
<Text>Kon het evenement niet laden...</Text>
</ScrollView>
<ErrorScreen message="Could not load the event..." />
);
}
}
......
......@@ -6,19 +6,13 @@ import Icon from 'react-native-vector-icons/MaterialIcons';
import Moment from 'moment';
import 'moment/locale/nl';
import LoadingScreen from './LoadingScreen';
import ErrorScreen from './ErrorScreen';
import { retrievePizzaInfo, cancelOrder, orderPizza } from '../actions/pizza';
import styles from './style/pizza';
import { colors } from '../style';
class Pizza extends Component {
constructor(props) {
super(props);
this.state = {
refreshing: false,
};
}
getProductFromList = (pk, pizzaList) => {
for (let i = 0; i < pizzaList.length; i += 1) {
if (pizzaList[i].pk === pk) {
......@@ -122,9 +116,7 @@ class Pizza extends Component {
);
handleRefresh = () => {
this.setState({ refreshing: true });
this.props.retrievePizzaInfo(this.props.token);
this.setState({ refreshing: false });
};
render() {
......@@ -135,16 +127,13 @@ class Pizza extends Component {
<ScrollView
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
refreshing={this.props.loading}
onRefresh={this.handleRefresh}
/>
}
contentContainerStyle={styles.content}
>
<View style={styles.content}>
<Text
style={styles.title}
>Something went wrong while retrieving pizza info.</Text>
</View>
<ErrorScreen message="Sorry! We couldn't load any data." />
</ScrollView>
);
} else if (!this.props.event) {
......@@ -152,16 +141,15 @@ class Pizza extends Component {
<ScrollView
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
refreshing={this.props.loading}
onRefresh={this.handleRefresh}
/>
}
contentContainerStyle={styles.content}
>
<View style={styles.content}>
<Text
style={styles.title}
>There is currently no event for which you can order food.</Text>
</View>
<Text
style={styles.title}
>There is currently no event for which you can order food.</Text>
</ScrollView>
);
}
......@@ -186,7 +174,7 @@ class Pizza extends Component {
<ScrollView
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
refreshing={this.props.loading}
onRefresh={this.handleRefresh}
/>
}
......@@ -205,6 +193,7 @@ class Pizza extends Component {
Pizza.propTypes = {
success: PropTypes.bool.isRequired,
loading: PropTypes.bool.isRequired,
hasLoaded: PropTypes.bool.isRequired,
event: PropTypes.shape({
start: PropTypes.string.isRequired,
......@@ -239,7 +228,8 @@ Pizza.defaultProps = {
const mapStateToProps = state => ({
success: state.pizza.success,
hasLoaded: state.pizza.success,
loading: state.pizza.loading,
hasLoaded: state.pizza.hasLoaded,
event: state.pizza.event,
order: state.pizza.order,
pizzaList: state.pizza.pizzaList,
......
......@@ -7,6 +7,7 @@ import LinearGradient from 'react-native-linear-gradient';
import Moment from 'moment';
import LoadingScreen from './LoadingScreen';
import ErrorScreen from './ErrorScreen';
import { back } from '../actions/navigation';
......@@ -108,11 +109,7 @@ class Profile extends Component {
this.scrollY = new Animated.Value(0);
}
render() {
if (!this.props.hasLoaded) {
return <LoadingScreen />;
}
getAppbar = () => {
const headerHeight = this.props.success ? this.scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE],
outputRange: [HEADER_MAX_HEIGHT, HEADER_MIN_HEIGHT],
......@@ -147,6 +144,52 @@ class Profile extends Component {
extrapolate: 'clamp',
}) : (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 (
<View style={styles.container}>
{
......@@ -164,44 +207,11 @@ class Profile extends Component {
</ScrollView>
) : (
<View style={styles.container}>
<Text style={styles.errorText}>
Ophalen profiel mislukt.
</Text>
<ErrorScreen message="Sorry! We couldn't load any data." />
</View>
)
}
<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>
{this.getAppbar()}
</View>
);
}
......
import React, { Component } from 'react';
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 Moment from 'moment';
import 'moment/locale/nl';
import EventDetailCard from './EventDetailCard';
import LoadingScreen from './LoadingScreen';
import ErrorScreen from './ErrorScreen';
import * as welcomeActions from '../actions/welcome';
import { navigate } from '../actions/navigation';
......@@ -67,17 +70,39 @@ class Welcome extends Component {
};
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 (
<View>
<Text>
No events found!
</Text>
</View>
<ScrollView
contentContainerStyle={styles.content}
refreshControl={(
<RefreshControl
onRefresh={this.handleRefresh}
refreshing={this.props.loading}
/>
)}
>
<ErrorScreen message="No events found!" />
</ScrollView>
);
}
return (
<View>
<View style={styles.content}>
<SectionList
style={styles.sectionList}
renderItem={item => <EventDetailCard event={item.item} />}
......@@ -110,11 +135,13 @@ Welcome.propTypes = {
})).isRequired,
refresh: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
};
const mapStateToProps = state => ({
eventList: state.welcome.eventList,
loading: state.welcome.loading,
status: state.welcome.status,
});
const mapDispatchToProps = dispatch => ({
......
......@@ -4,6 +4,10 @@ import { TOTAL_BAR_HEIGHT } from './navigator';
import { colors } from '../../style';
const styles = StyleSheet.create({
content: {
flex: 1,
backgroundColor: colors.background,
},
day: {
flex: 1,
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';
import { colors } from '../../style';
const styles = StyleSheet.create({
content: {
flex: 1,
backgroundColor: colors.background,
},
sectionList: {
backgroundColor: colors.background,
height: Dimensions.get('window').height - TOTAL_BAR_HEIGHT,
......
......@@ -3,6 +3,7 @@ import * as calendarActions from '../actions/calendar';
const initialState = {
eventList: [],
loading: true,
status: 'initial',
};
export default function calendar(state = initialState, action = {}) {
......@@ -11,9 +12,14 @@ export default function calendar(state = initialState, action = {}) {
return {
eventList: action.payload.eventList,
loading: false,
status: 'success',
};
case calendarActions.FAILURE:
return { ...state, loading: false };
return {
...state,
loading: false,
status: 'failure',
};
case calendarActions.REFRESH:
return { ...state, loading: true };
default:
......
......@@ -2,6 +2,7 @@ import * as pizzaActions from '../actions/pizza';
const initialState = {
success: false,
loading: false,
hasLoaded: false,
event: null,
order: null,
......@@ -13,6 +14,7 @@ export default function pizza(state = initialState, action = {}) {
case pizzaActions.SUCCESS:
return {
success: true,
loading: false,
hasLoaded: true,
event: action.payload.event,
order: action.payload.order,
......@@ -22,12 +24,13 @@ export default function pizza(state = initialState, action = {}) {
return {
...state,
success: false,
loading: false,
hasLoaded: true,
};
case pizzaActions.FETCHING:
return {
...state,
hasLoaded: false,
loading: true,
};
case pizzaActions.CANCEL_SUCCESS:
return {
......
import * as profileActions from '../actions/profile';
import { defaultProfileImage } from '../url';
const initialState = {
profile: {
pk: -1,
display_name: '',
photo: '',
photo: defaultProfileImage,
profile_description: '',
birthday: '',
starting_year: -1,
......
......@@ -3,6 +3,7 @@ import * as welcomeActions from '../actions/welcome';
const initialState = {
eventList: [],
loading: true,
status: 'initial',
};
export default function welcome(state = initialState, action = {}) {
......@@ -11,9 +12,14 @@ export default function welcome(state = initialState, action = {}) {
return {
eventList: action.payload.eventList,
loading: false,
status: 'success',
};
case welcomeActions.FAILURE:
return { ...state, loading: false };
return {
...state,
loading: false,
status: 'failure',
};
case welcomeActions.REFRESH:
return { ...state, loading: true };
default:
......
......@@ -6,6 +6,7 @@ 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 defaultProfileImage = `${server}/static/members/images/default-avatar.jpg`;
export const tokenSelector = state => state.session.token;
export class ServerError extends Error {
......
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