Archived
🎨 Add prettier and eslint, then format
This commit is contained in:
1 parent
ab77f9dea9
commit
148ea8e7c2
33 files changed
+2709
-2118
No files matched your search
+22
-23
@@ -1,31 +1,30 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Provider} from 'react-redux';
|
||||
import {PersistGate} from 'redux-persist/es/integration/react'
|
||||
import React, { Component } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { PersistGate } from 'redux-persist/es/integration/react';
|
||||
import Config from 'react-native-config';
|
||||
import {ApolloClient} from 'apollo-client';
|
||||
import {HttpLink} from 'apollo-link-http';
|
||||
import {InMemoryCache} from 'apollo-cache-inmemory';
|
||||
import {ApolloProvider} from "react-apollo";
|
||||
|
||||
import Dusken from "./Dusken";
|
||||
import {store, persistor} from './reduxStore';
|
||||
import { ApolloClient } from 'apollo-client';
|
||||
import { HttpLink } from 'apollo-link-http';
|
||||
import { InMemoryCache } from 'apollo-cache-inmemory';
|
||||
import { ApolloProvider } from 'react-apollo';
|
||||
|
||||
import Dusken from './Dusken';
|
||||
import { store, persistor } from './reduxStore';
|
||||
|
||||
const apolloClient = new ApolloClient({
|
||||
link: new HttpLink({uri: `${Config.DUSKEN_API_URL}/api/graphql/`}),
|
||||
cache: new InMemoryCache()
|
||||
link: new HttpLink({ uri: `${Config.DUSKEN_API_URL}/api/graphql/` }),
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
|
||||
export default class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<Dusken />
|
||||
</ApolloProvider>
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<Dusken />
|
||||
</ApolloProvider>
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
+12
-11
@@ -1,15 +1,16 @@
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from "react-redux";
|
||||
import {Root} from 'native-base';
|
||||
import DuskenNavigation from "./navigation";
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Root } from 'native-base';
|
||||
import DuskenNavigation from './navigation';
|
||||
|
||||
class DuskenContainer extends Component {
|
||||
render() {
|
||||
return (<Root>
|
||||
<DuskenNavigation/>
|
||||
</Root>)
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Root>
|
||||
<DuskenNavigation />
|
||||
</Root>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(DuskenContainer);
|
||||
export default connect()(DuskenContainer);
|
||||
+75
-62
@@ -13,103 +13,116 @@ const MEMBERSHIP_CHARGE_SUCCESS = 'MEMBERSHIP_CHARGE_SUCCESS';
|
||||
const MEMBERSHIP_CHARGE_FAILURE = 'MEMBERSHIP_CHARGE_FAILURE';
|
||||
|
||||
function loginRequest() {
|
||||
return {
|
||||
type: LOGIN_REQUEST
|
||||
}
|
||||
return {
|
||||
type: LOGIN_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
function loginSuccess(username, token) {
|
||||
return {
|
||||
type: LOGIN_SUCCESS,
|
||||
username,
|
||||
token
|
||||
}
|
||||
return {
|
||||
type: LOGIN_SUCCESS,
|
||||
username,
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
function loginFailure(loginError) {
|
||||
return {
|
||||
type: LOGIN_FAILURE,
|
||||
loginError
|
||||
}
|
||||
return {
|
||||
type: LOGIN_FAILURE,
|
||||
loginError,
|
||||
};
|
||||
}
|
||||
|
||||
function logout() {
|
||||
return {
|
||||
type: LOGOUT
|
||||
}
|
||||
return {
|
||||
type: LOGOUT,
|
||||
};
|
||||
}
|
||||
|
||||
function userDataRequest() {
|
||||
return {
|
||||
type: USER_DATA_REQUEST
|
||||
}
|
||||
return {
|
||||
type: USER_DATA_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
function userDataSuccess(data) {
|
||||
return {
|
||||
type: USER_DATA_SUCCESS,
|
||||
data
|
||||
}
|
||||
return {
|
||||
type: USER_DATA_SUCCESS,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function userDataFailure(userError) {
|
||||
return {
|
||||
type: USER_DATA_FAILURE,
|
||||
userError
|
||||
}
|
||||
return {
|
||||
type: USER_DATA_FAILURE,
|
||||
userError,
|
||||
};
|
||||
}
|
||||
|
||||
function registerUserRequest() {
|
||||
return {
|
||||
type: REGISTER_USER_REQUEST
|
||||
}
|
||||
return {
|
||||
type: REGISTER_USER_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
function registerUserSuccess(data) {
|
||||
return {
|
||||
type: REGISTER_USER_SUCCESS,
|
||||
data
|
||||
}
|
||||
return {
|
||||
type: REGISTER_USER_SUCCESS,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function registerUserFailure(registerError) {
|
||||
return {
|
||||
type: REGISTER_USER_FAILURE,
|
||||
registerError
|
||||
}
|
||||
return {
|
||||
type: REGISTER_USER_FAILURE,
|
||||
registerError,
|
||||
};
|
||||
}
|
||||
function membershipChargeRequest() {
|
||||
return {
|
||||
type: MEMBERSHIP_CHARGE_REQUEST
|
||||
}
|
||||
return {
|
||||
type: MEMBERSHIP_CHARGE_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
function membershipChargeSuccess(data) {
|
||||
return {
|
||||
type: MEMBERSHIP_CHARGE_SUCCESS,
|
||||
data
|
||||
}
|
||||
return {
|
||||
type: MEMBERSHIP_CHARGE_SUCCESS,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function membershipChargeFailure(chargeError) {
|
||||
return {
|
||||
type: MEMBERSHIP_CHARGE_FAILURE,
|
||||
chargeError
|
||||
}
|
||||
return {
|
||||
type: MEMBERSHIP_CHARGE_FAILURE,
|
||||
chargeError,
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
LOGIN_REQUEST, loginRequest,
|
||||
LOGIN_SUCCESS, loginSuccess,
|
||||
LOGIN_FAILURE, loginFailure,
|
||||
LOGOUT, logout,
|
||||
USER_DATA_REQUEST, userDataRequest,
|
||||
USER_DATA_SUCCESS, userDataSuccess,
|
||||
USER_DATA_FAILURE, userDataFailure,
|
||||
REGISTER_USER_REQUEST, registerUserRequest,
|
||||
REGISTER_USER_SUCCESS, registerUserSuccess,
|
||||
REGISTER_USER_FAILURE, registerUserFailure,
|
||||
MEMBERSHIP_CHARGE_REQUEST, membershipChargeRequest,
|
||||
MEMBERSHIP_CHARGE_SUCCESS, membershipChargeSuccess,
|
||||
MEMBERSHIP_CHARGE_FAILURE, membershipChargeFailure,
|
||||
};
|
||||
LOGIN_REQUEST,
|
||||
loginRequest,
|
||||
LOGIN_SUCCESS,
|
||||
loginSuccess,
|
||||
LOGIN_FAILURE,
|
||||
loginFailure,
|
||||
LOGOUT,
|
||||
logout,
|
||||
USER_DATA_REQUEST,
|
||||
userDataRequest,
|
||||
USER_DATA_SUCCESS,
|
||||
userDataSuccess,
|
||||
USER_DATA_FAILURE,
|
||||
userDataFailure,
|
||||
REGISTER_USER_REQUEST,
|
||||
registerUserRequest,
|
||||
REGISTER_USER_SUCCESS,
|
||||
registerUserSuccess,
|
||||
REGISTER_USER_FAILURE,
|
||||
registerUserFailure,
|
||||
MEMBERSHIP_CHARGE_REQUEST,
|
||||
membershipChargeRequest,
|
||||
MEMBERSHIP_CHARGE_SUCCESS,
|
||||
membershipChargeSuccess,
|
||||
MEMBERSHIP_CHARGE_FAILURE,
|
||||
membershipChargeFailure,
|
||||
};
|
||||
+135
-129
@@ -1,148 +1,154 @@
|
||||
import Config from 'react-native-config';
|
||||
|
||||
import {
|
||||
loginFailure, loginRequest, loginSuccess,
|
||||
registerUserFailure, registerUserRequest, registerUserSuccess,
|
||||
userDataFailure, userDataRequest, userDataSuccess,
|
||||
membershipChargeFailure, membershipChargeRequest, membershipChargeSuccess,
|
||||
} from "./actions";
|
||||
import { fetchWithTimeout } from "./utils";
|
||||
loginFailure,
|
||||
loginRequest,
|
||||
loginSuccess,
|
||||
registerUserFailure,
|
||||
registerUserRequest,
|
||||
registerUserSuccess,
|
||||
userDataFailure,
|
||||
userDataRequest,
|
||||
userDataSuccess,
|
||||
membershipChargeFailure,
|
||||
membershipChargeRequest,
|
||||
membershipChargeSuccess,
|
||||
} from './actions';
|
||||
import { fetchWithTimeout } from './utils';
|
||||
|
||||
export function requestLogin(username, password) {
|
||||
return (dispatch) => {
|
||||
// We are now logging in
|
||||
dispatch(loginRequest());
|
||||
return (dispatch) => {
|
||||
// We are now logging in
|
||||
dispatch(loginRequest());
|
||||
|
||||
return fetchWithTimeout(`${Config.DUSKEN_API_URL}/auth/obtain-token/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
'username': username,
|
||||
'password': password
|
||||
})
|
||||
})
|
||||
.then(response => response.json().then(json => ({ json, response })))
|
||||
.then(({json, response}) => {
|
||||
if (response.ok === false) {
|
||||
return Promise.reject(json)
|
||||
}
|
||||
return json
|
||||
})
|
||||
.then(
|
||||
data => {
|
||||
dispatch(loginSuccess(username, data.token))
|
||||
},
|
||||
data => {
|
||||
console.log(data);
|
||||
dispatch(loginFailure(data || {'non_field_errors': ['Kunne ikke logge inn, prøv igjen']}))
|
||||
}
|
||||
)
|
||||
}
|
||||
return fetchWithTimeout(`${Config.DUSKEN_API_URL}/auth/obtain-token/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json().then((json) => ({ json, response })))
|
||||
.then(({ json, response }) => {
|
||||
if (response.ok === false) {
|
||||
return Promise.reject(json);
|
||||
}
|
||||
return json;
|
||||
})
|
||||
.then(
|
||||
(data) => {
|
||||
dispatch(loginSuccess(username, data.token));
|
||||
},
|
||||
(data) => {
|
||||
console.log(data);
|
||||
dispatch(loginFailure(data || { non_field_errors: ['Kunne ikke logge inn, prøv igjen'] }));
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function requestUserData(auth_token) {
|
||||
return (dispatch) => {
|
||||
// We are now fetching user data
|
||||
dispatch(userDataRequest());
|
||||
return (dispatch) => {
|
||||
// We are now fetching user data
|
||||
dispatch(userDataRequest());
|
||||
|
||||
return fetchWithTimeout(`${Config.DUSKEN_API_URL}/api/me/`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Token ${auth_token}`
|
||||
}
|
||||
})
|
||||
.then(response => response.json().then(json => ({ json, response })))
|
||||
.then(({json, response}) => {
|
||||
if (response.ok === false) {
|
||||
return Promise.reject(json)
|
||||
}
|
||||
return json
|
||||
})
|
||||
.then(
|
||||
data => {
|
||||
dispatch(userDataSuccess(data))
|
||||
},
|
||||
data => {
|
||||
dispatch(userDataFailure(data || {'non_field_errors': ['Kunne ikke hente brukerdata']}))
|
||||
}
|
||||
)
|
||||
}
|
||||
return fetchWithTimeout(`${Config.DUSKEN_API_URL}/api/me/`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Token ${auth_token}`,
|
||||
},
|
||||
})
|
||||
.then((response) => response.json().then((json) => ({ json, response })))
|
||||
.then(({ json, response }) => {
|
||||
if (response.ok === false) {
|
||||
return Promise.reject(json);
|
||||
}
|
||||
return json;
|
||||
})
|
||||
.then(
|
||||
(data) => {
|
||||
dispatch(userDataSuccess(data));
|
||||
},
|
||||
(data) => {
|
||||
dispatch(userDataFailure(data || { non_field_errors: ['Kunne ikke hente brukerdata'] }));
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function requestRegisterUser(firstName, lastName, email, phoneNumber, password) {
|
||||
return (dispatch) => {
|
||||
dispatch(registerUserRequest());
|
||||
return (dispatch) => {
|
||||
dispatch(registerUserRequest());
|
||||
|
||||
return fetchWithTimeout(`${Config.DUSKEN_API_URL}/api/user/register/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
'first_name': firstName,
|
||||
'last_name': lastName,
|
||||
'email': email,
|
||||
'phone_number': phoneNumber,
|
||||
'password': password
|
||||
})
|
||||
})
|
||||
.then(response => response.json().then(json => ({ json, response })))
|
||||
.then(({json, response}) => {
|
||||
if (response.ok === false) {
|
||||
return Promise.reject(json)
|
||||
}
|
||||
return json
|
||||
})
|
||||
.then(
|
||||
data => {
|
||||
dispatch(registerUserSuccess(data))
|
||||
},
|
||||
data => {
|
||||
dispatch(registerUserFailure(data || {'non_field_errors': ['Kunne ikke registrere bruker, prøv igjen']}))
|
||||
}
|
||||
)
|
||||
}
|
||||
return fetchWithTimeout(`${Config.DUSKEN_API_URL}/api/user/register/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
email,
|
||||
phone_number: phoneNumber,
|
||||
password,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json().then((json) => ({ json, response })))
|
||||
.then(({ json, response }) => {
|
||||
if (response.ok === false) {
|
||||
return Promise.reject(json);
|
||||
}
|
||||
return json;
|
||||
})
|
||||
.then(
|
||||
(data) => {
|
||||
dispatch(registerUserSuccess(data));
|
||||
},
|
||||
(data) => {
|
||||
dispatch(registerUserFailure(data || { non_field_errors: ['Kunne ikke registrere bruker, prøv igjen'] }));
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function requestMembershipCharge(auth_token, stripe_token, membership_type) {
|
||||
return (dispatch) => {
|
||||
dispatch(membershipChargeRequest());
|
||||
return (dispatch) => {
|
||||
dispatch(membershipChargeRequest());
|
||||
|
||||
return fetchWithTimeout(`${Config.DUSKEN_API_URL}/api/membership/charge/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Token ${auth_token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
'stripe_token': stripe_token,
|
||||
'membership_type': membership_type,
|
||||
'payment_method': 'app',
|
||||
})
|
||||
})
|
||||
.then(response => response.json().then(json => ({ json, response })))
|
||||
.then(({json, response}) => {
|
||||
if (response.ok === false) {
|
||||
return Promise.reject(json)
|
||||
}
|
||||
return json
|
||||
})
|
||||
.then(
|
||||
data => {
|
||||
dispatch(membershipChargeSuccess(data))
|
||||
},
|
||||
data => {
|
||||
dispatch(membershipChargeFailure(data['detail'] || 'Kunne ikke belaste bankkortet, prøv igjen'))
|
||||
}
|
||||
)
|
||||
}
|
||||
return fetchWithTimeout(`${Config.DUSKEN_API_URL}/api/membership/charge/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Token ${auth_token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
stripe_token,
|
||||
membership_type,
|
||||
payment_method: 'app',
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json().then((json) => ({ json, response })))
|
||||
.then(({ json, response }) => {
|
||||
if (response.ok === false) {
|
||||
return Promise.reject(json);
|
||||
}
|
||||
return json;
|
||||
})
|
||||
.then(
|
||||
(data) => {
|
||||
dispatch(membershipChargeSuccess(data));
|
||||
},
|
||||
(data) => {
|
||||
dispatch(membershipChargeFailure(data.detail || 'Kunne ikke belaste bankkortet, prøv igjen'));
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+172
-137
@@ -1,156 +1,191 @@
|
||||
import React, { Component } from 'react';
|
||||
import {Text, ScrollView, View, Platform, StyleSheet, Image, Linking, TouchableOpacity} from "react-native";
|
||||
import { Text, ScrollView, View, Platform, StyleSheet, Image, Linking, TouchableOpacity } from 'react-native';
|
||||
// import HTMLView from 'react-native-htmlview';
|
||||
|
||||
import {Button, Text as NBText, Icon} from 'native-base';
|
||||
import theme from "../../theme";
|
||||
import { Button, Text as NBText, Icon } from 'native-base';
|
||||
import theme from '../../theme';
|
||||
|
||||
const CHATEAU_NEUF_ADDRESS = 'Det Norske Studentersamfund, Slemdalsveien 15, 0369 Oslo';
|
||||
|
||||
export default class About extends Component {
|
||||
/* FIXME: Values here are pretty hardcoded. Should fetch config from a JSON-file or API */
|
||||
/* FIXME: Values here are pretty hardcoded. Should fetch config from a JSON-file or API */
|
||||
|
||||
openFacebook() {
|
||||
/* Ref: https://stackoverflow.com/questions/4810803/open-facebook-page-from-android-app */
|
||||
const url = Platform.select({
|
||||
ios: 'fb://page/?id=23664032291',
|
||||
android: 'fb://page/23664032291/'
|
||||
});
|
||||
Linking.canOpenURL(url).then(supported => {
|
||||
if (!supported) {
|
||||
return Linking.openURL('https://www.facebook.com/studentersamfundet/');
|
||||
} else {
|
||||
return Linking.openURL(url);
|
||||
}
|
||||
}).catch(err => console.error('An error occurred', err));
|
||||
}
|
||||
openInstagram() {
|
||||
/* Ref: */
|
||||
const url = Platform.select({
|
||||
ios: 'instagram://user?username=studentersamfundet',
|
||||
android: 'http://instagram.com/_u/studentersamfundet'
|
||||
});
|
||||
openFacebook() {
|
||||
/* Ref: https://stackoverflow.com/questions/4810803/open-facebook-page-from-android-app */
|
||||
const url = Platform.select({
|
||||
ios: 'fb://page/?id=23664032291',
|
||||
android: 'fb://page/23664032291/',
|
||||
});
|
||||
Linking.canOpenURL(url)
|
||||
.then((supported) => {
|
||||
if (!supported) {
|
||||
return Linking.openURL('https://www.facebook.com/studentersamfundet/');
|
||||
}
|
||||
return Linking.openURL(url);
|
||||
})
|
||||
.catch((err) => console.error('An error occurred', err));
|
||||
}
|
||||
|
||||
Linking.canOpenURL(url).then(supported => {
|
||||
if (!supported) {
|
||||
return Linking.openURL('https://www.instagram.com/studentersamfundet/');
|
||||
} else {
|
||||
return Linking.openURL(url);
|
||||
}
|
||||
}).catch(err => console.error('An error occurred', err));
|
||||
}
|
||||
openInstagram() {
|
||||
/* Ref: */
|
||||
const url = Platform.select({
|
||||
ios: 'instagram://user?username=studentersamfundet',
|
||||
android: 'http://instagram.com/_u/studentersamfundet',
|
||||
});
|
||||
|
||||
openMap() {
|
||||
const url = Platform.select({
|
||||
ios: `comgooglemaps://?q=${CHATEAU_NEUF_ADDRESS}`,
|
||||
android: `https://www.google.com/maps/search/?api=1&query=${CHATEAU_NEUF_ADDRESS}`
|
||||
});
|
||||
Linking.canOpenURL(url).then(supported => {
|
||||
if (!supported) {
|
||||
return Linking.openURL(`https://www.google.com/maps/search/?api=1&query=${CHATEAU_NEUF_ADDRESS}`);
|
||||
} else {
|
||||
return Linking.openURL(url);
|
||||
}
|
||||
}).catch(err => console.error('An error occurred', err));
|
||||
}
|
||||
Linking.canOpenURL(url)
|
||||
.then((supported) => {
|
||||
if (!supported) {
|
||||
return Linking.openURL('https://www.instagram.com/studentersamfundet/');
|
||||
}
|
||||
return Linking.openURL(url);
|
||||
})
|
||||
.catch((err) => console.error('An error occurred', err));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.card}>
|
||||
<Text style={[styles.header, {marginTop: 8}]}>Det Norske Studentersamfund</Text>
|
||||
<TouchableOpacity onPress={() => Linking.openURL('https://www.studentersamfundet.no/')}>
|
||||
<Image source={{uri: 'https://galtinn.neuf.no/static/dist/images/logo.png'}} style={styles.logo} resizeMode="contain" />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.header}>Følg oss på</Text>
|
||||
<View style={[styles.section, {flex: 1, flexDirection: 'row'}]}>
|
||||
<TouchableOpacity style={styles.someBtn} onPress={this.openFacebook}>
|
||||
<Icon name="logo-facebook" style={styles.largeIcon} /><NBText>Facebook</NBText>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.someBtn} onPress={this.openInstagram}>
|
||||
<Icon name="logo-instagram" style={styles.largeIcon} /><NBText>Instagram</NBText>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
openMap() {
|
||||
const url = Platform.select({
|
||||
ios: `comgooglemaps://?q=${CHATEAU_NEUF_ADDRESS}`,
|
||||
android: `https://www.google.com/maps/search/?api=1&query=${CHATEAU_NEUF_ADDRESS}`,
|
||||
});
|
||||
Linking.canOpenURL(url)
|
||||
.then((supported) => {
|
||||
if (!supported) {
|
||||
return Linking.openURL(`https://www.google.com/maps/search/?api=1&query=${CHATEAU_NEUF_ADDRESS}`);
|
||||
}
|
||||
return Linking.openURL(url);
|
||||
})
|
||||
.catch((err) => console.error('An error occurred', err));
|
||||
}
|
||||
|
||||
<Text style={styles.header}>Adresse</Text>
|
||||
<Text style={[styles.paragraph, {textAlign: 'center'}]}>Det Norske Studentersamfund{"\n"}Slemdalsveien 15, 0369 Oslo</Text>
|
||||
<View style={styles.section}>
|
||||
<Button iconLeft onPress={this.openMap}>
|
||||
<Icon name="map" style={styles.icon} /><NBText> Vis i kart</NBText>
|
||||
</Button>
|
||||
</View>
|
||||
<Text style={styles.header}>Åpningstider</Text>
|
||||
<View style={styles.section}>
|
||||
<Button iconLeft onPress={() => Linking.openURL('https://studentersamfundet.no/aapningstider/')}>
|
||||
<Icon name="time" style={styles.icon} /><NBText>Vis Åpningstider</NBText>
|
||||
</Button>
|
||||
</View>
|
||||
<Text style={styles.header}>Ta kontakt</Text>
|
||||
<View style={[styles.section, {flex: 1, flexDirection: 'row'}]}>
|
||||
<View>
|
||||
<Button iconLeft onPress={() => Linking.openURL('tel:+4794430002')} style={{marginHorizontal: 10}}>
|
||||
<Icon name="call" style={styles.icon} /><NBText>Ring</NBText>
|
||||
</Button>
|
||||
</View>
|
||||
<View>
|
||||
<Button iconLeft onPress={() => Linking.openURL('mailto:ledelsen@studentersamfundet.no')} style={{marginHorizontal: 10}}>
|
||||
<Icon name="mail" style={styles.icon} /><NBText>Send e-post</NBText>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
render() {
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.card}>
|
||||
<Text style={[styles.header, { marginTop: 8 }]}>Det Norske Studentersamfund</Text>
|
||||
<TouchableOpacity onPress={() => Linking.openURL('https://www.studentersamfundet.no/')}>
|
||||
<Image
|
||||
source={{ uri: 'https://galtinn.neuf.no/static/dist/images/logo.png' }}
|
||||
style={styles.logo}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.header}>Følg oss på</Text>
|
||||
<View style={[styles.section, { flex: 1, flexDirection: 'row' }]}>
|
||||
<TouchableOpacity style={styles.someBtn} onPress={this.openFacebook}>
|
||||
<Icon name="logo-facebook" style={styles.largeIcon} />
|
||||
<NBText>Facebook</NBText>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.someBtn} onPress={this.openInstagram}>
|
||||
<Icon name="logo-instagram" style={styles.largeIcon} />
|
||||
<NBText>Instagram</NBText>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<Text style={styles.header}>Chateau Neuf</Text>
|
||||
<Image source={{uri: 'https://studentersamfundet.no/wp-content/uploads/2015/04/26_KUL_Chateauneuf-1280x720.jpg'}} style={styles.featuredImage} resizeMode="contain" />
|
||||
<Text style={styles.paragraph}>Studentene i Oslo har sitt naturlige tilholdssted på Det Norske Studentersamfund, i hyggelige lokaler på Chateau Neuf øverst på Majorstuen. Her er det åpent alle dager unntatt søndag, og hver dag skjer det en rekke ting i våre mange arrangementslokaler. Enten man ønsker en tur i baren, på kafé, på debatt, på konsert, teater eller kino, har man muligheten på Chateau Neuf.</Text>
|
||||
<Text style={styles.paragraph}>Chateau Neuf er et frivillig drevet studenthus. Studenter over alt i Oslo kan komme hit og få nyttig og spennende erfaring med å drive et av Oslos største kulturhus. Her kan du stå i bar, sette lys og lyd, jobbe med markedsføring, produsere konserter, bli kjent med hundrevis av bra mennesker og få minner for livet.</Text>
|
||||
<Text style={styles.paragraph}>Alle studentforeninger i Oslo kan arrangere gratis på Chateau Neuf. Skal du arrangere en fest, sette en konsert, holde et foredrag eller gjennomføre et møte, har vi lokaler som kan brukes til dette.</Text>
|
||||
<Text style={styles.header}>Adresse</Text>
|
||||
<Text style={[styles.paragraph, { textAlign: 'center' }]}>
|
||||
Det Norske Studentersamfund{'\n'}Slemdalsveien 15, 0369 Oslo
|
||||
</Text>
|
||||
<View style={styles.section}>
|
||||
<Button iconLeft onPress={this.openMap}>
|
||||
<Icon name="map" style={styles.icon} />
|
||||
<NBText> Vis i kart</NBText>
|
||||
</Button>
|
||||
</View>
|
||||
<Text style={styles.header}>Åpningstider</Text>
|
||||
<View style={styles.section}>
|
||||
<Button iconLeft onPress={() => Linking.openURL('https://studentersamfundet.no/aapningstider/')}>
|
||||
<Icon name="time" style={styles.icon} />
|
||||
<NBText>Vis Åpningstider</NBText>
|
||||
</Button>
|
||||
</View>
|
||||
<Text style={styles.header}>Ta kontakt</Text>
|
||||
<View style={[styles.section, { flex: 1, flexDirection: 'row' }]}>
|
||||
<View>
|
||||
<Button iconLeft onPress={() => Linking.openURL('tel:+4794430002')} style={{ marginHorizontal: 10 }}>
|
||||
<Icon name="call" style={styles.icon} />
|
||||
<NBText>Ring</NBText>
|
||||
</Button>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
iconLeft
|
||||
onPress={() => Linking.openURL('mailto:ledelsen@studentersamfundet.no')}
|
||||
style={{ marginHorizontal: 10 }}
|
||||
>
|
||||
<Icon name="mail" style={styles.icon} />
|
||||
<NBText>Send e-post</NBText>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/*<HTMLView style={styles.content} stylesheet={HTMLStyles} value={this.props.openingHours}/>*/}
|
||||
</View>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
<Text style={styles.header}>Chateau Neuf</Text>
|
||||
<Image
|
||||
source={{ uri: 'https://studentersamfundet.no/wp-content/uploads/2015/04/26_KUL_Chateauneuf-1280x720.jpg' }}
|
||||
style={styles.featuredImage}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<Text style={styles.paragraph}>
|
||||
Studentene i Oslo har sitt naturlige tilholdssted på Det Norske Studentersamfund, i hyggelige lokaler på
|
||||
Chateau Neuf øverst på Majorstuen. Her er det åpent alle dager unntatt søndag, og hver dag skjer det en
|
||||
rekke ting i våre mange arrangementslokaler. Enten man ønsker en tur i baren, på kafé, på debatt, på
|
||||
konsert, teater eller kino, har man muligheten på Chateau Neuf.
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Chateau Neuf er et frivillig drevet studenthus. Studenter over alt i Oslo kan komme hit og få nyttig og
|
||||
spennende erfaring med å drive et av Oslos største kulturhus. Her kan du stå i bar, sette lys og lyd, jobbe
|
||||
med markedsføring, produsere konserter, bli kjent med hundrevis av bra mennesker og få minner for livet.
|
||||
</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Alle studentforeninger i Oslo kan arrangere gratis på Chateau Neuf. Skal du arrangere en fest, sette en
|
||||
konsert, holde et foredrag eller gjennomføre et møte, har vi lokaler som kan brukes til dette.
|
||||
</Text>
|
||||
|
||||
{/* <HTMLView style={styles.content} stylesheet={HTMLStyles} value={this.props.openingHours}/> */}
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
header: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#000',
|
||||
textAlign: 'center',
|
||||
},
|
||||
paragraph: {
|
||||
fontSize: 16,
|
||||
color: '#000',
|
||||
paddingBottom: 10,
|
||||
lineHeight: 25,
|
||||
},
|
||||
card: theme.card,
|
||||
featuredImage: {
|
||||
height: 230,
|
||||
width: null,
|
||||
},
|
||||
logo: {
|
||||
height: 128,
|
||||
width: null,
|
||||
marginBottom: 10,
|
||||
},
|
||||
icon: {
|
||||
fontSize: 16,
|
||||
},
|
||||
largeIcon: {
|
||||
fontSize: 40,
|
||||
},
|
||||
section: {
|
||||
marginTop: 8,
|
||||
marginBottom: 20,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
someBtn: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
}
|
||||
header: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#000',
|
||||
textAlign: 'center',
|
||||
},
|
||||
paragraph: {
|
||||
fontSize: 16,
|
||||
color: '#000',
|
||||
paddingBottom: 10,
|
||||
lineHeight: 25,
|
||||
},
|
||||
card: theme.card,
|
||||
featuredImage: {
|
||||
height: 230,
|
||||
width: null,
|
||||
},
|
||||
logo: {
|
||||
height: 128,
|
||||
width: null,
|
||||
marginBottom: 10,
|
||||
},
|
||||
icon: {
|
||||
fontSize: 16,
|
||||
},
|
||||
largeIcon: {
|
||||
fontSize: 40,
|
||||
},
|
||||
section: {
|
||||
marginTop: 8,
|
||||
marginBottom: 20,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
someBtn: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
// const HTMLStyles = StyleSheet.create({
|
||||
@@ -165,4 +200,4 @@ const styles = StyleSheet.create({
|
||||
// marginTop: -20,
|
||||
// marginBottom: -30,
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
@@ -1,45 +1,12 @@
|
||||
import {connect} from "react-redux";
|
||||
import React, {Component} from 'react';
|
||||
import Config from 'react-native-config';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import About from "./About";
|
||||
import About from './About';
|
||||
|
||||
class AboutContainer extends Component {
|
||||
// constructor(props) {
|
||||
// super(props);
|
||||
// this.state = {
|
||||
// openingHours: '',
|
||||
// loading: false,
|
||||
// error: null,
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// componentDidMount() {
|
||||
// this.fetchOpeningHours();
|
||||
// }
|
||||
//
|
||||
// fetchOpeningHours() {
|
||||
// const url = `${Config.EVENT_API_URL}/wp-json/wp/v2/pages/?slug=aapningstider`;
|
||||
// this.setState({ loading: true });
|
||||
//
|
||||
// fetch(url)
|
||||
// .then(res => res.json())
|
||||
// .then(res => {
|
||||
// this.setState({
|
||||
// openingHours: res ? res[0].content.rendered : '',
|
||||
// error: res.error || null,
|
||||
// loading: false,
|
||||
// });
|
||||
//
|
||||
// })
|
||||
// .catch(error => {
|
||||
// this.setState({ error, loading: false });
|
||||
// });
|
||||
// }
|
||||
|
||||
render() {
|
||||
return <About {...this.state} />;
|
||||
}
|
||||
render() {
|
||||
return <About {...this.state} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(AboutContainer);
|
||||
export default connect()(AboutContainer);
|
||||
+292
-272
@@ -1,305 +1,325 @@
|
||||
import React, { Component } from 'react';
|
||||
import {Linking, StyleSheet, ScrollView, Text, Platform, View, Image, TouchableOpacity} from "react-native";
|
||||
import { Linking, StyleSheet, ScrollView, Text, Platform, View, Image, TouchableOpacity } from 'react-native';
|
||||
import HTMLView from 'react-native-htmlview';
|
||||
import moment from "moment";
|
||||
import {Icon} from "native-base";
|
||||
import theme from "../../theme";
|
||||
import moment from 'moment';
|
||||
import { Icon } from 'native-base';
|
||||
import theme from '../../theme';
|
||||
|
||||
moment.locale('nb');
|
||||
|
||||
const CHATEAU_NEUF_ADDRESS = 'Chateau Neuf, Slemdalsveien 15, 0369 Oslo';
|
||||
|
||||
export default class EventDetail extends Component {
|
||||
_formatTime(time) {
|
||||
return moment(time).format('HH:mm');
|
||||
}
|
||||
|
||||
_formatTime(time) {
|
||||
return moment(time).format('HH:mm');
|
||||
_formatMonth(time) {
|
||||
return moment(time).format('MMM');
|
||||
}
|
||||
|
||||
_formatDay(time) {
|
||||
return moment(time).format('DD.');
|
||||
}
|
||||
|
||||
_formatLocalDay(time) {
|
||||
return moment(time).format('ddd');
|
||||
}
|
||||
|
||||
_formatYear(time) {
|
||||
return moment(time).format('YYYY');
|
||||
}
|
||||
|
||||
_formatRelative(time) {
|
||||
return moment(time).fromNow();
|
||||
}
|
||||
|
||||
_formatLocation() {
|
||||
if (this.props.item.venue !== 'Annetsteds') {
|
||||
return CHATEAU_NEUF_ADDRESS;
|
||||
}
|
||||
return 'Ukjent adresse';
|
||||
}
|
||||
|
||||
_formatTicketText() {
|
||||
/* FIXME: This should be handled by API */
|
||||
const { item } = this.props;
|
||||
let reg_kr = '';
|
||||
let mem_kr = '';
|
||||
if (item.price_regular === '0') {
|
||||
item.price_regular = 'Gratis';
|
||||
}
|
||||
if (item.price_member === '0') {
|
||||
item.price_member = 'Gratis';
|
||||
}
|
||||
if (item.price_regular !== 'Gratis') {
|
||||
reg_kr = ' kr';
|
||||
}
|
||||
if (item.price_member !== 'Gratis') {
|
||||
mem_kr = ' kr';
|
||||
}
|
||||
|
||||
_formatMonth(time) {
|
||||
return moment(time).format('MMM');
|
||||
let text = '';
|
||||
if (item.price_regular && item.price_member) {
|
||||
text = `Pris: ${item.price_regular}${reg_kr} / Medlemmer: ${item.price_member}${mem_kr}`;
|
||||
} else if (item.price_regular) {
|
||||
text = `Pris: ${item.price_regular}${reg_kr}`;
|
||||
} else if (item.price_member) {
|
||||
text = `Pris (Medlemmer): ${item.price_member}${mem_kr}`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
_formatDay(time) {
|
||||
return moment(time).format('DD.');
|
||||
}
|
||||
_formatLocalDay(time) {
|
||||
return moment(time).format('ddd')
|
||||
}
|
||||
onTicketUrlPress(link) {
|
||||
Linking.openURL(link);
|
||||
}
|
||||
|
||||
_formatYear(time) {
|
||||
return moment(time).format('YYYY');
|
||||
onFacebookUrlPress(link) {
|
||||
const fbEventPattern = /https?:\/\/www\.facebook\.com\/events\/(\d+)\/?/i;
|
||||
const matches = fbEventPattern.exec(link);
|
||||
let iosURL = link;
|
||||
if (matches && matches.length) {
|
||||
iosURL = `fb://event/?id=${matches[1]}`;
|
||||
}
|
||||
|
||||
_formatRelative(time) {
|
||||
return moment(time).fromNow();
|
||||
}
|
||||
_formatLocation() {
|
||||
if(this.props.item.venue !== 'Annetsteds') {
|
||||
return CHATEAU_NEUF_ADDRESS;
|
||||
const url = Platform.select({
|
||||
ios: iosURL,
|
||||
android: link,
|
||||
});
|
||||
Linking.canOpenURL(url)
|
||||
.then((supported) => {
|
||||
if (!supported) {
|
||||
return Linking.openURL(link);
|
||||
}
|
||||
return 'Ukjent adresse'
|
||||
return Linking.openURL(url);
|
||||
})
|
||||
.catch((err) => console.error('An error occurred', err));
|
||||
}
|
||||
|
||||
onLocationPress(venue) {
|
||||
if (venue !== 'Annetsteds') {
|
||||
Linking.openURL(`https://www.google.com/maps/dir/?api=1&destination=${CHATEAU_NEUF_ADDRESS}`);
|
||||
}
|
||||
}
|
||||
|
||||
showPriceAndTicket() {
|
||||
const { item } = this.props;
|
||||
const text = this._formatTicketText();
|
||||
|
||||
if (text === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
_formatTicketText() {
|
||||
/* FIXME: This should be handled by API */
|
||||
const {item} = this.props;
|
||||
let reg_kr = '';
|
||||
let mem_kr = '';
|
||||
if (item.price_regular === '0') {
|
||||
item.price_regular = 'Gratis'
|
||||
}
|
||||
if (item.price_member === '0') {
|
||||
item.price_member = 'Gratis'
|
||||
}
|
||||
if (item.price_regular !== 'Gratis') {
|
||||
reg_kr = " kr"
|
||||
}
|
||||
if (item.price_member !== 'Gratis') {
|
||||
mem_kr = " kr"
|
||||
}
|
||||
|
||||
let text = '';
|
||||
if (item.price_regular && item.price_member) {
|
||||
text = `Pris: ${item.price_regular}${reg_kr} / Medlemmer: ${item.price_member}${mem_kr}`;
|
||||
}
|
||||
else if (item.price_regular) {
|
||||
text = `Pris: ${item.price_regular}${reg_kr}`;
|
||||
}
|
||||
else if (item.price_member) {
|
||||
text = `Pris (Medlemmer): ${item.price_member}${mem_kr}`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
return text;
|
||||
if (item.ticket_url) {
|
||||
return (
|
||||
<TouchableOpacity style={styles.metaInner} onPress={() => this.onTicketUrlPress(item.ticket_url)}>
|
||||
<View style={styles.metaIcon}>
|
||||
<Icon name="card" style={styles.icons} />
|
||||
</View>
|
||||
<View style={{ flexDirection: 'column' }}>
|
||||
<Text style={styles.metaText}>{text}</Text>
|
||||
<Text style={styles.metaSubtitle}>Kjøp billett</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
onTicketUrlPress(link) {
|
||||
Linking.openURL(link);
|
||||
return (
|
||||
<View style={styles.metaInner}>
|
||||
<View style={styles.metaIcon}>
|
||||
<Icon name="card" style={styles.icons} />
|
||||
</View>
|
||||
<View style={{ flexDirection: 'column' }}>
|
||||
<Text style={styles.metaText}>{text}</Text>
|
||||
<Text style={styles.metaSubtitle}>Billett i døra</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
showFacebutton() {
|
||||
const { item } = this.props;
|
||||
if (!item.facebook_url) {
|
||||
return;
|
||||
}
|
||||
|
||||
onFacebookUrlPress(link) {
|
||||
const fbEventPattern = /https?:\/\/www\.facebook\.com\/events\/(\d+)\/?/i;
|
||||
const matches = fbEventPattern.exec(link);
|
||||
let iosURL = link;
|
||||
if( matches && matches.length ) {
|
||||
iosURL = 'fb://event/?id=' + matches[1];
|
||||
}
|
||||
const url = Platform.select({
|
||||
ios: iosURL,
|
||||
android: link
|
||||
});
|
||||
Linking.canOpenURL(url).then(supported => {
|
||||
if (!supported) {
|
||||
return Linking.openURL(link);
|
||||
} else {
|
||||
return Linking.openURL(url);
|
||||
}
|
||||
}).catch(err => console.error('An error occurred', err));
|
||||
return (
|
||||
<TouchableOpacity onPress={() => this.onFacebookUrlPress(item.facebook_url)} style={styles.metaInner}>
|
||||
<View style={[styles.metaIcon, { marginTop: 0 }]}>
|
||||
<Icon name="logo-facebook" style={styles.icons} />
|
||||
</View>
|
||||
<View>
|
||||
<Text style={[styles.metaText, { paddingTop: 2 }]}>På Facebook</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
showImage() {
|
||||
const { item } = this.props;
|
||||
|
||||
if (item.thumbnail.medium_large) {
|
||||
return <Image source={{ uri: item.thumbnail.medium_large }} style={styles.image} />;
|
||||
}
|
||||
}
|
||||
|
||||
onLocationPress(venue) {
|
||||
if( venue !== 'Annetsteds' ) {
|
||||
Linking.openURL(`https://www.google.com/maps/dir/?api=1&destination=${CHATEAU_NEUF_ADDRESS}`);
|
||||
}
|
||||
showYear() {
|
||||
const { item } = this.props;
|
||||
|
||||
if (moment().year() !== moment(item.start_time).year()) {
|
||||
return <Text style={styles.year}>{this._formatYear(item.start_time)}</Text>;
|
||||
}
|
||||
}
|
||||
|
||||
showPriceAndTicket() {
|
||||
const {item} = this.props;
|
||||
const text = this._formatTicketText();
|
||||
render() {
|
||||
const { item } = this.props;
|
||||
|
||||
if(text === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if(item.ticket_url) {
|
||||
return (
|
||||
<TouchableOpacity style={styles.metaInner} onPress={() => this.onTicketUrlPress(item.ticket_url)}>
|
||||
<View style={styles.metaIcon}><Icon name="card" style={styles.icons} /></View>
|
||||
<View style={{flexDirection: 'column'}}>
|
||||
<Text style={styles.metaText}>{text}</Text>
|
||||
<Text style={styles.metaSubtitle}>Kjøp billett</Text>
|
||||
</View>
|
||||
</TouchableOpacity>);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.metaInner}>
|
||||
<View style={styles.metaIcon}><Icon name="card" style={styles.icons} /></View>
|
||||
<View style={{flexDirection: 'column'}}>
|
||||
<Text style={styles.metaText}>{text}</Text>
|
||||
<Text style={styles.metaSubtitle}>Billett i døra</Text>
|
||||
</View>
|
||||
</View>)
|
||||
}
|
||||
|
||||
showFacebutton() {
|
||||
const {item} = this.props;
|
||||
if (!item.facebook_url) { return;}
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={() => this.onFacebookUrlPress(item.facebook_url)} style={styles.metaInner}>
|
||||
<View style={[styles.metaIcon, {marginTop: 0}]}><Icon name="logo-facebook" style={styles.icons} /></View>
|
||||
<View>
|
||||
<Text style={[styles.metaText, {paddingTop: 2}]}>På Facebook</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
showImage() {
|
||||
const {item} = this.props;
|
||||
|
||||
if (item.thumbnail.medium_large) {
|
||||
return <Image source={{uri: item.thumbnail.medium_large}} style={styles.image} />;
|
||||
}
|
||||
}
|
||||
|
||||
showYear() {
|
||||
const {item} = this.props;
|
||||
|
||||
if(moment().year() !== moment(item.start_time).year()) {
|
||||
return <Text style={styles.year}>{this._formatYear(item.start_time)}</Text>
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {item} = this.props;
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
{this.showImage()}
|
||||
<View style={styles.card}>
|
||||
{/* Date and title */}
|
||||
<View style={styles.dateAndTitle}>
|
||||
<View style={styles.dateContainer}>
|
||||
<View style={[styles.card, styles.dateCard]}>
|
||||
<Text style={styles.day}>{this._formatDay(item.start_time)}</Text>
|
||||
<Text style={styles.month}>{this._formatMonth(item.start_time)}</Text>
|
||||
{this.showYear()}
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title} numberOfLines={2}>{item.title.decoded}</Text>
|
||||
</View>
|
||||
</View>
|
||||
{/* Time and calendar */}
|
||||
<TouchableOpacity style={styles.metaInner}>
|
||||
<View style={styles.metaIcon}><Icon name="time" style={styles.icons} /></View>
|
||||
<View style={{flexDirection: 'column'}}>
|
||||
<Text style={styles.metaText}>{this._formatLocalDay(item.start_time)} kl. {this._formatTime(item.start_time)} - {this._formatTime(item.end_time)}</Text>
|
||||
<Text style={styles.metaSubtitle}>{this._formatRelative(item.start_time)}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
{/* Location and maps */}
|
||||
<TouchableOpacity style={styles.metaInner} onPress={() => this.onLocationPress(item.venue)}>
|
||||
<View style={[styles.metaIcon, {paddingLeft: 1}]}><Icon name="pin" style={styles.icons} /></View>
|
||||
<View style={{flexDirection: 'column'}}>
|
||||
<Text style={styles.metaText}>{item.venue}</Text>
|
||||
<Text style={styles.metaSubtitle} numberOfLines={1}>{this._formatLocation()}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
{this.showPriceAndTicket()}
|
||||
{this.showFacebutton()}
|
||||
<HTMLView style={styles.content} stylesheet={HTMLStyles} value={item.content.rendered}/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<ScrollView>
|
||||
{this.showImage()}
|
||||
<View style={styles.card}>
|
||||
{/* Date and title */}
|
||||
<View style={styles.dateAndTitle}>
|
||||
<View style={styles.dateContainer}>
|
||||
<View style={[styles.card, styles.dateCard]}>
|
||||
<Text style={styles.day}>{this._formatDay(item.start_time)}</Text>
|
||||
<Text style={styles.month}>{this._formatMonth(item.start_time)}</Text>
|
||||
{this.showYear()}
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title} numberOfLines={2}>
|
||||
{item.title.decoded}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
{/* Time and calendar */}
|
||||
<TouchableOpacity style={styles.metaInner}>
|
||||
<View style={styles.metaIcon}>
|
||||
<Icon name="time" style={styles.icons} />
|
||||
</View>
|
||||
<View style={{ flexDirection: 'column' }}>
|
||||
<Text style={styles.metaText}>
|
||||
{this._formatLocalDay(item.start_time)} kl. {this._formatTime(item.start_time)} -{' '}
|
||||
{this._formatTime(item.end_time)}
|
||||
</Text>
|
||||
<Text style={styles.metaSubtitle}>{this._formatRelative(item.start_time)}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
{/* Location and maps */}
|
||||
<TouchableOpacity style={styles.metaInner} onPress={() => this.onLocationPress(item.venue)}>
|
||||
<View style={[styles.metaIcon, { paddingLeft: 1 }]}>
|
||||
<Icon name="pin" style={styles.icons} />
|
||||
</View>
|
||||
<View style={{ flexDirection: 'column' }}>
|
||||
<Text style={styles.metaText}>{item.venue}</Text>
|
||||
<Text style={styles.metaSubtitle} numberOfLines={1}>
|
||||
{this._formatLocation()}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
{this.showPriceAndTicket()}
|
||||
{this.showFacebutton()}
|
||||
<HTMLView style={styles.content} stylesheet={HTMLStyles} value={item.content.rendered} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
dateAndTitle: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: 8
|
||||
},
|
||||
titleContainer: {
|
||||
paddingLeft: 10,
|
||||
justifyContent: 'center',
|
||||
flex: 4,
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#000',
|
||||
},
|
||||
card: theme.card,
|
||||
image: {
|
||||
height: 260,
|
||||
width: null
|
||||
},
|
||||
time: {
|
||||
fontSize: 14,
|
||||
color: '#666'
|
||||
},
|
||||
metaInner: {
|
||||
flexDirection: 'row',
|
||||
marginVertical: 8,
|
||||
},
|
||||
metaIcon: {
|
||||
width: 16,
|
||||
paddingTop: 3,
|
||||
marginHorizontal: 8,
|
||||
marginVertical: 8,
|
||||
},
|
||||
metaText: {
|
||||
color: '#000',
|
||||
},
|
||||
metaSubtitle: {
|
||||
color: '#666',
|
||||
},
|
||||
content: {
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#ccc',
|
||||
marginTop: 8,
|
||||
paddingTop: 10,
|
||||
},
|
||||
dateContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
dateCard: {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
paddingBottom: 6,
|
||||
width: 62,
|
||||
},
|
||||
day: {
|
||||
paddingTop: 5,
|
||||
fontSize: 20,
|
||||
color: '#000',
|
||||
textAlign: 'center',
|
||||
},
|
||||
month: {
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
year: {
|
||||
fontSize: 9,
|
||||
color: '#fff',
|
||||
backgroundColor: '#f58220',
|
||||
textAlign: 'center',
|
||||
paddingRight: 20,
|
||||
paddingBottom: 3,
|
||||
paddingLeft: 20,
|
||||
paddingTop: 3,
|
||||
},
|
||||
icons: {
|
||||
fontSize: 16,
|
||||
},
|
||||
|
||||
dateAndTitle: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: 8,
|
||||
},
|
||||
titleContainer: {
|
||||
paddingLeft: 10,
|
||||
justifyContent: 'center',
|
||||
flex: 4,
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: '#000',
|
||||
},
|
||||
card: theme.card,
|
||||
image: {
|
||||
height: 260,
|
||||
width: null,
|
||||
},
|
||||
time: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
},
|
||||
metaInner: {
|
||||
flexDirection: 'row',
|
||||
marginVertical: 8,
|
||||
},
|
||||
metaIcon: {
|
||||
width: 16,
|
||||
paddingTop: 3,
|
||||
marginHorizontal: 8,
|
||||
marginVertical: 8,
|
||||
},
|
||||
metaText: {
|
||||
color: '#000',
|
||||
},
|
||||
metaSubtitle: {
|
||||
color: '#666',
|
||||
},
|
||||
content: {
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#ccc',
|
||||
marginTop: 8,
|
||||
paddingTop: 10,
|
||||
},
|
||||
dateContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
dateCard: {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
paddingBottom: 6,
|
||||
width: 62,
|
||||
},
|
||||
day: {
|
||||
paddingTop: 5,
|
||||
fontSize: 20,
|
||||
color: '#000',
|
||||
textAlign: 'center',
|
||||
},
|
||||
month: {
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
year: {
|
||||
fontSize: 9,
|
||||
color: '#fff',
|
||||
backgroundColor: '#f58220',
|
||||
textAlign: 'center',
|
||||
paddingRight: 20,
|
||||
paddingBottom: 3,
|
||||
paddingLeft: 20,
|
||||
paddingTop: 3,
|
||||
},
|
||||
icons: {
|
||||
fontSize: 16,
|
||||
},
|
||||
});
|
||||
|
||||
//FIXME: Ninja margin
|
||||
// FIXME: Ninja margin
|
||||
const HTMLStyles = StyleSheet.create({
|
||||
p: {
|
||||
fontSize: 16,
|
||||
marginBottom: -30,
|
||||
color: '#000',
|
||||
},
|
||||
ul: {
|
||||
fontSize: 16,
|
||||
color: '#000',
|
||||
marginTop: -20,
|
||||
marginBottom: -30,
|
||||
},
|
||||
});
|
||||
p: {
|
||||
fontSize: 16,
|
||||
marginBottom: -30,
|
||||
color: '#000',
|
||||
},
|
||||
ul: {
|
||||
fontSize: 16,
|
||||
color: '#000',
|
||||
marginTop: -20,
|
||||
marginBottom: -30,
|
||||
},
|
||||
});
|
||||
+128
-106
@@ -1,122 +1,144 @@
|
||||
import {StyleSheet, View, Platform} from "react-native";
|
||||
import {SectionList} from 'react-navigation';
|
||||
import {Card, ListItem, CardItem, Body, Text as Text, Left, Right, Icon, Button, Content, Spinner} from 'native-base';
|
||||
import { StyleSheet, View, Platform } from 'react-native';
|
||||
import { SectionList } from 'react-navigation';
|
||||
import { Card, ListItem, CardItem, Body, Text, Left, Right, Icon, Button, Content, Spinner } from 'native-base';
|
||||
import React, { Component } from 'react';
|
||||
import moment from "moment";
|
||||
import moment from 'moment';
|
||||
import 'moment/locale/nb';
|
||||
import theme from "../../theme";
|
||||
import theme from '../../theme';
|
||||
|
||||
moment.locale('nb');
|
||||
|
||||
export default class EventList extends Component {
|
||||
_renderItem = ({item}) => (
|
||||
<ListItem button onPress={() => { this._onPressItem(item); }} style={styles.listItem}>
|
||||
<Body style={{flex: 5}}>
|
||||
<Text style={styles.listItemTitle} numberOfLines={1}>{item.title.decoded}</Text>
|
||||
<Text style={styles.listItemTime}>{this._formatTime(item.start_time)}</Text>
|
||||
</Body>
|
||||
<Right style={{flex: 1}}>
|
||||
<Icon name="arrow-forward" />
|
||||
</Right>
|
||||
</ListItem>
|
||||
_renderItem = ({ item }) => (
|
||||
<ListItem
|
||||
button
|
||||
onPress={() => {
|
||||
this._onPressItem(item);
|
||||
}}
|
||||
style={styles.listItem}
|
||||
>
|
||||
<Body style={{ flex: 5 }}>
|
||||
<Text style={styles.listItemTitle} numberOfLines={1}>
|
||||
{item.title.decoded}
|
||||
</Text>
|
||||
<Text style={styles.listItemTime}>{this._formatTime(item.start_time)}</Text>
|
||||
</Body>
|
||||
<Right style={{ flex: 1 }}>
|
||||
<Icon name="arrow-forward" />
|
||||
</Right>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
_renderSectionHeader = ({ section }) => {
|
||||
return (
|
||||
<ListItem style={styles.sectionHeader}>
|
||||
<Body>
|
||||
<Text style={styles.sectionTitle}>{section.title}</Text>
|
||||
</Body>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
_renderSectionHeader = ({section}) => {
|
||||
return (<ListItem style={styles.sectionHeader}>
|
||||
<Body>
|
||||
<Text style={styles.sectionTitle}>{section.title}</Text>
|
||||
</Body>
|
||||
</ListItem>);
|
||||
};
|
||||
|
||||
_renderFooter = () => {
|
||||
if(!this.props.loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (<View style={{paddingVertical: 20}}>
|
||||
<Spinner color="#f58220"/>
|
||||
</View>);
|
||||
};
|
||||
|
||||
_onPressItem = (item) => {
|
||||
this.props.showEvent(item);
|
||||
};
|
||||
|
||||
|
||||
_formatTime(time) {
|
||||
return moment(time).format('llll');
|
||||
_renderFooter = () => {
|
||||
if (!this.props.loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.error) {
|
||||
return (<Content>
|
||||
<Card style={{margin: 4}}>
|
||||
<CardItem onPress={() => { this._onPressItem(item); }}>
|
||||
<Text style={styles.loadingText}>Kunne ikke hente programmet...</Text>
|
||||
</CardItem>
|
||||
<CardItem>
|
||||
<Button onPress={this.props.handleRefresh}><Text>Prøv igjen</Text></Button>
|
||||
</CardItem>
|
||||
</Card>
|
||||
</Content>)
|
||||
}
|
||||
return (
|
||||
<View style={{ paddingVertical: 20 }}>
|
||||
<Spinner color="#f58220" />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (<SectionList
|
||||
sections={this.props.eventsSectioned}
|
||||
renderItem={this._renderItem}
|
||||
renderSectionHeader={this._renderSectionHeader}
|
||||
keyExtractor={(item) => item.id}
|
||||
refreshing={this.props.refreshing}
|
||||
onRefresh={this.props.handleRefresh}
|
||||
onEndReached={this.props.handleLoadMore}
|
||||
onEndReachedThreshould={10}
|
||||
initialNumToRender={10}
|
||||
ListFooterComponent={this._renderFooter}
|
||||
style={[styles.card, styles.list]}
|
||||
ListHeaderComponent={() => { return <View style={styles.listHeader}/> }}
|
||||
/>);
|
||||
_onPressItem = (item) => {
|
||||
this.props.showEvent(item);
|
||||
};
|
||||
|
||||
_formatTime(time) {
|
||||
return moment(time).format('llll');
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.error) {
|
||||
return (
|
||||
<Content>
|
||||
<Card style={{ margin: 4 }}>
|
||||
<CardItem
|
||||
onPress={() => {
|
||||
this._onPressItem(item);
|
||||
}}
|
||||
>
|
||||
<Text style={styles.loadingText}>Kunne ikke hente programmet...</Text>
|
||||
</CardItem>
|
||||
<CardItem>
|
||||
<Button onPress={this.props.handleRefresh}>
|
||||
<Text>Prøv igjen</Text>
|
||||
</Button>
|
||||
</CardItem>
|
||||
</Card>
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionList
|
||||
sections={this.props.eventsSectioned}
|
||||
renderItem={this._renderItem}
|
||||
renderSectionHeader={this._renderSectionHeader}
|
||||
keyExtractor={(item) => item.id}
|
||||
refreshing={this.props.refreshing}
|
||||
onRefresh={this.props.handleRefresh}
|
||||
onEndReached={this.props.handleLoadMore}
|
||||
onEndReachedThreshould={10}
|
||||
initialNumToRender={10}
|
||||
ListFooterComponent={this._renderFooter}
|
||||
style={[styles.card, styles.list]}
|
||||
ListHeaderComponent={() => {
|
||||
return <View style={styles.listHeader} />;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
listItem: {
|
||||
backgroundColor: 'white',
|
||||
marginLeft: 0
|
||||
},
|
||||
listItemTitle: {
|
||||
fontSize: 14,
|
||||
},
|
||||
listItemTime: {
|
||||
fontSize: 14,
|
||||
color: '#666'
|
||||
},
|
||||
sectionHeader: {
|
||||
backgroundColor: 'white',
|
||||
marginLeft: 0,
|
||||
...Platform.select({
|
||||
'ios': {marginBottom: -8},
|
||||
}),
|
||||
paddingBottom: 0,
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
loadingText: {
|
||||
textAlign: 'center',
|
||||
paddingBottom: 8
|
||||
},
|
||||
card: theme.card,
|
||||
list: {
|
||||
padding: 0,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
listHeader: {
|
||||
paddingTop: 8,
|
||||
}
|
||||
listItem: {
|
||||
backgroundColor: 'white',
|
||||
marginLeft: 0,
|
||||
},
|
||||
listItemTitle: {
|
||||
fontSize: 14,
|
||||
},
|
||||
listItemTime: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
},
|
||||
sectionHeader: {
|
||||
backgroundColor: 'white',
|
||||
marginLeft: 0,
|
||||
...Platform.select({
|
||||
ios: { marginBottom: -8 },
|
||||
}),
|
||||
paddingBottom: 0,
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
loadingText: {
|
||||
textAlign: 'center',
|
||||
paddingBottom: 8,
|
||||
},
|
||||
card: theme.card,
|
||||
list: {
|
||||
padding: 0,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
listHeader: {
|
||||
paddingTop: 8,
|
||||
},
|
||||
});
|
||||
@@ -1,119 +1,127 @@
|
||||
import {connect} from "react-redux";
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import Config from 'react-native-config';
|
||||
import moment from "moment";
|
||||
import moment from 'moment';
|
||||
import 'moment/locale/nb';
|
||||
|
||||
import EventList from "./EventList";
|
||||
import EventList from './EventList';
|
||||
|
||||
moment.locale('nb');
|
||||
|
||||
/* Ref: https://medium.com/react-native-development/how-to-use-the-flatlist-component-react-native-basics-92c482816fe6 */
|
||||
class EventListContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
loading: false,
|
||||
events: [],
|
||||
eventsSectioned: [],
|
||||
page: 1,
|
||||
totalPages: 0,
|
||||
error: null,
|
||||
refreshing: false,
|
||||
};
|
||||
}
|
||||
|
||||
_formatDate(time) {
|
||||
const m = moment(time);
|
||||
if(moment().year() === m.year() ) {
|
||||
return m.format('dddd D. MMM')
|
||||
}
|
||||
return m.format('ll');
|
||||
}
|
||||
|
||||
_toSectionFormat(events) {
|
||||
let sectioned = {};
|
||||
events.map((event) => {
|
||||
const title = this._formatDate(event.start_time);
|
||||
if( title in sectioned ) {
|
||||
sectioned[title].push(event);
|
||||
} else {
|
||||
sectioned[title] = [event];
|
||||
}
|
||||
});
|
||||
|
||||
let sectionedList = [];
|
||||
Object.entries(sectioned).forEach(([title, section]) => {
|
||||
sectionedList.push({data: section, title: title});
|
||||
});
|
||||
|
||||
return sectionedList;
|
||||
}
|
||||
|
||||
handleRefresh = () => {
|
||||
this.setState({
|
||||
page: 1,
|
||||
refreshing: true,
|
||||
totalPages: 1,
|
||||
}, () => {
|
||||
this.fetchEvents()
|
||||
});
|
||||
this.state = {
|
||||
loading: false,
|
||||
events: [],
|
||||
eventsSectioned: [],
|
||||
page: 1,
|
||||
totalPages: 0,
|
||||
error: null,
|
||||
refreshing: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleLoadMore = () => {
|
||||
if(this.state.page >= this.state.totalPages || this.state.loading) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
page: this.state.page + 1,
|
||||
}, () => {
|
||||
this.fetchEvents();
|
||||
})
|
||||
};
|
||||
|
||||
fetchEvents() {
|
||||
const { page } = this.state;
|
||||
const url = `${Config.EVENT_API_URL}/wp-json/wp/v2/events?page=${page}&future=1`;
|
||||
this.setState({ loading: true });
|
||||
|
||||
fetch(url)
|
||||
.then(res => {
|
||||
this.setState({totalPages: parseInt(res.headers.get('x-wp-totalpages'))});
|
||||
return res.json();
|
||||
})
|
||||
.then(res => {
|
||||
this.setState({
|
||||
events: page === 1 ? res : [...this.state.events, ...res],
|
||||
eventsSectioned: page === 1 ? this._toSectionFormat(res) : this._toSectionFormat([...this.state.events, ...res]),
|
||||
error: res.error || null,
|
||||
loading: false,
|
||||
refreshing: false
|
||||
});
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
this.setState({ error, loading: false });
|
||||
});
|
||||
_formatDate(time) {
|
||||
const m = moment(time);
|
||||
if (moment().year() === m.year()) {
|
||||
return m.format('dddd D. MMM');
|
||||
}
|
||||
return m.format('ll');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
_toSectionFormat(events) {
|
||||
const sectioned = {};
|
||||
events.map((event) => {
|
||||
const title = this._formatDate(event.start_time);
|
||||
if (title in sectioned) {
|
||||
sectioned[title].push(event);
|
||||
} else {
|
||||
sectioned[title] = [event];
|
||||
}
|
||||
});
|
||||
|
||||
const sectionedList = [];
|
||||
Object.entries(sectioned).forEach(([title, section]) => {
|
||||
sectionedList.push({ data: section, title });
|
||||
});
|
||||
|
||||
return sectionedList;
|
||||
}
|
||||
|
||||
handleRefresh = () => {
|
||||
this.setState(
|
||||
{
|
||||
page: 1,
|
||||
refreshing: true,
|
||||
totalPages: 1,
|
||||
},
|
||||
() => {
|
||||
this.fetchEvents();
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
showEvent = (item) => {
|
||||
this.props.showEvent(item);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (<EventList
|
||||
showEvent={this.showEvent}
|
||||
handleRefresh={this.handleRefresh}
|
||||
handleLoadMore={this.handleLoadMore}
|
||||
{...this.state}
|
||||
/>)
|
||||
handleLoadMore = () => {
|
||||
if (this.state.page >= this.state.totalPages || this.state.loading) {
|
||||
return;
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
page: this.state.page + 1,
|
||||
},
|
||||
() => {
|
||||
this.fetchEvents();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
fetchEvents() {
|
||||
const { page } = this.state;
|
||||
const url = `${Config.EVENT_API_URL}/wp-json/wp/v2/events?page=${page}&future=1`;
|
||||
this.setState({ loading: true });
|
||||
|
||||
fetch(url)
|
||||
.then((res) => {
|
||||
this.setState({ totalPages: parseInt(res.headers.get('x-wp-totalpages')) });
|
||||
return res.json();
|
||||
})
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
events: page === 1 ? res : [...this.state.events, ...res],
|
||||
eventsSectioned:
|
||||
page === 1 ? this._toSectionFormat(res) : this._toSectionFormat([...this.state.events, ...res]),
|
||||
error: res.error || null,
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({ error, loading: false });
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchEvents();
|
||||
}
|
||||
|
||||
showEvent = (item) => {
|
||||
this.props.showEvent(item);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EventList
|
||||
showEvent={this.showEvent}
|
||||
handleRefresh={this.handleRefresh}
|
||||
handleLoadMore={this.handleLoadMore}
|
||||
{...this.state}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(EventListContainer);
|
||||
export default connect()(EventListContainer);
|
||||
+157
-146
@@ -1,162 +1,173 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Linking, StyleSheet, View, Platform} from 'react-native';
|
||||
import {Container, Header, Content, Form, Item, Input, Label, Spinner, Button, Text,
|
||||
Icon} from 'native-base';
|
||||
import React, { Component } from 'react';
|
||||
import { Linking, StyleSheet, View, Platform } from 'react-native';
|
||||
import { Container, Header, Content, Form, Item, Input, Label, Spinner, Button, Text, Icon } from 'native-base';
|
||||
import Config from 'react-native-config';
|
||||
import theme from "../../theme";
|
||||
import theme from '../../theme';
|
||||
|
||||
export default class Login extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
email: '',
|
||||
password: ''
|
||||
}
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
email: '',
|
||||
password: '',
|
||||
};
|
||||
}
|
||||
|
||||
emailInput() {
|
||||
if (this.props.loginError && this.props.loginError.username) {
|
||||
return <Item stackedLabel last error>
|
||||
<Label>E-post / Brukernavn</Label>
|
||||
<Input
|
||||
keyboardType="email-address"
|
||||
autoFocus={true}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
returnKeyType="next"
|
||||
onChangeText={this.handleEmail}
|
||||
onSubmitEditing={() => {
|
||||
this.refs.passwordInput._root.focus()
|
||||
}}
|
||||
/>
|
||||
</Item>;
|
||||
}
|
||||
|
||||
return <Item stackedLabel last>
|
||||
<Label>E-post / Brukernavn</Label>
|
||||
<Input
|
||||
keyboardType="email-address"
|
||||
autoFocus={true}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
returnKeyType="next"
|
||||
onChangeText={this.handleEmail}
|
||||
onSubmitEditing={() => {
|
||||
this.refs.passwordInput._root.focus()
|
||||
}}
|
||||
/>
|
||||
</Item>;
|
||||
}
|
||||
|
||||
passwordInput() {
|
||||
if (this.props.loginError && this.props.loginError.password) {
|
||||
return <Item stackedLabel last error>
|
||||
<Label>Passord</Label>
|
||||
<Input
|
||||
ref="passwordInput"
|
||||
secureTextEntry={true}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
onChangeText={this.handlePassword}
|
||||
onSubmitEditing={this.onLoginPress}
|
||||
/>
|
||||
</Item>
|
||||
}
|
||||
return <Item stackedLabel last>
|
||||
<Label>Passord</Label>
|
||||
<Input
|
||||
ref="passwordInput"
|
||||
secureTextEntry={true}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
onChangeText={this.handlePassword}
|
||||
onSubmitEditing={this.onLoginPress}
|
||||
/>
|
||||
emailInput() {
|
||||
if (this.props.loginError && this.props.loginError.username) {
|
||||
return (
|
||||
<Item stackedLabel last error>
|
||||
<Label>E-post / Brukernavn</Label>
|
||||
<Input
|
||||
keyboardType="email-address"
|
||||
autoFocus={true}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
returnKeyType="next"
|
||||
onChangeText={this.handleEmail}
|
||||
onSubmitEditing={() => {
|
||||
this.refs.passwordInput._root.focus();
|
||||
}}
|
||||
/>
|
||||
</Item>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Container style={styles.container}>
|
||||
<Content keyboardShouldPersistTaps='always'>
|
||||
<Form style={styles.card}>
|
||||
{this.emailInput()}
|
||||
{this.passwordInput()}
|
||||
{this.showError()}
|
||||
<Button full onPress={this.onLoginPress} style={styles.loginButton}>
|
||||
<Text>Logg inn</Text>
|
||||
</Button>
|
||||
<Button
|
||||
small
|
||||
light
|
||||
onPress={() => { Linking.openURL(Config.DUSKEN_FORGOT_PASSWORD_URL); } }
|
||||
style={styles.forgotPasswordButton}
|
||||
>
|
||||
<Text>Glemt passord?</Text>
|
||||
</Button>
|
||||
{this.showSpinner()}
|
||||
</Form>
|
||||
</Content>
|
||||
</Container>
|
||||
);
|
||||
return (
|
||||
<Item stackedLabel last>
|
||||
<Label>E-post / Brukernavn</Label>
|
||||
<Input
|
||||
keyboardType="email-address"
|
||||
autoFocus={true}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
returnKeyType="next"
|
||||
onChangeText={this.handleEmail}
|
||||
onSubmitEditing={() => {
|
||||
this.refs.passwordInput._root.focus();
|
||||
}}
|
||||
/>
|
||||
</Item>
|
||||
);
|
||||
}
|
||||
|
||||
passwordInput() {
|
||||
if (this.props.loginError && this.props.loginError.password) {
|
||||
return (
|
||||
<Item stackedLabel last error>
|
||||
<Label>Passord</Label>
|
||||
<Input
|
||||
ref="passwordInput"
|
||||
secureTextEntry={true}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
onChangeText={this.handlePassword}
|
||||
onSubmitEditing={this.onLoginPress}
|
||||
/>
|
||||
</Item>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Item stackedLabel last>
|
||||
<Label>Passord</Label>
|
||||
<Input
|
||||
ref="passwordInput"
|
||||
secureTextEntry={true}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
onChangeText={this.handlePassword}
|
||||
onSubmitEditing={this.onLoginPress}
|
||||
/>
|
||||
</Item>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Container style={styles.container}>
|
||||
<Content keyboardShouldPersistTaps="always">
|
||||
<Form style={styles.card}>
|
||||
{this.emailInput()}
|
||||
{this.passwordInput()}
|
||||
{this.showError()}
|
||||
<Button full onPress={this.onLoginPress} style={styles.loginButton}>
|
||||
<Text>Logg inn</Text>
|
||||
</Button>
|
||||
<Button
|
||||
small
|
||||
light
|
||||
onPress={() => {
|
||||
Linking.openURL(Config.DUSKEN_FORGOT_PASSWORD_URL);
|
||||
}}
|
||||
style={styles.forgotPasswordButton}
|
||||
>
|
||||
<Text>Glemt passord?</Text>
|
||||
</Button>
|
||||
{this.showSpinner()}
|
||||
</Form>
|
||||
</Content>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
showError = () => {
|
||||
if (this.props.loginError) {
|
||||
const err = this.props.loginError.non_field_errors;
|
||||
const errorFormatted = err ? err[0] : 'Feil brukernavn eller passord, prøv igjen...';
|
||||
// TODO: Format these and highlight error fields
|
||||
return (
|
||||
<View style={styles.errorBox}>
|
||||
<Text style={styles.errorMessage}>{errorFormatted}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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}><Text style={styles.errorMessage}>{errorFormatted}</Text></View>
|
||||
}
|
||||
else {
|
||||
return <View style={styles.errorBox}/>
|
||||
}
|
||||
};
|
||||
return <View style={styles.errorBox} />;
|
||||
};
|
||||
|
||||
showSpinner = () => {
|
||||
if (this.props.isLoggingIn) {
|
||||
return <Spinner color="#f58220"/>
|
||||
}
|
||||
};
|
||||
showSpinner = () => {
|
||||
if (this.props.isLoggingIn) {
|
||||
return <Spinner color="#f58220" />;
|
||||
}
|
||||
};
|
||||
|
||||
handleEmail = (text) => {
|
||||
this.setState({email: text})
|
||||
};
|
||||
handleEmail = (text) => {
|
||||
this.setState({ email: text });
|
||||
};
|
||||
|
||||
handlePassword = (text) => {
|
||||
this.setState({password: text})
|
||||
};
|
||||
|
||||
onLoginPress = () => {
|
||||
this.props.requestLogin(this.state.email, this.state.password)
|
||||
};
|
||||
handlePassword = (text) => {
|
||||
this.setState({ password: text });
|
||||
};
|
||||
|
||||
onLoginPress = () => {
|
||||
this.props.requestLogin(this.state.email, this.state.password);
|
||||
};
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
input: {
|
||||
width: 320,
|
||||
fontSize: 20,
|
||||
height: 52,
|
||||
},
|
||||
loginButton: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
forgotPasswordButton: {
|
||||
alignSelf: 'center',
|
||||
marginTop: 32,
|
||||
marginBottom: 16,
|
||||
},
|
||||
errorBox: {
|
||||
marginVertical: 10,
|
||||
},
|
||||
errorMessage: {
|
||||
textAlign: 'center',
|
||||
color: 'red',
|
||||
},
|
||||
card: theme.card,
|
||||
});
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
input: {
|
||||
width: 320,
|
||||
fontSize: 20,
|
||||
height: 52,
|
||||
},
|
||||
loginButton: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
forgotPasswordButton: {
|
||||
alignSelf: 'center',
|
||||
marginTop: 32,
|
||||
marginBottom: 16,
|
||||
},
|
||||
errorBox: {
|
||||
marginVertical: 10,
|
||||
},
|
||||
errorMessage: {
|
||||
textAlign: 'center',
|
||||
color: 'red',
|
||||
},
|
||||
card: theme.card,
|
||||
});
|
||||
@@ -1,27 +1,27 @@
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from "react-redux";
|
||||
import Login from "./Login";
|
||||
import {requestLogin} from "../../api";
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Login from './Login';
|
||||
import { requestLogin } from '../../api';
|
||||
|
||||
class LoginContainer extends Component {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.isAuthenticated !== nextProps.isAuthenticated) {
|
||||
if (nextProps.isAuthenticated) {
|
||||
this.props.onLogin();
|
||||
}
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.isAuthenticated !== nextProps.isAuthenticated) {
|
||||
if (nextProps.isAuthenticated) {
|
||||
this.props.onLogin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Login {...this.props} />
|
||||
}
|
||||
render() {
|
||||
return <Login {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(store) => ({
|
||||
isAuthenticated: store.isAuthenticated,
|
||||
isLoggingIn: store.isLoggingIn,
|
||||
loginError: store.loginError,
|
||||
}),
|
||||
{requestLogin}
|
||||
(store) => ({
|
||||
isAuthenticated: store.isAuthenticated,
|
||||
isLoggingIn: store.isLoggingIn,
|
||||
loginError: store.loginError,
|
||||
}),
|
||||
{ requestLogin }
|
||||
)(LoginContainer);
|
||||
@@ -1,80 +1,82 @@
|
||||
import React, {Component} from 'react';
|
||||
import {ScrollView, Text, View, StyleSheet} from "react-native";
|
||||
import {Button, Text as NBText, Spinner} from "native-base";
|
||||
|
||||
import theme from "../../theme";
|
||||
import React, { Component } from 'react';
|
||||
import { ScrollView, Text, View, StyleSheet } from 'react-native';
|
||||
import { Button, Text as NBText, Spinner } from 'native-base';
|
||||
|
||||
import theme from '../../theme';
|
||||
|
||||
export default class Membership extends Component {
|
||||
render() {
|
||||
if (this.props.isFetchingUserData) {
|
||||
return (
|
||||
<View style={styles.card}>
|
||||
<Spinner color="#f58220"/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.headerTitle}>Hei 👋</Text>
|
||||
<Text style={styles.paragraph}>Her kan du bli medlem i Det Norske Studentersamfund - Chateau Neuf og ha medlemsbeviset ditt på telefonen</Text>
|
||||
<View style={styles.button}>
|
||||
<Button onPress={this.props.onRegisterPress} full><NBText>Bli medlem</NBText></Button>
|
||||
</View>
|
||||
<Text style={styles.header}>Hva får jeg?</Text>
|
||||
<Text style={[styles.paragraph, {textAlign: 'left', paddingLeft: 4}]}>
|
||||
- Minst 25% rabatt på de fleste arrangement{"\n"}
|
||||
- Gratis inngang på enkelte arrangement{"\n"}
|
||||
- Store rabatter i barene{"\n"}
|
||||
- Egne medlemsarrangement og -tilbud{"\n"}
|
||||
- Mulighet til å delta i landets eldste studentdemokrati</Text>
|
||||
<Text style={styles.em}>Allerede medlem?</Text>
|
||||
<View style={styles.button}>
|
||||
<Button onPress={this.props.onLoginPress} full><NBText>Logg inn</NBText></Button>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
)
|
||||
};
|
||||
render() {
|
||||
if (this.props.isFetchingUserData) {
|
||||
return (
|
||||
<View style={styles.card}>
|
||||
<Spinner color="#f58220" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.headerTitle}>Hei 👋</Text>
|
||||
<Text style={styles.paragraph}>
|
||||
Her kan du bli medlem i Det Norske Studentersamfund - Chateau Neuf og ha medlemsbeviset ditt på telefonen
|
||||
</Text>
|
||||
<View style={styles.button}>
|
||||
<Button onPress={this.props.onRegisterPress} full>
|
||||
<NBText>Bli medlem</NBText>
|
||||
</Button>
|
||||
</View>
|
||||
<Text style={styles.header}>Hva får jeg?</Text>
|
||||
<Text style={[styles.paragraph, { textAlign: 'left', paddingLeft: 4 }]}>
|
||||
- Minst 25% rabatt på de fleste arrangement{'\n'}- Gratis inngang på enkelte arrangement{'\n'}- Store
|
||||
rabatter i barene{'\n'}- Egne medlemsarrangement og -tilbud{'\n'}- Mulighet til å delta i landets eldste
|
||||
studentdemokrati
|
||||
</Text>
|
||||
<Text style={styles.em}>Allerede medlem?</Text>
|
||||
<View style={styles.button}>
|
||||
<Button onPress={this.props.onLoginPress} full>
|
||||
<NBText>Logg inn</NBText>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: theme.card,
|
||||
paragraph: {
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
color: '#000',
|
||||
paddingBottom: 10,
|
||||
lineHeight: 25,
|
||||
},
|
||||
button: {
|
||||
marginTop: 6,
|
||||
marginBottom: 16,
|
||||
},
|
||||
em: {
|
||||
fontSize: 16,
|
||||
color: '#000',
|
||||
fontStyle: 'italic',
|
||||
textAlign: 'center',
|
||||
marginVertical: 10,
|
||||
},
|
||||
headerTitle: {
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 20,
|
||||
color: '#000',
|
||||
paddingVertical: 10,
|
||||
lineHeight: 25,
|
||||
},
|
||||
header: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
color: '#000',
|
||||
lineHeight: 25,
|
||||
paddingLeft: 2
|
||||
},
|
||||
});
|
||||
card: theme.card,
|
||||
paragraph: {
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
color: '#000',
|
||||
paddingBottom: 10,
|
||||
lineHeight: 25,
|
||||
},
|
||||
button: {
|
||||
marginTop: 6,
|
||||
marginBottom: 16,
|
||||
},
|
||||
em: {
|
||||
fontSize: 16,
|
||||
color: '#000',
|
||||
fontStyle: 'italic',
|
||||
textAlign: 'center',
|
||||
marginVertical: 10,
|
||||
},
|
||||
headerTitle: {
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 20,
|
||||
color: '#000',
|
||||
paddingVertical: 10,
|
||||
lineHeight: 25,
|
||||
},
|
||||
header: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
color: '#000',
|
||||
lineHeight: 25,
|
||||
paddingLeft: 2,
|
||||
},
|
||||
});
|
||||
@@ -1,86 +1,89 @@
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from "react-redux";
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Membership from "./Membership";
|
||||
import ProofContainer from "./ProofContainer";
|
||||
import { logout } from "../../actions";
|
||||
import {requestUserData} from "../../api";
|
||||
import Membership from './Membership';
|
||||
import ProofContainer from './ProofContainer';
|
||||
import { logout } from '../../actions';
|
||||
import { requestUserData } from '../../api';
|
||||
|
||||
class MembershipContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isFetchingUserData: props.isFetchingUserData,
|
||||
userToken: props.userToken,
|
||||
lastOrder: props.lastOrder,
|
||||
this.state = {
|
||||
isFetchingUserData: props.isFetchingUserData,
|
||||
userToken: props.userToken,
|
||||
lastOrder: props.lastOrder,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.state.userToken !== nextProps.userToken) {
|
||||
this.setState({ userToken: nextProps.userToken }, () => {
|
||||
if (nextProps.userToken) {
|
||||
this.fetchUser();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(this.state.userToken !== nextProps.userToken) {
|
||||
this.setState({userToken: nextProps.userToken}, () => {
|
||||
if(nextProps.userToken) {
|
||||
this.fetchUser();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
if(this.state.lastOrder !== nextProps.lastOrder) {
|
||||
this.setState({lastOrder: nextProps.lastOrder}, () => {
|
||||
if(nextProps.userToken) {
|
||||
this.fetchUser();
|
||||
}
|
||||
});
|
||||
if (this.state.lastOrder !== nextProps.lastOrder) {
|
||||
this.setState({ lastOrder: nextProps.lastOrder }, () => {
|
||||
if (nextProps.userToken) {
|
||||
this.fetchUser();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fetchUser = () => {
|
||||
this.props.requestUserData(this.state.userToken)
|
||||
};
|
||||
fetchUser = () => {
|
||||
this.props.requestUserData(this.state.userToken);
|
||||
};
|
||||
|
||||
onLoginPress = () => {
|
||||
this.props.onLoginPress();
|
||||
};
|
||||
onLoginPress = () => {
|
||||
this.props.onLoginPress();
|
||||
};
|
||||
|
||||
onRegisterPress = () => {
|
||||
this.props.onRegisterPress();
|
||||
};
|
||||
onRegisterPress = () => {
|
||||
this.props.onRegisterPress();
|
||||
};
|
||||
|
||||
onLogoutPress = () => {
|
||||
this.props.logout();
|
||||
this.props.onLogoutPress();
|
||||
};
|
||||
onLogoutPress = () => {
|
||||
this.props.logout();
|
||||
this.props.onLogoutPress();
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.props.user) {
|
||||
return <ProofContainer
|
||||
user={this.props.user}
|
||||
isAuthenticated={this.props.isAuthenticated}
|
||||
userToken={this.state.userToken}
|
||||
fetchUser={this.fetchUser}
|
||||
onLogoutPress={this.onLogoutPress}
|
||||
/>;
|
||||
}
|
||||
return <Membership
|
||||
isFetchingUserData={this.props.isFetchingUserData}
|
||||
onLoginPress={this.onLoginPress}
|
||||
onRegisterPress={this.onRegisterPress}
|
||||
/>;
|
||||
render() {
|
||||
if (this.props.user) {
|
||||
return (
|
||||
<ProofContainer
|
||||
user={this.props.user}
|
||||
isAuthenticated={this.props.isAuthenticated}
|
||||
userToken={this.state.userToken}
|
||||
fetchUser={this.fetchUser}
|
||||
onLogoutPress={this.onLogoutPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Membership
|
||||
isFetchingUserData={this.props.isFetchingUserData}
|
||||
onLoginPress={this.onLoginPress}
|
||||
onRegisterPress={this.onRegisterPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function storeToProps(store) {
|
||||
return {
|
||||
isAuthenticated: store.isAuthenticated,
|
||||
isFetchingUserData: store.isFetchingUserData,
|
||||
userToken: store.userToken,
|
||||
user: store.user,
|
||||
lastOrder: store.lastOrder
|
||||
};
|
||||
return {
|
||||
isAuthenticated: store.isAuthenticated,
|
||||
isFetchingUserData: store.isFetchingUserData,
|
||||
userToken: store.userToken,
|
||||
user: store.user,
|
||||
lastOrder: store.lastOrder,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
storeToProps,
|
||||
{ requestUserData, logout }
|
||||
storeToProps,
|
||||
{ requestUserData, logout }
|
||||
)(MembershipContainer);
|
||||
+236
-223
@@ -1,241 +1,254 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Image, ScrollView, StyleSheet, Text, TouchableOpacity, View} from "react-native";
|
||||
import {Button, Text as NBText, Spinner} from 'native-base';
|
||||
import React, { Component } from 'react';
|
||||
import { Image, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { Button, Text as NBText, Spinner } from 'native-base';
|
||||
import Confetti from 'react-native-confetti';
|
||||
|
||||
import theme from "../../theme";
|
||||
import theme from '../../theme';
|
||||
|
||||
export default class Proof extends Component {
|
||||
CONFETTI_TIMEOUT = 15000;
|
||||
CONFETTI_TIMEOUT = 15000;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
stopConfetti: false,
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
stopConfetti: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
if (this._confettiView && this.state.stopConfetti) {
|
||||
this._confettiView.stopConfetti();
|
||||
this.setState({stopConfetti: false});
|
||||
}
|
||||
componentDidUpdate() {
|
||||
if (this._confettiView && this.state.stopConfetti) {
|
||||
this._confettiView.stopConfetti();
|
||||
this.setState({ stopConfetti: false });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._confettiView) {
|
||||
this._confettiView.stopConfetti();
|
||||
}
|
||||
}
|
||||
|
||||
onMembershipPress = () => {
|
||||
this.setState({ showConfetti: true }, () => {
|
||||
if (this._confettiView) {
|
||||
this._confettiView.startConfetti();
|
||||
setTimeout(
|
||||
function() {
|
||||
this.setState({ stopConfetti: true });
|
||||
}.bind(this),
|
||||
this.CONFETTI_TIMEOUT
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
membershipValidTo() {
|
||||
if (!this.props.user.last_membership) {
|
||||
return;
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this._confettiView) {
|
||||
this._confettiView.stopConfetti();
|
||||
}
|
||||
let validTo = this.props.user.last_membership.end_date;
|
||||
if (this.props.user.last_membership.membership_type === 'lifelong') {
|
||||
validTo = 'Livsvarig';
|
||||
}
|
||||
|
||||
onMembershipPress = () => {
|
||||
this.setState({showConfetti: true}, () => {
|
||||
if(this._confettiView) {
|
||||
this._confettiView.startConfetti();
|
||||
setTimeout(function() {
|
||||
this.setState({stopConfetti: true});
|
||||
}.bind(this), this.CONFETTI_TIMEOUT);
|
||||
}
|
||||
})
|
||||
};
|
||||
return (
|
||||
<View style={{ alignItems: 'center', flex: 1 }}>
|
||||
<Text>Gyldig til: </Text>
|
||||
<Text style={styles.validToValue}>{validTo}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
membershipValidTo() {
|
||||
if( !this.props.user.last_membership) {
|
||||
return;
|
||||
}
|
||||
membershipStatus() {
|
||||
if (!this.props.user.is_member) {
|
||||
if (!this.props.user.last_membership) {
|
||||
return;
|
||||
}
|
||||
|
||||
let validTo = this.props.user.last_membership.end_date;
|
||||
if (this.props.user.last_membership.membership_type === 'lifelong') {
|
||||
validTo = 'Livsvarig'
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{alignItems: 'center', flex: 1}}>
|
||||
<Text>Gyldig til: </Text>
|
||||
<Text style={styles.validToValue}>{validTo}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
membershipStatus() {
|
||||
if (!this.props.user.is_member) {
|
||||
if ( !this.props.user.last_membership ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.expired}>
|
||||
<Text style={styles.expiredText}>Utløpt</Text>
|
||||
</View>)
|
||||
}
|
||||
|
||||
const statusText = this.props.user.is_volunteer ? '❤️ AKTIV ❤️ ' : 'MEDLEM';
|
||||
|
||||
return (
|
||||
<View style={styles.membershipStatus}>
|
||||
<TouchableOpacity onPress={this.onMembershipPress} style={styles.valid}>
|
||||
<Text style={styles.validText}>{statusText}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>);
|
||||
}
|
||||
|
||||
purchaseButton() {
|
||||
if (this.props.user.is_member) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
{this.props.isChargingMembership && <Spinner color="#f58220"/>}
|
||||
<Button
|
||||
onPress={this.props.onChargePress}
|
||||
full
|
||||
disabled={this.props.isChargingMembership || this.props.isLoadingMembershipType}
|
||||
style={styles.purchaseButton}
|
||||
>
|
||||
<NBText>{`Kjøp medlemskap (${this.props.membershipPrice} NOK)`}</NBText>
|
||||
</Button>
|
||||
<View style={styles.purchaseText}>
|
||||
<Text style={styles.purchaseTextInner}>Medlemskapet er gyldig i ett år</Text>
|
||||
</View>
|
||||
</View>)
|
||||
}
|
||||
|
||||
confetti() {
|
||||
if (this.props.user.is_member) {
|
||||
// Confetti!!!!
|
||||
return <Confetti ref={(node) => this._confettiView = node} confettiCount={Number.MAX_SAFE_INTEGER} />
|
||||
}
|
||||
}
|
||||
|
||||
logo() {
|
||||
if (!this.props.user.is_member) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: URL to config
|
||||
return (
|
||||
<View style={{marginTop: 8}}>
|
||||
<Image
|
||||
style={{width: null, height: 100}}
|
||||
source={{uri: 'https://galtinn.neuf.no/static/dist/images/logo.png'}}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</View>)
|
||||
}
|
||||
|
||||
chargeError() {
|
||||
if (!this.props.chargeError) {
|
||||
return;
|
||||
}
|
||||
|
||||
return <View><Text style={styles.errorMessage}>{this.props.chargeError}</Text></View>
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
{this.confetti()}
|
||||
<ScrollView>
|
||||
<View style={[styles.card, {paddingVertical: 16}]}>
|
||||
{this.membershipName()}
|
||||
{this.logo()}
|
||||
{this.membershipStatus()}
|
||||
{this.chargeError()}
|
||||
{this.purchaseButton()}
|
||||
{this.membershipValidTo()}
|
||||
</View>
|
||||
<View>
|
||||
<Button onPress={this.props.onLogoutPress} style={styles.logoutButton} small>
|
||||
<NBText>Logg ut</NBText>
|
||||
</Button>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
)
|
||||
};
|
||||
|
||||
membershipName() {
|
||||
let name = `${this.props.user.first_name} ${this.props.user.last_name}`;
|
||||
if(name === ' ') {
|
||||
name = this.props.user.email;
|
||||
}
|
||||
return <View>
|
||||
<Text style={styles.nameText}>{name}</Text>
|
||||
return (
|
||||
<View style={styles.expired}>
|
||||
<Text style={styles.expiredText}>Utløpt</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const statusText = this.props.user.is_volunteer ? '❤️ AKTIV ❤️ ' : 'MEDLEM';
|
||||
|
||||
return (
|
||||
<View style={styles.membershipStatus}>
|
||||
<TouchableOpacity onPress={this.onMembershipPress} style={styles.valid}>
|
||||
<Text style={styles.validText}>{statusText}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
purchaseButton() {
|
||||
if (this.props.user.is_member) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
{this.props.isChargingMembership && <Spinner color="#f58220" />}
|
||||
<Button
|
||||
onPress={this.props.onChargePress}
|
||||
full
|
||||
disabled={this.props.isChargingMembership || this.props.isLoadingMembershipType}
|
||||
style={styles.purchaseButton}
|
||||
>
|
||||
<NBText>{`Kjøp medlemskap (${this.props.membershipPrice} NOK)`}</NBText>
|
||||
</Button>
|
||||
<View style={styles.purchaseText}>
|
||||
<Text style={styles.purchaseTextInner}>Medlemskapet er gyldig i ett år</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
confetti() {
|
||||
if (this.props.user.is_member) {
|
||||
// Confetti!!!!
|
||||
return <Confetti ref={(node) => (this._confettiView = node)} confettiCount={Number.MAX_SAFE_INTEGER} />;
|
||||
}
|
||||
}
|
||||
|
||||
logo() {
|
||||
if (!this.props.user.is_member) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: URL to config
|
||||
return (
|
||||
<View style={{ marginTop: 8 }}>
|
||||
<Image
|
||||
style={{ width: null, height: 100 }}
|
||||
source={{ uri: 'https://galtinn.neuf.no/static/dist/images/logo.png' }}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
chargeError() {
|
||||
if (!this.props.chargeError) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text style={styles.errorMessage}>{this.props.chargeError}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
{this.confetti()}
|
||||
<ScrollView>
|
||||
<View style={[styles.card, { paddingVertical: 16 }]}>
|
||||
{this.membershipName()}
|
||||
{this.logo()}
|
||||
{this.membershipStatus()}
|
||||
{this.chargeError()}
|
||||
{this.purchaseButton()}
|
||||
{this.membershipValidTo()}
|
||||
</View>
|
||||
<View>
|
||||
<Button onPress={this.props.onLogoutPress} style={styles.logoutButton} small>
|
||||
<NBText>Logg ut</NBText>
|
||||
</Button>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
membershipName() {
|
||||
let name = `${this.props.user.first_name} ${this.props.user.last_name}`;
|
||||
if (name === ' ') {
|
||||
name = this.props.user.email;
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<Text style={styles.nameText}>{name}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: theme.card,
|
||||
membershipStatus: {
|
||||
marginVertical: 16,
|
||||
},
|
||||
nameText: {
|
||||
flex: 1,
|
||||
textAlign: 'center',
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
valid: {
|
||||
backgroundColor: '#5cb85c',
|
||||
flex: 1,
|
||||
marginHorizontal: -16,
|
||||
padding: 16
|
||||
},
|
||||
validText: {
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
fontSize: 30,
|
||||
fontWeight: "800"
|
||||
},
|
||||
expired: {
|
||||
backgroundColor: '#d9534f',
|
||||
flex: 1,
|
||||
marginHorizontal: -16,
|
||||
padding: 16,
|
||||
marginVertical: 16,
|
||||
},
|
||||
expiredText: {
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
fontSize: 24
|
||||
},
|
||||
notMember: {
|
||||
backgroundColor: '#62B1F6',
|
||||
flex: 1,
|
||||
marginHorizontal: -16,
|
||||
padding: 16
|
||||
},
|
||||
notMemberText: {
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
fontSize: 24
|
||||
},
|
||||
validToValue: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: 18,
|
||||
},
|
||||
purchaseButton: {
|
||||
marginTop: 16,
|
||||
},
|
||||
logoutButton: {
|
||||
alignSelf: 'center',
|
||||
marginVertical: 20,
|
||||
},
|
||||
purchaseText: {
|
||||
alignItems: 'center'
|
||||
},
|
||||
purchaseTextInner: {
|
||||
fontStyle: 'italic',
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
paddingTop: 10,
|
||||
paddingBottom: 8
|
||||
},
|
||||
errorMessage: {
|
||||
paddingTop: 10,
|
||||
textAlign: 'center',
|
||||
color: theme.colors.danger
|
||||
}
|
||||
});
|
||||
card: theme.card,
|
||||
membershipStatus: {
|
||||
marginVertical: 16,
|
||||
},
|
||||
nameText: {
|
||||
flex: 1,
|
||||
textAlign: 'center',
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
valid: {
|
||||
backgroundColor: '#5cb85c',
|
||||
flex: 1,
|
||||
marginHorizontal: -16,
|
||||
padding: 16,
|
||||
},
|
||||
validText: {
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
fontSize: 30,
|
||||
fontWeight: '800',
|
||||
},
|
||||
expired: {
|
||||
backgroundColor: '#d9534f',
|
||||
flex: 1,
|
||||
marginHorizontal: -16,
|
||||
padding: 16,
|
||||
marginVertical: 16,
|
||||
},
|
||||
expiredText: {
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
fontSize: 24,
|
||||
},
|
||||
notMember: {
|
||||
backgroundColor: '#62B1F6',
|
||||
flex: 1,
|
||||
marginHorizontal: -16,
|
||||
padding: 16,
|
||||
},
|
||||
notMemberText: {
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
fontSize: 24,
|
||||
},
|
||||
validToValue: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: 18,
|
||||
},
|
||||
purchaseButton: {
|
||||
marginTop: 16,
|
||||
},
|
||||
logoutButton: {
|
||||
alignSelf: 'center',
|
||||
marginVertical: 20,
|
||||
},
|
||||
purchaseText: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
purchaseTextInner: {
|
||||
fontStyle: 'italic',
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
paddingTop: 10,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
errorMessage: {
|
||||
paddingTop: 10,
|
||||
textAlign: 'center',
|
||||
color: theme.colors.danger,
|
||||
},
|
||||
});
|
||||
@@ -1,105 +1,90 @@
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from "react-redux";
|
||||
import stripe from 'tipsi-stripe'
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import stripe from 'tipsi-stripe';
|
||||
import Config from 'react-native-config';
|
||||
import { graphql } from 'react-apollo';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import Proof from "./Proof";
|
||||
import {requestMembershipCharge} from "../../api";
|
||||
|
||||
import Proof from './Proof';
|
||||
import { requestMembershipCharge } from '../../api';
|
||||
|
||||
stripe.setOptions({
|
||||
publishableKey: Config.STRIPE_PUBLISHABLE_KEY || '',
|
||||
publishableKey: Config.STRIPE_PUBLISHABLE_KEY || '',
|
||||
});
|
||||
|
||||
|
||||
class ProofContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
refreshing: false,
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
refreshing: false,
|
||||
};
|
||||
}
|
||||
|
||||
getPrice(membershipTypes) {
|
||||
if (!membershipTypes) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
getPrice(membershipTypes) {
|
||||
if (!membershipTypes) {
|
||||
return 0;
|
||||
}
|
||||
return membershipTypes[0].price / 100;
|
||||
}
|
||||
|
||||
return membershipTypes[0].price / 100;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Proof
|
||||
{...this.state}
|
||||
user={this.props.user}
|
||||
isChargingMembership={this.props.isChargingMembership}
|
||||
isLoadingMembershipType={this.props.data.loading}
|
||||
chargeError={this.props.chargeError ? this.props.chargeError : ''}
|
||||
onChargePress={this.openStripe}
|
||||
onLogoutPress={this.props.onLogoutPress}
|
||||
membershipPrice={this.getPrice(this.props.data.membershipTypes)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Proof
|
||||
{...this.state}
|
||||
user={this.props.user}
|
||||
isChargingMembership={this.props.isChargingMembership}
|
||||
isLoadingMembershipType={this.props.data.loading}
|
||||
chargeError={this.props.chargeError ? this.props.chargeError : ''}
|
||||
onChargePress={this.openStripe}
|
||||
onLogoutPress={this.props.onLogoutPress}
|
||||
membershipPrice={this.getPrice(this.props.data.membershipTypes)}
|
||||
/>
|
||||
}
|
||||
|
||||
openStripe = () => {
|
||||
const options = {
|
||||
prefilledInformation: {
|
||||
email: this.props.user.email,
|
||||
},
|
||||
smsAutofillDisabled: true,
|
||||
};
|
||||
|
||||
const membershipTypeSlug = this.props.data.membershipTypes ? this.props.data.membershipTypes[0].slug : 'standard';
|
||||
|
||||
stripe.paymentRequestWithCardForm(options).then(
|
||||
(token) => {
|
||||
// returns a token.tokenId which should be saved
|
||||
this.props.requestMembershipCharge(
|
||||
this.props.userToken,
|
||||
{id: token.tokenId, email: this.props.user.email},
|
||||
membershipTypeSlug);
|
||||
},
|
||||
(error) => { /* FIXME: Happens only if card dialog is canceled? */ }
|
||||
);
|
||||
openStripe = () => {
|
||||
const options = {
|
||||
prefilledInformation: {
|
||||
email: this.props.user.email,
|
||||
},
|
||||
smsAutofillDisabled: true,
|
||||
};
|
||||
|
||||
// fetchUser = () => {
|
||||
// this.props.fetchUser().then(() => {
|
||||
// this.setState({refreshing: false});
|
||||
// });
|
||||
// };
|
||||
const membershipTypeSlug = this.props.data.membershipTypes ? this.props.data.membershipTypes[0].slug : 'standard';
|
||||
|
||||
// _onRefresh() {
|
||||
// console.log('onRefresh')
|
||||
// this.setState({refreshing: true});
|
||||
// this.fetchUser()
|
||||
// }
|
||||
|
||||
// refreshControl() {
|
||||
// return (
|
||||
// <RefreshControl
|
||||
// refreshing={this.state.refreshing}
|
||||
// onRefresh={this._onRefresh.bind(this)}
|
||||
// />)
|
||||
// }
|
||||
stripe.paymentRequestWithCardForm(options).then(
|
||||
(token) => {
|
||||
// returns a token.tokenId which should be saved
|
||||
this.props.requestMembershipCharge(
|
||||
this.props.userToken,
|
||||
{ id: token.tokenId, email: this.props.user.email },
|
||||
membershipTypeSlug
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
/* FIXME: Happens only if card dialog is canceled? */
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const PROOF_QUERY = gql`{
|
||||
const PROOF_QUERY = gql`
|
||||
{
|
||||
membershipTypes(isDefault: true) {
|
||||
slug
|
||||
price
|
||||
slug
|
||||
price
|
||||
}
|
||||
}`;
|
||||
}
|
||||
`;
|
||||
|
||||
const proofContainerWithState = connect(
|
||||
(store) => ({
|
||||
userToken: store.userToken,
|
||||
isChargingMembership: store.isChargingMembership,
|
||||
chargeError: store.chargeError
|
||||
}),
|
||||
{requestMembershipCharge}
|
||||
(store) => ({
|
||||
userToken: store.userToken,
|
||||
isChargingMembership: store.isChargingMembership,
|
||||
chargeError: store.chargeError,
|
||||
}),
|
||||
{ requestMembershipCharge }
|
||||
)(ProofContainer);
|
||||
|
||||
export default graphql(PROOF_QUERY)(proofContainerWithState);
|
||||
@@ -1,264 +1,293 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Container, Header, Content, Form, Item, Input, Label, Spinner, Button, Text, Icon, Toast} from 'native-base';
|
||||
import {StyleSheet, View} from "react-native";
|
||||
import React, { Component } from 'react';
|
||||
import { Container, Header, Content, Form, Item, Input, Label, Spinner, Button, Text, Icon, Toast } from 'native-base';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import EmailValidator from 'email-validator';
|
||||
|
||||
import theme from "../../theme";
|
||||
import theme from '../../theme';
|
||||
|
||||
export default class UserRegister extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
phoneNumber: '',
|
||||
password: '',
|
||||
errors: {},
|
||||
touched: new Set()
|
||||
}
|
||||
this.state = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
phoneNumber: '',
|
||||
password: '',
|
||||
errors: {},
|
||||
touched: new Set(),
|
||||
};
|
||||
}
|
||||
|
||||
validateForm(triggeredBy) {
|
||||
const fieldNames = ['firstName', 'lastName', 'email', 'phoneNumber', 'password'];
|
||||
validateForm(triggeredBy) {
|
||||
const fieldNames = ['firstName', 'lastName', 'email', 'phoneNumber', 'password'];
|
||||
|
||||
let errors = Object.assign({}, this.state.errors);
|
||||
let touched = new Set(this.state.touched).add(triggeredBy);
|
||||
const errors = Object.assign({}, this.state.errors);
|
||||
const touched = new Set(this.state.touched).add(triggeredBy);
|
||||
|
||||
/* No empty fields */
|
||||
for(let key of fieldNames) {
|
||||
const value = this.state[key];
|
||||
if(value === '') {
|
||||
errors[key] = 'kan ikke være tomt';
|
||||
} else if(key === 'email' && !EmailValidator.validate(value)) {
|
||||
errors[key] = 'er ikke en gyldig e-post';
|
||||
} else {
|
||||
delete errors[key];
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({errors: errors, touched});
|
||||
};
|
||||
|
||||
fieldHasError(field) {
|
||||
return this.state.touched.has(field) && field in this.state.errors;
|
||||
/* No empty fields */
|
||||
for (const key of fieldNames) {
|
||||
const value = this.state[key];
|
||||
if (value === '') {
|
||||
errors[key] = 'kan ikke være tomt';
|
||||
} else if (key === 'email' && !EmailValidator.validate(value)) {
|
||||
errors[key] = 'er ikke en gyldig e-post';
|
||||
} else {
|
||||
delete errors[key];
|
||||
}
|
||||
}
|
||||
|
||||
canSubmitForm() {
|
||||
return Object.keys(this.state.errors).length === 0;
|
||||
}
|
||||
this.setState({ errors, touched });
|
||||
}
|
||||
|
||||
firstNameInput() {
|
||||
return <Item error={this.fieldHasError('firstName')}>
|
||||
<Label>Fornavn</Label>
|
||||
<Input
|
||||
autoFocus={true}
|
||||
returnKeyType="next"
|
||||
onChangeText={(firstName) => {
|
||||
this.setState({firstName}, () => { this.validateForm('firstName'); });
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
this.refs.lastNameInput._root.focus()
|
||||
}}
|
||||
value={this.state.firstName}
|
||||
/>
|
||||
{this.fieldHasError('firstName') && <Icon name='close-circle' />}
|
||||
</Item>;
|
||||
}
|
||||
fieldHasError(field) {
|
||||
return this.state.touched.has(field) && field in this.state.errors;
|
||||
}
|
||||
|
||||
lastNameInput() {
|
||||
return <Item error={this.fieldHasError('lastName')}>
|
||||
<Label>Etternavn</Label>
|
||||
<Input
|
||||
ref="lastNameInput"
|
||||
returnKeyType="next"
|
||||
onChangeText={(lastName) => {
|
||||
this.setState({lastName}, () => { this.validateForm('lastName'); });
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
this.refs.emailInput._root.focus()
|
||||
}}
|
||||
value={this.state.lastName}
|
||||
/>
|
||||
{this.fieldHasError('lastName') && <Icon name='close-circle' />}
|
||||
</Item>;
|
||||
}
|
||||
canSubmitForm() {
|
||||
return Object.keys(this.state.errors).length === 0;
|
||||
}
|
||||
|
||||
emailInput() {
|
||||
return <Item error={this.fieldHasError('email')}>
|
||||
<Label>E-post</Label>
|
||||
<Input
|
||||
ref="emailInput"
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
returnKeyType="next"
|
||||
onChangeText={(email) => {
|
||||
this.setState({email}, () => { this.validateForm('email'); });
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
this.refs.phoneInput._root.focus()
|
||||
}}
|
||||
value={this.state.email}
|
||||
/>
|
||||
{this.fieldHasError('email') && <Icon name='close-circle' />}
|
||||
</Item>;
|
||||
}
|
||||
|
||||
phoneNumberInput() {
|
||||
return <Item error={this.fieldHasError('phoneNumber')}>
|
||||
<Label>Mobilnummer</Label>
|
||||
<Input
|
||||
ref="phoneInput"
|
||||
returnKeyType="next"
|
||||
keyboardType="phone-pad"
|
||||
onChangeText={(phoneNumber) => {
|
||||
this.setState({phoneNumber}, () => { this.validateForm('phoneNumber'); });
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
this.refs.passwordInput._root.focus()
|
||||
}}
|
||||
value={this.state.phoneNumber}
|
||||
/>
|
||||
{this.fieldHasError('phoneNumber') && <Icon name='close-circle' />}
|
||||
</Item>;
|
||||
}
|
||||
|
||||
passwordInput() {
|
||||
return <Item error={this.fieldHasError('password')}>
|
||||
<Label>Passord</Label>
|
||||
<Input
|
||||
ref="passwordInput"
|
||||
secureTextEntry={true}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
onChangeText={(password) => {
|
||||
this.setState({password}, () => { this.validateForm('password'); });
|
||||
}}
|
||||
onSubmitEditing={this.onRegisterPress}
|
||||
value={this.state.password}
|
||||
/>
|
||||
{this.fieldHasError('password') && <Icon name='close-circle' />}
|
||||
</Item>
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(this.props.registerError !== nextProps.registerError) {
|
||||
this.setState({'errors': nextProps.registerError})
|
||||
}
|
||||
}
|
||||
|
||||
showNonFieldError = () => {
|
||||
const errors = this.props.registerError;
|
||||
if (Object.keys(errors).length !== 0 && 'non_field_errors' in errors) {
|
||||
const err = errors.non_field_errors;
|
||||
let errorFormatted = err ? err[0] : 'Kunne ikke registrere bruker, prøv igjen...';
|
||||
return <View style={styles.errorBox}><Text style={styles.errorMessage}>{errorFormatted}</Text></View>
|
||||
|
||||
} else if (Object.keys(errors).length !== 0 && 'detail' in errors) {
|
||||
const err = errors.detail;
|
||||
let errorFormatted = err ? err : 'Kunne ikke registrere bruker, prøv igjen...';
|
||||
return <View style={styles.errorBox}><Text style={styles.errorMessage}>{errorFormatted}</Text></View>
|
||||
}
|
||||
|
||||
return <View style={styles.errorBox}/>
|
||||
};
|
||||
|
||||
showFieldError = (field) => {
|
||||
if(!this.fieldHasError(field) ) {
|
||||
return;
|
||||
}
|
||||
return <Item style={styles.errorText}><Text style={styles.errorMessage}>{this.state.errors[field]}</Text></Item>;
|
||||
};
|
||||
|
||||
showSpinner = () => {
|
||||
if (this.props.isRegisteringUser) {
|
||||
return <Spinner color="#f58220"/>
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Container style={styles.container}>
|
||||
<Content keyboardShouldPersistTaps='always'>
|
||||
<Form style={styles.card}>
|
||||
{this.firstNameInput()}
|
||||
{this.showFieldError('firstName')}
|
||||
{this.lastNameInput()}
|
||||
{this.showFieldError('lastName')}
|
||||
{this.emailInput()}
|
||||
{this.showFieldError('email')}
|
||||
{this.phoneNumberInput()}
|
||||
{this.showFieldError('phoneNumber')}
|
||||
{this.passwordInput()}
|
||||
{this.showFieldError('password')}
|
||||
{this.showNonFieldError()}
|
||||
{this.registerbutton()}
|
||||
{this.showSpinner()}
|
||||
</Form>
|
||||
</Content>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
onRegisterPress = () => {
|
||||
if( !this.canSubmitForm() ) {
|
||||
Toast.show({
|
||||
text: 'Noen av feltene er ikke fylt ut riktig',
|
||||
position: 'bottom',
|
||||
buttonText: 'OK',
|
||||
duration: 1500,
|
||||
firstNameInput() {
|
||||
return (
|
||||
<Item error={this.fieldHasError('firstName')}>
|
||||
<Label>Fornavn</Label>
|
||||
<Input
|
||||
autoFocus={true}
|
||||
returnKeyType="next"
|
||||
onChangeText={(firstName) => {
|
||||
this.setState({ firstName }, () => {
|
||||
this.validateForm('firstName');
|
||||
});
|
||||
return;
|
||||
}
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
this.refs.lastNameInput._root.focus();
|
||||
}}
|
||||
value={this.state.firstName}
|
||||
/>
|
||||
{this.fieldHasError('firstName') && <Icon name="close-circle" />}
|
||||
</Item>
|
||||
);
|
||||
}
|
||||
|
||||
this.props.onRegisterPress(
|
||||
this.state.firstName,
|
||||
this.state.lastName,
|
||||
this.state.email,
|
||||
this.state.phoneNumber,
|
||||
this.state.password,
|
||||
);
|
||||
};
|
||||
lastNameInput() {
|
||||
return (
|
||||
<Item error={this.fieldHasError('lastName')}>
|
||||
<Label>Etternavn</Label>
|
||||
<Input
|
||||
ref="lastNameInput"
|
||||
returnKeyType="next"
|
||||
onChangeText={(lastName) => {
|
||||
this.setState({ lastName }, () => {
|
||||
this.validateForm('lastName');
|
||||
});
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
this.refs.emailInput._root.focus();
|
||||
}}
|
||||
value={this.state.lastName}
|
||||
/>
|
||||
{this.fieldHasError('lastName') && <Icon name="close-circle" />}
|
||||
</Item>
|
||||
);
|
||||
}
|
||||
|
||||
registerbutton() {
|
||||
const isDisabled = this.state.touched.size === 0 || this.props.isRegisteringUser || !this.canSubmitForm();
|
||||
emailInput() {
|
||||
return (
|
||||
<Item error={this.fieldHasError('email')}>
|
||||
<Label>E-post</Label>
|
||||
<Input
|
||||
ref="emailInput"
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
returnKeyType="next"
|
||||
onChangeText={(email) => {
|
||||
this.setState({ email }, () => {
|
||||
this.validateForm('email');
|
||||
});
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
this.refs.phoneInput._root.focus();
|
||||
}}
|
||||
value={this.state.email}
|
||||
/>
|
||||
{this.fieldHasError('email') && <Icon name="close-circle" />}
|
||||
</Item>
|
||||
);
|
||||
}
|
||||
|
||||
return (<Button
|
||||
full
|
||||
disabled={isDisabled}
|
||||
onPress={this.onRegisterPress}
|
||||
style={styles.registerButton}
|
||||
>
|
||||
<Text>Registrer meg</Text>
|
||||
</Button>);
|
||||
phoneNumberInput() {
|
||||
return (
|
||||
<Item error={this.fieldHasError('phoneNumber')}>
|
||||
<Label>Mobilnummer</Label>
|
||||
<Input
|
||||
ref="phoneInput"
|
||||
returnKeyType="next"
|
||||
keyboardType="phone-pad"
|
||||
onChangeText={(phoneNumber) => {
|
||||
this.setState({ phoneNumber }, () => {
|
||||
this.validateForm('phoneNumber');
|
||||
});
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
this.refs.passwordInput._root.focus();
|
||||
}}
|
||||
value={this.state.phoneNumber}
|
||||
/>
|
||||
{this.fieldHasError('phoneNumber') && <Icon name="close-circle" />}
|
||||
</Item>
|
||||
);
|
||||
}
|
||||
|
||||
passwordInput() {
|
||||
return (
|
||||
<Item error={this.fieldHasError('password')}>
|
||||
<Label>Passord</Label>
|
||||
<Input
|
||||
ref="passwordInput"
|
||||
secureTextEntry={true}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
onChangeText={(password) => {
|
||||
this.setState({ password }, () => {
|
||||
this.validateForm('password');
|
||||
});
|
||||
}}
|
||||
onSubmitEditing={this.onRegisterPress}
|
||||
value={this.state.password}
|
||||
/>
|
||||
{this.fieldHasError('password') && <Icon name="close-circle" />}
|
||||
</Item>
|
||||
);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.registerError !== nextProps.registerError) {
|
||||
this.setState({ errors: nextProps.registerError });
|
||||
}
|
||||
}
|
||||
|
||||
showNonFieldError = () => {
|
||||
const errors = this.props.registerError;
|
||||
if (Object.keys(errors).length !== 0 && 'non_field_errors' in errors) {
|
||||
const err = errors.non_field_errors;
|
||||
const errorFormatted = err ? err[0] : 'Kunne ikke registrere bruker, prøv igjen...';
|
||||
return (
|
||||
<View style={styles.errorBox}>
|
||||
<Text style={styles.errorMessage}>{errorFormatted}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (Object.keys(errors).length !== 0 && 'detail' in errors) {
|
||||
const err = errors.detail;
|
||||
const errorFormatted = err || 'Kunne ikke registrere bruker, prøv igjen...';
|
||||
return (
|
||||
<View style={styles.errorBox}>
|
||||
<Text style={styles.errorMessage}>{errorFormatted}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return <View style={styles.errorBox} />;
|
||||
};
|
||||
|
||||
showFieldError = (field) => {
|
||||
if (!this.fieldHasError(field)) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<Item style={styles.errorText}>
|
||||
<Text style={styles.errorMessage}>{this.state.errors[field]}</Text>
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
showSpinner = () => {
|
||||
if (this.props.isRegisteringUser) {
|
||||
return <Spinner color="#f58220" />;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Container style={styles.container}>
|
||||
<Content keyboardShouldPersistTaps="always">
|
||||
<Form style={styles.card}>
|
||||
{this.firstNameInput()}
|
||||
{this.showFieldError('firstName')}
|
||||
{this.lastNameInput()}
|
||||
{this.showFieldError('lastName')}
|
||||
{this.emailInput()}
|
||||
{this.showFieldError('email')}
|
||||
{this.phoneNumberInput()}
|
||||
{this.showFieldError('phoneNumber')}
|
||||
{this.passwordInput()}
|
||||
{this.showFieldError('password')}
|
||||
{this.showNonFieldError()}
|
||||
{this.registerbutton()}
|
||||
{this.showSpinner()}
|
||||
</Form>
|
||||
</Content>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
onRegisterPress = () => {
|
||||
if (!this.canSubmitForm()) {
|
||||
Toast.show({
|
||||
text: 'Noen av feltene er ikke fylt ut riktig',
|
||||
position: 'bottom',
|
||||
buttonText: 'OK',
|
||||
duration: 1500,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onRegisterPress(
|
||||
this.state.firstName,
|
||||
this.state.lastName,
|
||||
this.state.email,
|
||||
this.state.phoneNumber,
|
||||
this.state.password
|
||||
);
|
||||
};
|
||||
|
||||
registerbutton() {
|
||||
const isDisabled = this.state.touched.size === 0 || this.props.isRegisteringUser || !this.canSubmitForm();
|
||||
|
||||
return (
|
||||
<Button full disabled={isDisabled} onPress={this.onRegisterPress} style={styles.registerButton}>
|
||||
<Text>Registrer meg</Text>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
input: {
|
||||
width: 320,
|
||||
fontSize: 20,
|
||||
height: 52,
|
||||
},
|
||||
registerButton: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
errorBox: {
|
||||
marginVertical: 10,
|
||||
},
|
||||
errorMessage: {
|
||||
textAlign: 'center',
|
||||
color: theme.colors.danger,
|
||||
fontSize: 14,
|
||||
},
|
||||
card: theme.card,
|
||||
errorText: {
|
||||
paddingTop: 2,
|
||||
paddingBottom: 12,
|
||||
}
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
input: {
|
||||
width: 320,
|
||||
fontSize: 20,
|
||||
height: 52,
|
||||
},
|
||||
registerButton: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
errorBox: {
|
||||
marginVertical: 10,
|
||||
},
|
||||
errorMessage: {
|
||||
textAlign: 'center',
|
||||
color: theme.colors.danger,
|
||||
fontSize: 14,
|
||||
},
|
||||
card: theme.card,
|
||||
errorText: {
|
||||
paddingTop: 2,
|
||||
paddingBottom: 12,
|
||||
},
|
||||
});
|
||||
@@ -1,49 +1,50 @@
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from "react-redux";
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import UserRegister from "./UserRegister";
|
||||
import {requestRegisterUser} from "../../api";
|
||||
import UserRegister from './UserRegister';
|
||||
import { requestRegisterUser } from '../../api';
|
||||
|
||||
class UserRegisterContainer extends Component {
|
||||
|
||||
constructor(state) {
|
||||
super(state);
|
||||
this.state = {
|
||||
isAuthenticated: false,
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.state.isAuthenticated !== nextProps.isAuthenticated) {
|
||||
if (nextProps.isAuthenticated) {
|
||||
this.props.onRegister();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRegisterPress = (firstName, lastName, email, phoneNumber, password) => {
|
||||
this.props.requestRegisterUser(firstName, lastName, email, phoneNumber, password);
|
||||
constructor(state) {
|
||||
super(state);
|
||||
this.state = {
|
||||
isAuthenticated: false,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return <UserRegister
|
||||
{...this.state}
|
||||
onRegisterPress={this.onRegisterPress}
|
||||
registerError={this.props.registerError}
|
||||
isRegisteringUser={this.props.isRegisteringUser}
|
||||
/>;
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.state.isAuthenticated !== nextProps.isAuthenticated) {
|
||||
if (nextProps.isAuthenticated) {
|
||||
this.props.onRegister();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRegisterPress = (firstName, lastName, email, phoneNumber, password) => {
|
||||
this.props.requestRegisterUser(firstName, lastName, email, phoneNumber, password);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<UserRegister
|
||||
{...this.state}
|
||||
onRegisterPress={this.onRegisterPress}
|
||||
registerError={this.props.registerError}
|
||||
isRegisteringUser={this.props.isRegisteringUser}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function storeToProps(store) {
|
||||
const errors = store.registerError === null ? {} : store.registerError;
|
||||
return {
|
||||
isAuthenticated: store.isAuthenticated,
|
||||
isRegisteringUser: store.isRegisteringUser,
|
||||
registerError: errors
|
||||
};
|
||||
const errors = store.registerError === null ? {} : store.registerError;
|
||||
return {
|
||||
isAuthenticated: store.isAuthenticated,
|
||||
isRegisteringUser: store.isRegisteringUser,
|
||||
registerError: errors,
|
||||
};
|
||||
}
|
||||
export default connect(
|
||||
storeToProps,
|
||||
{requestRegisterUser}
|
||||
storeToProps,
|
||||
{ requestRegisterUser }
|
||||
)(UserRegisterContainer);
|
||||
+55
-50
@@ -1,67 +1,72 @@
|
||||
import {Platform, StyleSheet} from 'react-native';
|
||||
import {createBottomTabNavigator, createStackNavigator, createAppContainer, getActiveChildNavigationOptions} from "react-navigation";
|
||||
import { Platform, StyleSheet } from 'react-native';
|
||||
import {
|
||||
createBottomTabNavigator,
|
||||
createStackNavigator,
|
||||
createAppContainer,
|
||||
getActiveChildNavigationOptions,
|
||||
} from 'react-navigation';
|
||||
|
||||
import AboutScreen from "./screens/AboutScreen";
|
||||
import EventDetailScreen from "./screens/EventDetailScreen";
|
||||
import EventListScreen from "./screens/EventListScreen";
|
||||
import LoginScreen from "./screens/LoginScreen";
|
||||
import MembershipScreen from "./screens/MembershipScreen";
|
||||
import UserRegisterScreen from "./screens/UserRegisterScreen";
|
||||
import AboutScreen from './screens/AboutScreen';
|
||||
import EventDetailScreen from './screens/EventDetailScreen';
|
||||
import EventListScreen from './screens/EventListScreen';
|
||||
import LoginScreen from './screens/LoginScreen';
|
||||
import MembershipScreen from './screens/MembershipScreen';
|
||||
import UserRegisterScreen from './screens/UserRegisterScreen';
|
||||
|
||||
const tabBarRoutes = {
|
||||
EventList: EventListScreen,
|
||||
Membership: MembershipScreen,
|
||||
About: AboutScreen,
|
||||
EventList: EventListScreen,
|
||||
Membership: MembershipScreen,
|
||||
About: AboutScreen,
|
||||
};
|
||||
|
||||
const tabBarOptions = {
|
||||
tabBarPosition: 'bottom',
|
||||
tabBarOptions: {
|
||||
style: {
|
||||
...Platform.select({
|
||||
android: {
|
||||
backgroundColor: "#fff",
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: 'rgba(0, 0, 0, 0.1)'
|
||||
}
|
||||
})
|
||||
},
|
||||
indicatorStyle: {
|
||||
backgroundColor: "#f58220"
|
||||
},
|
||||
activeTintColor: '#f58220',
|
||||
inactiveTintColor: '#333333',
|
||||
showIcon: true,
|
||||
labelStyle: {
|
||||
fontSize: 10,
|
||||
marginBottom: 0,
|
||||
...Platform.select({
|
||||
android: {
|
||||
fontWeight: 'bold',
|
||||
}
|
||||
}),
|
||||
tabBarPosition: 'bottom',
|
||||
tabBarOptions: {
|
||||
style: {
|
||||
...Platform.select({
|
||||
android: {
|
||||
backgroundColor: '#fff',
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: 'rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
}),
|
||||
},
|
||||
navigationOptions: ({ navigation, screenProps }) => ({
|
||||
// get header title from active child screen
|
||||
...getActiveChildNavigationOptions(navigation, screenProps),
|
||||
headerStyle: {
|
||||
backgroundColor: '#f58220',
|
||||
indicatorStyle: {
|
||||
backgroundColor: '#f58220',
|
||||
},
|
||||
activeTintColor: '#f58220',
|
||||
inactiveTintColor: '#333333',
|
||||
showIcon: true,
|
||||
labelStyle: {
|
||||
fontSize: 10,
|
||||
marginBottom: 0,
|
||||
...Platform.select({
|
||||
android: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
headerTintColor: 'white',
|
||||
})
|
||||
}),
|
||||
},
|
||||
},
|
||||
navigationOptions: ({ navigation, screenProps }) => ({
|
||||
// get header title from active child screen
|
||||
...getActiveChildNavigationOptions(navigation, screenProps),
|
||||
headerStyle: {
|
||||
backgroundColor: '#f58220',
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
headerTintColor: 'white',
|
||||
}),
|
||||
};
|
||||
|
||||
const tabNav = createBottomTabNavigator(tabBarRoutes, tabBarOptions);
|
||||
|
||||
const DuskenNavigation = createStackNavigator({
|
||||
Root: tabNav,
|
||||
Login: LoginScreen,
|
||||
UserRegister: UserRegisterScreen,
|
||||
EventDetail: EventDetailScreen,
|
||||
Root: tabNav,
|
||||
Login: LoginScreen,
|
||||
UserRegister: UserRegisterScreen,
|
||||
EventDetail: EventDetailScreen,
|
||||
});
|
||||
|
||||
export default createAppContainer(DuskenNavigation);
|
||||
+107
-107
@@ -1,133 +1,133 @@
|
||||
import {
|
||||
LOGIN_REQUEST,
|
||||
LOGIN_SUCCESS,
|
||||
LOGIN_FAILURE,
|
||||
LOGOUT,
|
||||
USER_DATA_REQUEST,
|
||||
USER_DATA_SUCCESS,
|
||||
USER_DATA_FAILURE,
|
||||
REGISTER_USER_REQUEST,
|
||||
REGISTER_USER_FAILURE,
|
||||
REGISTER_USER_SUCCESS, MEMBERSHIP_CHARGE_REQUEST, MEMBERSHIP_CHARGE_FAILURE, MEMBERSHIP_CHARGE_SUCCESS,
|
||||
LOGIN_REQUEST,
|
||||
LOGIN_SUCCESS,
|
||||
LOGIN_FAILURE,
|
||||
LOGOUT,
|
||||
USER_DATA_REQUEST,
|
||||
USER_DATA_SUCCESS,
|
||||
USER_DATA_FAILURE,
|
||||
REGISTER_USER_REQUEST,
|
||||
REGISTER_USER_FAILURE,
|
||||
REGISTER_USER_SUCCESS,
|
||||
MEMBERSHIP_CHARGE_REQUEST,
|
||||
MEMBERSHIP_CHARGE_FAILURE,
|
||||
MEMBERSHIP_CHARGE_SUCCESS,
|
||||
} from './actions';
|
||||
|
||||
import { snakeToCamelCase } from './utils'
|
||||
import { snakeToCamelCase } from './utils';
|
||||
|
||||
const initialState = {
|
||||
isAuthenticated: false,
|
||||
isLoggingIn: false,
|
||||
isFetchingUserData: false,
|
||||
isRegisteringUser: false,
|
||||
isChargingMembership: false,
|
||||
userToken: null,
|
||||
user: null,
|
||||
loginError: null,
|
||||
userError: null,
|
||||
registerError: null,
|
||||
chargeError: null,
|
||||
lastOrder: null,
|
||||
isAuthenticated: false,
|
||||
isLoggingIn: false,
|
||||
isFetchingUserData: false,
|
||||
isRegisteringUser: false,
|
||||
isChargingMembership: false,
|
||||
userToken: null,
|
||||
user: null,
|
||||
loginError: null,
|
||||
userError: null,
|
||||
registerError: null,
|
||||
chargeError: null,
|
||||
lastOrder: null,
|
||||
};
|
||||
|
||||
|
||||
function formatErrors(errs) {
|
||||
let _errs = {};
|
||||
for(let [key, value] of Object.entries(errs)) {
|
||||
_errs[snakeToCamelCase(key)] = Array.isArray(value) ? value.join('\n') : value;
|
||||
}
|
||||
return _errs
|
||||
const _errs = {};
|
||||
for (const [key, value] of Object.entries(errs)) {
|
||||
_errs[snakeToCamelCase(key)] = Array.isArray(value) ? value.join('\n') : value;
|
||||
}
|
||||
return _errs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default function duskenApp(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
/* Login */
|
||||
case LOGIN_REQUEST:
|
||||
return Object.assign({}, state, {
|
||||
isLoggingIn: true,
|
||||
loginError: null,
|
||||
});
|
||||
switch (action.type) {
|
||||
/* Login */
|
||||
case LOGIN_REQUEST:
|
||||
return Object.assign({}, state, {
|
||||
isLoggingIn: true,
|
||||
loginError: null,
|
||||
});
|
||||
|
||||
case LOGIN_FAILURE:
|
||||
return Object.assign({}, state, {
|
||||
isLoggingIn: false,
|
||||
loginError: action.loginError
|
||||
});
|
||||
case LOGIN_FAILURE:
|
||||
return Object.assign({}, state, {
|
||||
isLoggingIn: false,
|
||||
loginError: action.loginError,
|
||||
});
|
||||
|
||||
case LOGIN_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isLoggingIn: false,
|
||||
isAuthenticated: true,
|
||||
userToken: action.token
|
||||
});
|
||||
case LOGIN_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isLoggingIn: false,
|
||||
isAuthenticated: true,
|
||||
userToken: action.token,
|
||||
});
|
||||
|
||||
case LOGOUT:
|
||||
return initialState;
|
||||
case LOGOUT:
|
||||
return initialState;
|
||||
|
||||
/* User data */
|
||||
case USER_DATA_REQUEST:
|
||||
return Object.assign({}, state, {
|
||||
isFetchingUserData: true,
|
||||
userError: null
|
||||
});
|
||||
/* User data */
|
||||
case USER_DATA_REQUEST:
|
||||
return Object.assign({}, state, {
|
||||
isFetchingUserData: true,
|
||||
userError: null,
|
||||
});
|
||||
|
||||
case USER_DATA_FAILURE:
|
||||
return Object.assign({}, state, {
|
||||
isFetchingUserData: false,
|
||||
userError: action.userError
|
||||
});
|
||||
case USER_DATA_FAILURE:
|
||||
return Object.assign({}, state, {
|
||||
isFetchingUserData: false,
|
||||
userError: action.userError,
|
||||
});
|
||||
|
||||
case USER_DATA_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isFetchingUserData: false,
|
||||
user: action.data,
|
||||
});
|
||||
case USER_DATA_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isFetchingUserData: false,
|
||||
user: action.data,
|
||||
});
|
||||
|
||||
/* Register user */
|
||||
case REGISTER_USER_REQUEST:
|
||||
return Object.assign({}, state, {
|
||||
isRegisteringUser: true,
|
||||
registerError: null,
|
||||
});
|
||||
/* Register user */
|
||||
case REGISTER_USER_REQUEST:
|
||||
return Object.assign({}, state, {
|
||||
isRegisteringUser: true,
|
||||
registerError: null,
|
||||
});
|
||||
|
||||
case REGISTER_USER_FAILURE:
|
||||
return Object.assign({}, state, {
|
||||
isRegisteringUser: false,
|
||||
registerError: formatErrors(action.registerError)
|
||||
});
|
||||
case REGISTER_USER_FAILURE:
|
||||
return Object.assign({}, state, {
|
||||
isRegisteringUser: false,
|
||||
registerError: formatErrors(action.registerError),
|
||||
});
|
||||
|
||||
case REGISTER_USER_SUCCESS:
|
||||
let userData = action.data;
|
||||
const token = action.data.auth_token;
|
||||
delete userData['auth_token'];
|
||||
case REGISTER_USER_SUCCESS:
|
||||
const userData = action.data;
|
||||
const token = action.data.auth_token;
|
||||
delete userData.auth_token;
|
||||
|
||||
return Object.assign({}, state, {
|
||||
isRegisteringUser: false,
|
||||
isAuthenticated: true,
|
||||
user: userData,
|
||||
userToken: token,
|
||||
});
|
||||
return Object.assign({}, state, {
|
||||
isRegisteringUser: false,
|
||||
isAuthenticated: true,
|
||||
user: userData,
|
||||
userToken: token,
|
||||
});
|
||||
|
||||
/* User data */
|
||||
case MEMBERSHIP_CHARGE_REQUEST:
|
||||
return Object.assign({}, state, {
|
||||
isChargingMembership: true,
|
||||
chargeError: null
|
||||
});
|
||||
/* User data */
|
||||
case MEMBERSHIP_CHARGE_REQUEST:
|
||||
return Object.assign({}, state, {
|
||||
isChargingMembership: true,
|
||||
chargeError: null,
|
||||
});
|
||||
|
||||
case MEMBERSHIP_CHARGE_FAILURE:
|
||||
return Object.assign({}, state, {
|
||||
isChargingMembership: false,
|
||||
chargeError: action.chargeError
|
||||
});
|
||||
case MEMBERSHIP_CHARGE_FAILURE:
|
||||
return Object.assign({}, state, {
|
||||
isChargingMembership: false,
|
||||
chargeError: action.chargeError,
|
||||
});
|
||||
|
||||
case MEMBERSHIP_CHARGE_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isChargingMembership: false,
|
||||
lastOrder: action.data,
|
||||
});
|
||||
case MEMBERSHIP_CHARGE_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isChargingMembership: false,
|
||||
lastOrder: action.data,
|
||||
});
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
+5
-14
@@ -1,26 +1,17 @@
|
||||
import { applyMiddleware, createStore } from 'redux';
|
||||
import { persistStore, persistReducer } from 'redux-persist';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import thunk from "redux-thunk";
|
||||
|
||||
import rootReducer from './reducers'
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
import rootReducer from './reducers';
|
||||
|
||||
const persistConfig = {
|
||||
key: 'root',
|
||||
storage: AsyncStorage,
|
||||
blacklist: [
|
||||
'isLoggingIn',
|
||||
'isFetchingUserData',
|
||||
'isRegisteringUser',
|
||||
'isChargingMembership'
|
||||
]
|
||||
blacklist: ['isLoggingIn', 'isFetchingUserData', 'isRegisteringUser', 'isChargingMembership'],
|
||||
};
|
||||
|
||||
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
||||
|
||||
export let store = createStore(
|
||||
persistedReducer,
|
||||
applyMiddleware(...[thunk])
|
||||
);
|
||||
export let persistor = persistStore(store);
|
||||
export const store = createStore(persistedReducer, applyMiddleware(...[thunk]));
|
||||
export const persistor = persistStore(store);
|
||||
+13
-14
@@ -1,22 +1,21 @@
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'react-native-vector-icons/SimpleLineIcons';
|
||||
import {StyleProvider} from "native-base";
|
||||
import { StyleProvider } from 'native-base';
|
||||
|
||||
import AboutContainer from "../modules/about/AboutContainer";
|
||||
import AboutContainer from '../modules/about/AboutContainer';
|
||||
import getTheme from '../../native-base-theme/components';
|
||||
|
||||
export default class AboutScreen extends Component {
|
||||
static navigationOptions = {
|
||||
title: 'Om oss',
|
||||
tabBarIcon: ({ tintColor }) => (
|
||||
<Icon name="info" size={16} color={tintColor} style={{marginTop: 5}} />
|
||||
),
|
||||
};
|
||||
static navigationOptions = {
|
||||
title: 'Om oss',
|
||||
tabBarIcon: ({ tintColor }) => <Icon name="info" size={16} color={tintColor} style={{ marginTop: 5 }} />,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyleProvider style={getTheme()}>
|
||||
<AboutContainer />
|
||||
</StyleProvider>);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<StyleProvider style={getTheme()}>
|
||||
<AboutContainer />
|
||||
</StyleProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
import React, { Component } from 'react';
|
||||
import {StyleProvider} from "native-base";
|
||||
import EventDetail from "../modules/events/EventDetail";
|
||||
import { StyleProvider } from 'native-base';
|
||||
import EventDetail from '../modules/events/EventDetail';
|
||||
import getTheme from '../../native-base-theme/components';
|
||||
|
||||
export default class EventDetailScreen extends Component {
|
||||
static navigationOptions = ({navigation}) => ({
|
||||
title: navigation.state.params.item.title.decoded,
|
||||
headerStyle: {
|
||||
backgroundColor: '#f58220',
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
headerTintColor: 'white',
|
||||
});
|
||||
static navigationOptions = ({ navigation }) => ({
|
||||
title: navigation.state.params.item.title.decoded,
|
||||
headerStyle: {
|
||||
backgroundColor: '#f58220',
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
headerTintColor: 'white',
|
||||
});
|
||||
|
||||
render() {
|
||||
const { params } = this.props.navigation.state;
|
||||
return (
|
||||
<StyleProvider style={getTheme()}>
|
||||
<EventDetail item={params.item}/>
|
||||
</StyleProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const { params } = this.props.navigation.state;
|
||||
return (
|
||||
<StyleProvider style={getTheme()}>
|
||||
<EventDetail item={params.item} />
|
||||
</StyleProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,25 @@
|
||||
import React, { Component } from 'react';
|
||||
import EventListContainer from "../modules/events/EventListContainer";
|
||||
import Icon from 'react-native-vector-icons/SimpleLineIcons';
|
||||
import {StyleProvider} from "native-base";
|
||||
import { StyleProvider } from 'native-base';
|
||||
import EventListContainer from '../modules/events/EventListContainer';
|
||||
import getTheme from '../../native-base-theme/components';
|
||||
|
||||
export default class EventsScreen extends Component {
|
||||
static navigationOptions = {
|
||||
title: 'Program',
|
||||
tabBarIcon: ({ tintColor }) => (
|
||||
<Icon name="event" size={16} color={tintColor} style={{marginTop: 5}}/>
|
||||
),
|
||||
};
|
||||
static navigationOptions = {
|
||||
title: 'Program',
|
||||
tabBarIcon: ({ tintColor }) => <Icon name="event" size={16} color={tintColor} style={{ marginTop: 5 }} />,
|
||||
};
|
||||
|
||||
showEvent = (item) => {
|
||||
const { navigate } = this.props.navigation;
|
||||
navigate('EventDetail', {item: item})
|
||||
};
|
||||
showEvent = (item) => {
|
||||
const { navigate } = this.props.navigation;
|
||||
navigate('EventDetail', { item });
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyleProvider style={getTheme()}>
|
||||
<EventListContainer showEvent={this.showEvent}/>
|
||||
</StyleProvider>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<StyleProvider style={getTheme()}>
|
||||
<EventListContainer showEvent={this.showEvent} />
|
||||
</StyleProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
+24
-24
@@ -1,30 +1,30 @@
|
||||
import React, { Component } from 'react';
|
||||
import LoginContainer from "../modules/login/LoginContainer";
|
||||
import {StyleProvider} from "native-base";
|
||||
import { StyleProvider } from 'native-base';
|
||||
import LoginContainer from '../modules/login/LoginContainer';
|
||||
import getTheme from '../../native-base-theme/components';
|
||||
|
||||
export default class LoginScreen extends Component {
|
||||
static navigationOptions = {
|
||||
title: 'Logg inn',
|
||||
headerStyle: {
|
||||
backgroundColor: '#f58220',
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
headerTintColor: 'white',
|
||||
};
|
||||
static navigationOptions = {
|
||||
title: 'Logg inn',
|
||||
headerStyle: {
|
||||
backgroundColor: '#f58220',
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
headerTintColor: 'white',
|
||||
};
|
||||
|
||||
onLogin = () => {
|
||||
// This works since LoginScreen is allways navigated to from MembershipScreen
|
||||
this.props.navigation.goBack();
|
||||
};
|
||||
onLogin = () => {
|
||||
// This works since LoginScreen is allways navigated to from MembershipScreen
|
||||
this.props.navigation.goBack();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyleProvider style={getTheme()}>
|
||||
<LoginContainer onLogin={this.onLogin} />
|
||||
</StyleProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<StyleProvider style={getTheme()}>
|
||||
<LoginContainer onLogin={this.onLogin} />
|
||||
</StyleProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,36 @@
|
||||
import React, { Component } from 'react';
|
||||
import MembershipContainer from "../modules/membership/MembershipContainer";
|
||||
import Icon from 'react-native-vector-icons/SimpleLineIcons';
|
||||
import {StyleProvider} from "native-base";
|
||||
import { StyleProvider } from 'native-base';
|
||||
import MembershipContainer from '../modules/membership/MembershipContainer';
|
||||
import getTheme from '../../native-base-theme/components';
|
||||
|
||||
export default class MembershipScreen extends Component {
|
||||
static navigationOptions = {
|
||||
title: 'Medlemskap',
|
||||
tabBarIcon: ({ tintColor }) => (
|
||||
<Icon name="user" size={16} color={tintColor} style={{marginTop: 5}} />
|
||||
),
|
||||
};
|
||||
static navigationOptions = {
|
||||
title: 'Medlemskap',
|
||||
tabBarIcon: ({ tintColor }) => <Icon name="user" size={16} color={tintColor} style={{ marginTop: 5 }} />,
|
||||
};
|
||||
|
||||
loginNavigate = () => {
|
||||
this.props.navigation.navigate('Login');
|
||||
};
|
||||
loginNavigate = () => {
|
||||
this.props.navigation.navigate('Login');
|
||||
};
|
||||
|
||||
logoutNavigate = () => {
|
||||
this.props.navigation.navigate('Membership');
|
||||
};
|
||||
logoutNavigate = () => {
|
||||
this.props.navigation.navigate('Membership');
|
||||
};
|
||||
|
||||
registerNavigate = () => {
|
||||
this.props.navigation.navigate('UserRegister');
|
||||
};
|
||||
registerNavigate = () => {
|
||||
this.props.navigation.navigate('UserRegister');
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyleProvider style={getTheme()}>
|
||||
<MembershipContainer
|
||||
onLoginPress={this.loginNavigate}
|
||||
onLogoutPress={this.logoutNavigate}
|
||||
onRegisterPress={this.registerNavigate} />
|
||||
</StyleProvider>
|
||||
)
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<StyleProvider style={getTheme()}>
|
||||
<MembershipContainer
|
||||
onLoginPress={this.loginNavigate}
|
||||
onLogoutPress={this.logoutNavigate}
|
||||
onRegisterPress={this.registerNavigate}
|
||||
/>
|
||||
</StyleProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,30 @@
|
||||
import React, { Component } from 'react';
|
||||
import UserRegisterContainer from "../modules/register/UserRegisterContainer";
|
||||
import {StyleProvider} from "native-base";
|
||||
import { StyleProvider } from 'native-base';
|
||||
import UserRegisterContainer from '../modules/register/UserRegisterContainer';
|
||||
import getTheme from '../../native-base-theme/components';
|
||||
|
||||
export default class UserRegisterScreen extends Component {
|
||||
static navigationOptions = {
|
||||
title: 'Bli medlem',
|
||||
headerStyle: {
|
||||
backgroundColor: '#f58220',
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
headerTintColor: 'white',
|
||||
};
|
||||
static navigationOptions = {
|
||||
title: 'Bli medlem',
|
||||
headerStyle: {
|
||||
backgroundColor: '#f58220',
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
headerTintColor: 'white',
|
||||
};
|
||||
|
||||
onRegister = () => {
|
||||
// This works since UserRegisterScreen is allways navigated to from MembershipScreen
|
||||
this.props.navigation.goBack();
|
||||
};
|
||||
onRegister = () => {
|
||||
// This works since UserRegisterScreen is allways navigated to from MembershipScreen
|
||||
this.props.navigation.goBack();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyleProvider style={getTheme()}>
|
||||
<UserRegisterContainer onRegister={this.onRegister} />
|
||||
</StyleProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<StyleProvider style={getTheme()}>
|
||||
<UserRegisterContainer onRegister={this.onRegister} />
|
||||
</StyleProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
+18
-18
@@ -1,24 +1,24 @@
|
||||
import { Platform } from "react-native";
|
||||
import platform from "native-base/src/theme/variables/platform";
|
||||
import { Platform } from 'react-native';
|
||||
import platform from 'native-base/src/theme/variables/platform';
|
||||
|
||||
const colors = {
|
||||
danger: platform.btnDangerBg
|
||||
danger: platform.btnDangerBg,
|
||||
};
|
||||
|
||||
const card = {
|
||||
...Platform.select({
|
||||
ios: {
|
||||
shadowColor: 'rgba(0,0,0, .2)',
|
||||
shadowOffset: {height: 0, width: 0},
|
||||
shadowOpacity: 1,
|
||||
shadowRadius: 1,
|
||||
},
|
||||
android: {
|
||||
elevation: 1,
|
||||
},
|
||||
}),
|
||||
padding: 8,
|
||||
margin: 8,
|
||||
backgroundColor: '#fff',
|
||||
...Platform.select({
|
||||
ios: {
|
||||
shadowColor: 'rgba(0,0,0, .2)',
|
||||
shadowOffset: { height: 0, width: 0 },
|
||||
shadowOpacity: 1,
|
||||
shadowRadius: 1,
|
||||
},
|
||||
android: {
|
||||
elevation: 1,
|
||||
},
|
||||
}),
|
||||
padding: 8,
|
||||
margin: 8,
|
||||
backgroundColor: '#fff',
|
||||
};
|
||||
export default {card, colors};
|
||||
export default { card, colors };
|
||||
+10
-10
@@ -1,14 +1,14 @@
|
||||
export function snakeToCamelCase(val) {
|
||||
return val.replace(/_([a-z])/g, function (g) { return g[1].toUpperCase(); });
|
||||
return val.replace(/_([a-z])/g, function(g) {
|
||||
return g[1].toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchWithTimeout(url, options, timeout=10000) {
|
||||
/** Wraps fetch to allow it to time out */
|
||||
// FIXME: show timeout message in UI
|
||||
return Promise.race([
|
||||
fetch(url, options),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Fetch request timed out')), timeout)
|
||||
)
|
||||
]);
|
||||
export function fetchWithTimeout(url, options, timeout = 10000) {
|
||||
/** Wraps fetch to allow it to time out */
|
||||
// FIXME: show timeout message in UI
|
||||
return Promise.race([
|
||||
fetch(url, options),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('Fetch request timed out')), timeout)),
|
||||
]);
|
||||
}
|
||||
Reference in new issue
Block a user