🎨 Add prettier and eslint, then format

This commit is contained in:
Nikolai R Kristiansen committed 2019-05-05 12:13:55 +02:00
1 parent ab77f9dea9
commit 148ea8e7c2
33 files changed
+2709 -2118

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
{
"extends": [
"airbnb-base",
"plugin:prettier/recommended",
"plugin:react/recommended",
"plugin:import/errors",
"plugin:import/warnings",
],
"rules": {
"no-plusplus": 0,
"no-console": 0,
"no-underscore-dangle": 0,
"no-param-reassign": 0,
"react/prefer-stateless-function": 1,
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"react/sort-comp": 0,
"max-len": 0,
"no-use-before-define": ["error", { "functions": true, "classes": true, "variables": false }] // disable the rule for variables, but enable it for functions and classes
},
"plugins": [
"react",
"prettier"
],
"parser": "babel-eslint",
"settings": {
"react": {
"version": "16.8"
}
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 120,
"arrowParens": "always"
}
+7 -4
View File
@@ -119,10 +119,13 @@ android {
}
signingConfigs {
release {
storeFile file(DUSKEN_RELEASE_STORE_FILE)
storePassword DUSKEN_RELEASE_STORE_PASSWORD
keyAlias DUSKEN_RELEASE_KEY_ALIAS
keyPassword DUSKEN_RELEASE_KEY_PASSWORD
// These variables are in ~/.gradle/gradle.properties
if (project.hasProperty('DUSKEN_RELEASE_STORE_FILE')) {
storeFile file(DUSKEN_RELEASE_STORE_FILE)
storePassword DUSKEN_RELEASE_STORE_PASSWORD
keyAlias DUSKEN_RELEASE_KEY_ALIAS
keyPassword DUSKEN_RELEASE_KEY_PASSWORD
}
}
}
buildTypes {
+11
View File
@@ -6,7 +6,9 @@
"android": "react-native run-android",
"build:android": "cd android && ./gradlew assembleRelease",
"emulator": "emulator -avd $(emulator -list-avds | head)&",
"format": "eslint --fix \"src/**/*.js\"",
"ios": "react-native run-ios",
"lint:format": "eslint --quiet \"src/**/*.js\"",
"release:android": "cd android && bundle exec fastlane beta",
"release:ios": "cd ios && bundle exec fastlane beta",
"start": "node node_modules/react-native/local-cli/cli.js start",
@@ -44,10 +46,19 @@
"devDependencies": {
"@babel/core": "^7.4.4",
"@babel/runtime": "^7.4.4",
"babel-eslint": "^10.0.1",
"babel-jest": "^24.7.1",
"eslint": "^5.16.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-config-prettier": "^4.0.0",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-prettier": "^3.0.1",
"eslint-plugin-react": "^7.12.4",
"jest": "^24.7.1",
"jest-fetch-mock": "^1.6.5",
"metro-react-native-babel-preset": "^0.54.0",
"prettier": "^1.17.0",
"react-dom": "16.8.3",
"react-test-renderer": "16.8.3"
}
+22 -23
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
// },
// });
// });
+7 -40
View File
@@ -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
View File
@@ -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
View File
@@ -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,
},
});
+108 -100
View File
@@ -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
View File
@@ -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,
});
+19 -19
View File
@@ -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);
+75 -73
View File
@@ -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,
},
});
+68 -65
View File
@@ -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
View File
@@ -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,
},
});
+65 -80
View File
@@ -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);
+271 -242
View File
@@ -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,
},
});
+38 -37
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>
);
}
}
+21 -21
View File
@@ -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>
);
}
}
+17 -19
View File
@@ -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
View File
@@ -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>
);
}
}
+26 -27
View File
@@ -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>
);
}
}
+24 -24
View File
@@ -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
View File
@@ -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
View File
@@ -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)),
]);
}
+454 -21
View File
@@ -975,6 +975,11 @@ acorn-globals@^4.1.0:
acorn "^6.0.1"
acorn-walk "^6.0.1"
acorn-jsx@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==
acorn-walk@^6.0.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913"
@@ -985,7 +990,7 @@ acorn@^5.5.3:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
acorn@^6.0.1:
acorn@^6.0.1, acorn@^6.0.7:
version "6.1.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f"
integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==
@@ -997,7 +1002,7 @@ agent-base@^4.1.0:
dependencies:
es6-promisify "^5.0.0"
ajv@^6.5.5:
ajv@^6.5.5, ajv@^6.9.1:
version "6.10.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1"
integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==
@@ -1191,6 +1196,14 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
aria-query@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc"
integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=
dependencies:
ast-types-flow "0.0.7"
commander "^2.11.0"
arr-diff@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a"
@@ -1236,6 +1249,14 @@ array-filter@~0.0.0:
resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec"
integrity sha1-fajPLiZijtcygDWB/SH2fKzS7uw=
array-includes@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=
dependencies:
define-properties "^1.1.2"
es-abstract "^1.7.0"
array-map@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
@@ -1288,6 +1309,11 @@ assign-symbols@^1.0.0:
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
ast-types-flow@0.0.7, ast-types-flow@^0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0=
astral-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
@@ -1325,6 +1351,25 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
axobject-query@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9"
integrity sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==
dependencies:
ast-types-flow "0.0.7"
babel-eslint@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/parser" "^7.0.0"
"@babel/traverse" "^7.0.0"
"@babel/types" "^7.0.0"
eslint-scope "3.7.1"
eslint-visitor-keys "^1.0.0"
babel-jest@^24.7.1:
version "24.7.1"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.7.1.tgz#73902c9ff15a7dfbdc9994b0b17fcefd96042178"
@@ -1628,7 +1673,7 @@ chalk@^1.1.1:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2:
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -1765,7 +1810,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@^2.19.0, commander@^2.9.0, commander@~2.20.0:
commander@^2.11.0, commander@^2.19.0, commander@^2.9.0, commander@~2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
@@ -1840,6 +1885,11 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
contains-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=
convert-source-map@^1.1.0, convert-source-map@^1.4.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
@@ -1908,7 +1958,7 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0:
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^6.0.0:
cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
@@ -1931,6 +1981,11 @@ cssstyle@^1.0.0:
dependencies:
cssom "0.3.x"
damerau-levenshtein@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514"
integrity sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
@@ -1947,7 +2002,7 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -1961,7 +2016,7 @@ debug@^3.1.0:
dependencies:
ms "^2.1.1"
debug@^4.1.0, debug@^4.1.1:
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
@@ -1995,7 +2050,7 @@ default-require-extensions@^2.0.0:
dependencies:
strip-bom "^3.0.0"
define-properties@^1.1.2:
define-properties@^1.1.2, define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
@@ -2064,6 +2119,28 @@ diff-sequences@^24.3.0:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==
doctrine@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=
dependencies:
esutils "^2.0.2"
isarray "^1.0.0"
doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==
dependencies:
esutils "^2.0.2"
doctrine@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
dependencies:
esutils "^2.0.2"
dom-serializer@0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
@@ -2122,7 +2199,7 @@ email-validator@^2.0.4:
resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed"
integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==
emoji-regex@^7.0.1:
emoji-regex@^7.0.1, emoji-regex@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
@@ -2171,7 +2248,7 @@ errorhandler@^1.5.0:
accepts "~1.3.3"
escape-html "~1.0.3"
es-abstract@^1.5.1:
es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.5.1, es-abstract@^1.7.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==
@@ -2226,6 +2303,180 @@ escodegen@^1.9.1:
optionalDependencies:
source-map "~0.6.1"
eslint-config-airbnb-base@^13.1.0:
version "13.1.0"
resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.1.0.tgz#b5a1b480b80dfad16433d6c4ad84e6605052c05c"
integrity sha512-XWwQtf3U3zIoKO1BbHh6aUhJZQweOwSt4c2JrPDg9FP3Ltv3+YfEv7jIDB8275tVnO/qOHbfuYg3kzw6Je7uWw==
dependencies:
eslint-restricted-globals "^0.1.1"
object.assign "^4.1.0"
object.entries "^1.0.4"
eslint-config-airbnb@^17.1.0:
version "17.1.0"
resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-17.1.0.tgz#3964ed4bc198240315ff52030bf8636f42bc4732"
integrity sha512-R9jw28hFfEQnpPau01NO5K/JWMGLi6aymiF6RsnMURjTk+MqZKllCqGK/0tOvHkPi/NWSSOU2Ced/GX++YxLnw==
dependencies:
eslint-config-airbnb-base "^13.1.0"
object.assign "^4.1.0"
object.entries "^1.0.4"
eslint-config-prettier@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-4.2.0.tgz#70b946b629cd0e3e98233fd9ecde4cb9778de96c"
integrity sha512-y0uWc/FRfrHhpPZCYflWC8aE0KRJRY04rdZVfl8cL3sEZmOYyaBdhdlQPjKZBnuRMyLVK+JUZr7HaZFClQiH4w==
dependencies:
get-stdin "^6.0.0"
eslint-import-resolver-node@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a"
integrity sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==
dependencies:
debug "^2.6.9"
resolve "^1.5.0"
eslint-module-utils@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a"
integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==
dependencies:
debug "^2.6.8"
pkg-dir "^2.0.0"
eslint-plugin-import@^2.16.0:
version "2.17.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.2.tgz#d227d5c6dc67eca71eb590d2bb62fb38d86e9fcb"
integrity sha512-m+cSVxM7oLsIpmwNn2WXTJoReOF9f/CtLMo7qOVmKd1KntBy0hEcuNZ3erTmWjx+DxRO0Zcrm5KwAvI9wHcV5g==
dependencies:
array-includes "^3.0.3"
contains-path "^0.1.0"
debug "^2.6.9"
doctrine "1.5.0"
eslint-import-resolver-node "^0.3.2"
eslint-module-utils "^2.4.0"
has "^1.0.3"
lodash "^4.17.11"
minimatch "^3.0.4"
read-pkg-up "^2.0.0"
resolve "^1.10.0"
eslint-plugin-jsx-a11y@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.1.tgz#4ebba9f339b600ff415ae4166e3e2e008831cf0c"
integrity sha512-cjN2ObWrRz0TTw7vEcGQrx+YltMvZoOEx4hWU8eEERDnBIU00OTq7Vr+jA7DFKxiwLNv4tTh5Pq2GUNEa8b6+w==
dependencies:
aria-query "^3.0.0"
array-includes "^3.0.3"
ast-types-flow "^0.0.7"
axobject-query "^2.0.2"
damerau-levenshtein "^1.0.4"
emoji-regex "^7.0.2"
has "^1.0.3"
jsx-ast-utils "^2.0.1"
eslint-plugin-prettier@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.1.tgz#19d521e3981f69dd6d14f64aec8c6a6ac6eb0b0d"
integrity sha512-/PMttrarPAY78PLvV3xfWibMOdMDl57hmlQ2XqFeA37wd+CJ7WSxV7txqjVPHi/AAFKd2lX0ZqfsOc/i5yFCSQ==
dependencies:
prettier-linter-helpers "^1.0.0"
eslint-plugin-react@^7.12.4:
version "7.13.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.13.0.tgz#bc13fd7101de67996ea51b33873cd9dc2b7e5758"
integrity sha512-uA5LrHylu8lW/eAH3bEQe9YdzpPaFd9yAJTwTi/i/BKTD7j6aQMKVAdGM/ML72zD6womuSK7EiGtMKuK06lWjQ==
dependencies:
array-includes "^3.0.3"
doctrine "^2.1.0"
has "^1.0.3"
jsx-ast-utils "^2.1.0"
object.fromentries "^2.0.0"
prop-types "^15.7.2"
resolve "^1.10.1"
eslint-restricted-globals@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7"
integrity sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=
eslint-scope@3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=
dependencies:
esrecurse "^4.1.0"
estraverse "^4.1.1"
eslint-scope@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==
dependencies:
esrecurse "^4.1.0"
estraverse "^4.1.1"
eslint-utils@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512"
integrity sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==
eslint-visitor-keys@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==
eslint@^5.16.0:
version "5.16.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea"
integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==
dependencies:
"@babel/code-frame" "^7.0.0"
ajv "^6.9.1"
chalk "^2.1.0"
cross-spawn "^6.0.5"
debug "^4.0.1"
doctrine "^3.0.0"
eslint-scope "^4.0.3"
eslint-utils "^1.3.1"
eslint-visitor-keys "^1.0.0"
espree "^5.0.1"
esquery "^1.0.1"
esutils "^2.0.2"
file-entry-cache "^5.0.1"
functional-red-black-tree "^1.0.1"
glob "^7.1.2"
globals "^11.7.0"
ignore "^4.0.6"
import-fresh "^3.0.0"
imurmurhash "^0.1.4"
inquirer "^6.2.2"
js-yaml "^3.13.0"
json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.3.0"
lodash "^4.17.11"
minimatch "^3.0.4"
mkdirp "^0.5.1"
natural-compare "^1.4.0"
optionator "^0.8.2"
path-is-inside "^1.0.2"
progress "^2.0.0"
regexpp "^2.0.1"
semver "^5.5.1"
strip-ansi "^4.0.0"
strip-json-comments "^2.0.1"
table "^5.2.3"
text-table "^0.2.0"
espree@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a"
integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==
dependencies:
acorn "^6.0.7"
acorn-jsx "^5.0.0"
eslint-visitor-keys "^1.0.0"
esprima@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
@@ -2236,7 +2487,21 @@ esprima@^4.0.0:
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
estraverse@^4.2.0:
esquery@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708"
integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==
dependencies:
estraverse "^4.0.0"
esrecurse@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==
dependencies:
estraverse "^4.1.0"
estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
@@ -2439,6 +2704,11 @@ fast-deep-equal@^2.0.1:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
fast-diff@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
@@ -2511,6 +2781,13 @@ figures@^2.0.0:
dependencies:
escape-string-regexp "^1.0.5"
file-entry-cache@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c"
integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==
dependencies:
flat-cache "^2.0.1"
filename-regex@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
@@ -2567,7 +2844,7 @@ find-cache-dir@^2.0.0:
make-dir "^2.0.0"
pkg-dir "^3.0.0"
find-up@^2.0.0:
find-up@^2.0.0, find-up@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
@@ -2581,6 +2858,20 @@ find-up@^3.0.0:
dependencies:
locate-path "^3.0.0"
flat-cache@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==
dependencies:
flatted "^2.0.0"
rimraf "2.6.3"
write "1.0.3"
flatted@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916"
integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==
for-in@^1.0.1, for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -2675,6 +2966,11 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
gauge@~1.2.5:
version "1.2.7"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-1.2.7.tgz#e9cec5483d3d4ee0ef44b60a7d99e4935e136d93"
@@ -2710,6 +3006,11 @@ get-caller-file@^2.0.1:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-stdin@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
@@ -2769,7 +3070,7 @@ global@^4.3.0:
min-document "^2.19.0"
process "~0.5.1"
globals@^11.1.0:
globals@^11.1.0, globals@^11.7.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
@@ -2963,6 +3264,11 @@ ignore-walk@^3.0.1:
dependencies:
minimatch "^3.0.4"
ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
image-size@^0.6.0:
version "0.6.3"
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.6.3.tgz#e7e5c65bb534bd7cdcedd6cb5166272a85f75fb2"
@@ -2981,6 +3287,14 @@ import-fresh@^2.0.0:
caller-path "^2.0.0"
resolve-from "^3.0.0"
import-fresh@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390"
integrity sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==
dependencies:
parent-module "^1.0.0"
resolve-from "^4.0.0"
import-local@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
@@ -3032,7 +3346,7 @@ inquirer@^3.0.6:
strip-ansi "^4.0.0"
through "^2.3.6"
inquirer@^6.2.0:
inquirer@^6.2.0, inquirer@^6.2.2:
version "6.3.1"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7"
integrity sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==
@@ -3284,7 +3598,7 @@ isarray@0.0.1:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
isarray@1.0.0, isarray@~1.0.0:
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
@@ -3860,6 +4174,11 @@ json-schema@0.2.3:
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
json-stable-stringify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
@@ -3908,6 +4227,13 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"
jsx-ast-utils@^2.0.1, jsx-ast-utils@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.1.0.tgz#0ee4e2c971fb9601c67b5641b71be80faecf0b36"
integrity sha512-yDGDG2DS4JcqhA6blsuYbtsT09xL8AoLuUR2Gb5exrw7UEM19sBcOTq+YBBhrNbl0PUC4R4LnFu+dHg2HKeVvA==
dependencies:
array-includes "^3.0.3"
kind-of@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44"
@@ -3973,7 +4299,7 @@ leven@^2.1.0:
resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA=
levn@~0.3.0:
levn@^0.3.0, levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=
@@ -4852,7 +5178,7 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
object-keys@^1.0.12:
object-keys@^1.0.11, object-keys@^1.0.12:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
@@ -4864,6 +5190,36 @@ object-visit@^1.0.0:
dependencies:
isobject "^3.0.0"
object.assign@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
dependencies:
define-properties "^1.1.2"
function-bind "^1.1.1"
has-symbols "^1.0.0"
object-keys "^1.0.11"
object.entries@^1.0.4:
version "1.1.0"
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519"
integrity sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.12.0"
function-bind "^1.1.1"
has "^1.0.3"
object.fromentries@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.0.tgz#49a543d92151f8277b3ac9600f1e930b189d30ab"
integrity sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==
dependencies:
define-properties "^1.1.2"
es-abstract "^1.11.0"
function-bind "^1.1.1"
has "^1.0.1"
object.getownpropertydescriptors@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
@@ -4942,7 +5298,7 @@ optimist@^0.6.1:
minimist "~0.0.1"
wordwrap "~0.0.2"
optionator@^0.8.1:
optionator@^0.8.1, optionator@^0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=
@@ -5060,6 +5416,13 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
dependencies:
callsites "^3.0.0"
parse-glob@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
@@ -5115,6 +5478,11 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
path-is-inside@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
path-key@^2.0.0, path-key@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
@@ -5173,6 +5541,13 @@ pirates@^4.0.0, pirates@^4.0.1:
dependencies:
node-modules-regexp "^1.0.0"
pkg-dir@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=
dependencies:
find-up "^2.1.0"
pkg-dir@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
@@ -5229,6 +5604,18 @@ preserve@^0.2.0:
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
prettier-linter-helpers@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
dependencies:
fast-diff "^1.1.2"
prettier@^1.17.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.17.0.tgz#53b303676eed22cc14a9f0cec09b477b3026c008"
integrity sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==
pretty-format@24.0.0-alpha.6:
version "24.0.0-alpha.6"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.0.0-alpha.6.tgz#25ad2fa46b342d6278bf241c5d2114d4376fbac1"
@@ -5274,6 +5661,11 @@ progress@2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
integrity sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=
progress@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
promise-polyfill@^7.1.1:
version "7.1.2"
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-7.1.2.tgz#ab05301d8c28536301622d69227632269a70ca3b"
@@ -5823,6 +6215,11 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
regexpp@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==
regexpu-core@^4.5.4:
version "4.5.4"
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae"
@@ -5931,6 +6328,11 @@ resolve-from@^3.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
integrity sha1-six699nWiBvItuZTM17rywoYh0g=
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -5941,7 +6343,7 @@ resolve@1.1.7:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
resolve@^1.10.0, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1:
resolve@^1.10.0, resolve@^1.10.1, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1:
version "1.10.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18"
integrity sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==
@@ -5961,7 +6363,7 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3:
rimraf@2.6.3, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
@@ -6222,6 +6624,15 @@ slash@^2.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
slice-ansi@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==
dependencies:
ansi-styles "^3.2.0"
astral-regex "^1.0.0"
is-fullwidth-code-point "^2.0.0"
slide@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
@@ -6471,7 +6882,7 @@ strip-eof@^1.0.0:
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
strip-json-comments@~2.0.1:
strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
@@ -6505,6 +6916,16 @@ symbol-tree@^3.2.2:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=
table@^5.2.3:
version "5.2.3"
resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2"
integrity sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==
dependencies:
ajv "^6.9.1"
lodash "^4.17.11"
slice-ansi "^2.1.0"
string-width "^3.0.0"
tar@^4:
version "4.4.8"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d"
@@ -6536,6 +6957,11 @@ test-exclude@^5.2.3:
read-pkg-up "^4.0.0"
require-main-filename "^2.0.0"
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
throat@^4.0.0, throat@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"
@@ -6977,6 +7403,13 @@ write-file-atomic@^1.2.0:
imurmurhash "^0.1.4"
slide "^1.1.5"
write@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==
dependencies:
mkdirp "^0.5.1"
ws@^1.1.0, ws@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.5.tgz#cbd9e6e75e09fc5d2c90015f21f0c40875e0dd51"