Commit becfde33 authored by Gijs Hendriksen's avatar Gijs Hendriksen
Browse files

Improve Registration screen

parent cace4483
......@@ -2,6 +2,9 @@ export const REGISTER = 'REGISTRATION_REGISTER';
export const UPDATE = 'REGISTRATION_UPDATE';
export const CANCEL = 'REGISTRATION_CANCEL';
export const FIELDS = 'REGISTRATION_FIELDS';
export const LOADING = 'REGISTRATION_FETCHING';
export const FAILURE = 'REGISTRATION_FAILURE';
export const SUCCESS = 'REGISTRATION_SUCCESS';
export const SHOW_FIELDS = 'REGISTRATION_SHOW_FIELDS';
export function register(event) {
......@@ -20,6 +23,18 @@ export function retrieveFields(registration) {
return { type: FIELDS, payload: { registration } };
}
export function loading() {
return { type: LOADING };
}
export function failure() {
return { type: FAILURE };
}
export function success() {
return { type: SUCCESS };
}
export function showFields(registration, fields) {
return { type: SHOW_FIELDS, payload: { registration, fields } };
}
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Image, ScrollView, Text, View, RefreshControl, Button } from 'react-native';
import { Alert, Image, ScrollView, Text, View, RefreshControl, Button } from 'react-native';
import { connect } from 'react-redux';
import Moment from 'moment';
import 'moment/locale/nl';
......@@ -15,6 +15,15 @@ import * as eventActions from '../actions/event';
import * as registrationActions from '../actions/registration';
class Event extends Component {
cancelPrompt = pk => Alert.alert(
'Cancel registration?',
'Are you sure you want to cancel your registration?',
[
{ text: 'Dismiss' },
{ text: 'Cancel registration', onPress: () => this.props.cancel(pk) },
],
);
eventDesc = (data) => {
const startDate = Moment(data.start).format('D MMM YYYY, HH:mm');
const endDate = Moment(data.end).format('D MMM YYYY, HH:mm');
......@@ -145,7 +154,6 @@ class Event extends Component {
return (<View />);
};
// eslint-disable-next-line arrow-body-style
eventActions = (event) => {
const nowDate = new Date();
const startRegDate = new Date(event.registration_start);
......@@ -185,7 +193,7 @@ class Event extends Component {
<Button
color={colors.magenta}
title="Afmelden"
onPress={() => this.props.cancel(event.user_registration.pk)}
onPress={() => this.cancelPrompt(event.user_registration.pk)}
/>
</View>
</View>
......@@ -193,7 +201,7 @@ class Event extends Component {
}
return (
<View style={styles.registrationActions}>
<Button color={colors.magenta} title="Afmelden" onPress={() => this.props.cancel(event.user_registration.pk)} />
<Button color={colors.magenta} title="Afmelden" onPress={() => this.cancelPrompt(event.user_registration.pk)} />
</View>
);
}
......
......@@ -12,7 +12,6 @@ const LoadingScreen = () => (
<ActivityIndicator
animating
color={colors.magenta}
style={styles.indicator}
size="large"
/>
</View>
......
......@@ -236,7 +236,7 @@ class Profile extends Component {
return (
<View style={styles.container}>
<StatusBar
backgroundColor={colors.statusBar}
backgroundColor={colors.semiTransparent}
barStyle="light-content"
translucent
animated
......
import React, { Component } from 'react';
import { View, ScrollView, Switch, TextInput, Text, Button } from 'react-native';
import { ActivityIndicator, Modal, View, ScrollView, Switch, TextInput, Text, Button } from 'react-native';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import styles from './style/registration';
import { colors } from '../style';
import ErrorScreen from './ErrorScreen';
import * as registrationActions from '../actions/registration';
class Registration extends Component {
......@@ -26,23 +28,31 @@ class Registration extends Component {
}
}
isFieldValid = (key) => {
getFieldValidity = (key) => {
const field = this.props.fields[key];
const value = this.state[key];
if (field.required) {
if (field.type === 'integer' && (value === '' || value === null || !value.match(/^\d+$/))) {
return false;
if (field.type === 'integer' && (value === '' || value === null || !value.match(/^-?\d+$/))) {
return {
isValid: false,
reason: 'This field is required and must be an integer.',
};
} else if (field.type === 'text' && (value === '' || value === null)) {
return false;
return {
isValid: false,
reason: 'This field is required.',
};
}
}
return true;
return {
isValid: true,
};
};
isFormValid = () => {
const keys = Object.keys(this.props.fields);
for (let i = 0; i < keys.length; i += 1) {
if (!this.isFieldValid(keys[i])) {
if (!this.getFieldValidity(keys[i]).isValid) {
return false;
}
}
......@@ -56,12 +66,33 @@ class Registration extends Component {
};
render() {
if (this.props.status === 'failure') {
return <ErrorScreen message="Sorry! We couldn't load any data." />;
}
const keys = Object.keys(this.props.fields);
return (
<ScrollView style={styles.content}>
<ScrollView
style={styles.content}
keyboardShouldPersistTaps="handled"
>
<Modal
visible={this.props.status === 'loading'}
transparent
onRequestClose={() => ({})}
>
<View style={styles.overlay}>
<ActivityIndicator
animating
color={colors.magenta}
size="large"
/>
</View>
</Modal>
{keys.map((key) => {
const field = this.props.fields[key];
const validity = this.getFieldValidity(key);
if (field.type === 'boolean') {
return (
<View key={key} style={styles.booleanContainer}>
......@@ -83,10 +114,11 @@ class Registration extends Component {
onChangeText={value => this.updateField(key, value)}
keyboardType={field.type === 'integer' ? 'numeric' : 'default'}
style={styles.field}
underlineColorAndroid={this.isFieldValid(key) ? colors.lightGray :
underlineColorAndroid={validity.isValid ? colors.lightGray :
colors.lightRed}
placeholder={field.description}
/>
{validity.isValid || <Text style={styles.invalid}>{validity.reason}</Text>}
</View>
);
}
......@@ -108,12 +140,14 @@ class Registration extends Component {
Registration.propTypes = {
registration: PropTypes.number.isRequired,
fields: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
status: PropTypes.string.isRequired,
update: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
registration: state.registration.registration,
fields: state.registration.fields,
status: state.registration.status,
});
const mapDispatchToProps = dispatch => ({
......
......@@ -21,6 +21,8 @@ const sceneToTitle = (scene) => {
return 'Pizza';
case 'profile':
return 'Profiel';
case 'registration':
return 'Registratie';
default:
return 'ThaliApp';
}
......@@ -30,7 +32,7 @@ const StandardHeader = props => (
<View>
<View style={styles.statusBar}>
<StatusBar
backgroundColor={colors.statusBar}
backgroundColor={colors.semiTransparent}
translucent
animated
barStyle="light-content"
......
......@@ -81,7 +81,7 @@ const ReduxNavigator = (props) => {
>
<View style={styles.statusBar}>
<StatusBar
backgroundColor={colors.statusBar}
backgroundColor={colors.semiTransparent}
barStyle="light-content"
translucent
animated
......
import { StyleSheet } from 'react-native';
import { APPBAR_HEIGHT } from './standardHeader';
import { colors } from '../../style';
const styles = StyleSheet.create({
......@@ -7,6 +8,13 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: colors.background,
},
overlay: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginTop: APPBAR_HEIGHT,
backgroundColor: colors.semiTransparent,
},
fieldContainer: {
borderTopWidth: 1,
borderTopColor: colors.dividerGrey,
......@@ -25,6 +33,12 @@ const styles = StyleSheet.create({
fontSize: 14,
color: colors.textColour,
},
invalid: {
fontFamily: 'sans-serif-medium',
fontSize: 12,
color: colors.lightRed,
marginLeft: 4,
},
buttonView: {
margin: 16,
},
......
......@@ -3,6 +3,7 @@ import * as registrationActions from '../actions/registration';
const initialState = {
registration: 0,
fields: {},
status: 'success',
};
......@@ -12,6 +13,25 @@ export default function navigate(state = initialState, action = {}) {
return {
registration: action.payload.registration,
fields: action.payload.fields,
status: 'success',
};
}
case registrationActions.SUCCESS: {
return {
...state,
status: 'success',
};
}
case registrationActions.LOADING: {
return {
...state,
status: 'loading',
};
}
case registrationActions.FAILURE: {
return {
...state,
status: 'failure',
};
}
default:
......
import { delay } from 'redux-saga';
import { call, put, select, takeEvery } from 'redux-saga/effects';
import Snackbar from 'react-native-snackbar';
import { apiRequest, tokenSelector } from '../url';
......@@ -32,6 +34,7 @@ const register = function* register(action) {
if (registration.fields) {
yield put(registrationActions.retrieveFields(registration.pk));
}
Snackbar.show({ title: 'Registration successful!' });
} catch (error) {
yield put(eventActions.failure());
}
......@@ -41,8 +44,7 @@ const update = function* update(action) {
const { registration, fields } = action.payload;
const token = yield select(tokenSelector);
yield put(eventActions.fetching());
yield put(navigationActions.back());
yield put(registrationActions.loading());
const body = {};
......@@ -62,9 +64,12 @@ const update = function* update(action) {
try {
yield call(apiRequest, `registrations/${registration}`, data);
yield put(eventActions.done());
yield put(navigationActions.back());
yield put(registrationActions.success());
yield delay(50);
Snackbar.show({ title: 'Successfully updated registration' });
} catch (error) {
yield put(eventActions.failure());
yield put(registrationActions.failure());
}
};
......@@ -85,6 +90,7 @@ const cancel = function* cancel(action) {
try {
yield call(apiRequest, `registrations/${registration}`, data);
Snackbar.show({ title: 'Successfully cancelled registration' });
} catch (error) {
// Swallow error for now
}
......
......@@ -11,7 +11,7 @@ export const colors = {
textColour: '#313131',
darkGrey: '#373737',
dividerGrey: 'rgba(0, 0, 0, 0.12)',
statusBar: 'rgba(0, 0, 0, 0.20)',
semiTransparent: 'rgba(0, 0, 0, 0.20)',
transparent: 'transparent',
lightGreen: '#8fcc74',
darkGreen: '#81b968',
......
Supports Markdown
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