diff --git a/.eslintrc b/.eslintrc index 8c3380c..4f70013 100644 --- a/.eslintrc +++ b/.eslintrc @@ -12,6 +12,8 @@ "no-underscore-dangle": 0, "no-param-reassign": 0, "react/prefer-stateless-function": 1, + "react/prop-types": 1, // FIXME: refactor + "class-methods-use-this": 1, // FIXME: refactor "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], "react/sort-comp": 0, "max-len": 0, diff --git a/android/fastlane/metadata/android/en-US/images/icon.png b/android/fastlane/metadata/android/en-US/images/icon.png index e186674..fe8f0c5 100644 Binary files a/android/fastlane/metadata/android/en-US/images/icon.png and b/android/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/android/fastlane/metadata/android/no-NO/images/icon.png b/android/fastlane/metadata/android/no-NO/images/icon.png index e186674..fe8f0c5 100644 Binary files a/android/fastlane/metadata/android/no-NO/images/icon.png and b/android/fastlane/metadata/android/no-NO/images/icon.png differ diff --git a/src/App.js b/src/App.js index 4e3b4e3..25bfb95 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React from 'react'; import { Provider } from 'react-redux'; import { PersistGate } from 'redux-persist/es/integration/react'; import Config from 'react-native-config'; @@ -15,16 +15,14 @@ const apolloClient = new ApolloClient({ cache: new InMemoryCache(), }); -export default class App extends Component { - render() { - return ( - - - - - - - - ); - } +export default function App() { + return ( + + + + + + + + ); } diff --git a/src/Dusken.js b/src/Dusken.js index 7744392..d6e666b 100644 --- a/src/Dusken.js +++ b/src/Dusken.js @@ -1,16 +1,12 @@ -import React, { Component } from 'react'; +import React from 'react'; import { connect } from 'react-redux'; import { Root } from 'native-base'; import DuskenNavigation from './navigation'; -class DuskenContainer extends Component { - render() { - return ( - - - - ); - } -} +const DuskenContainer = () => ( + + + +); export default connect()(DuskenContainer); diff --git a/src/api.js b/src/api.js index dd96f18..bd1c621 100644 --- a/src/api.js +++ b/src/api.js @@ -51,7 +51,7 @@ export function requestLogin(username, password) { }; } -export function requestUserData(auth_token) { +export function requestUserData(authToken) { return (dispatch) => { // We are now fetching user data dispatch(userDataRequest()); @@ -61,7 +61,7 @@ export function requestUserData(auth_token) { headers: { Accept: 'application/json', 'Content-Type': 'application/json', - Authorization: `Token ${auth_token}`, + Authorization: `Token ${authToken}`, }, }) .then((response) => response.json().then((json) => ({ json, response }))) @@ -118,7 +118,7 @@ export function requestRegisterUser(firstName, lastName, email, phoneNumber, pas }; } -export function requestMembershipCharge(auth_token, stripe_token, membership_type) { +export function requestMembershipCharge(authToken, stripeToken, membershipType) { return (dispatch) => { dispatch(membershipChargeRequest()); @@ -127,11 +127,11 @@ export function requestMembershipCharge(auth_token, stripe_token, membership_typ headers: { Accept: 'application/json', 'Content-Type': 'application/json', - Authorization: `Token ${auth_token}`, + Authorization: `Token ${authToken}`, }, body: JSON.stringify({ - stripe_token, - membership_type, + stripe_token: stripeToken, + membership_type: membershipType, payment_method: 'app', }), }) diff --git a/src/modules/about/About.js b/src/modules/about/About.js index 3fcb7cd..8999579 100644 --- a/src/modules/about/About.js +++ b/src/modules/about/About.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; -import { Text, ScrollView, View, Platform, StyleSheet, Image, Linking, TouchableOpacity } from 'react-native'; -// import HTMLView from 'react-native-htmlview'; +import { Text, View, Platform, StyleSheet, Image, Linking, TouchableOpacity } from 'react-native'; +import { ScrollView } from 'react-navigation'; import { Button, Text as NBText, Icon } from 'native-base'; import theme from '../../theme'; @@ -140,8 +140,6 @@ export default class About extends Component { 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. - - {/* */} ); diff --git a/src/modules/about/AboutContainer.js b/src/modules/about/AboutContainer.js index dd811ce..47c8b7c 100644 --- a/src/modules/about/AboutContainer.js +++ b/src/modules/about/AboutContainer.js @@ -1,12 +1,7 @@ -import { connect } from 'react-redux'; -import React, { Component } from 'react'; +import React from 'react'; import About from './About'; -class AboutContainer extends Component { - render() { - return ; - } -} +const AboutContainer = () => ; -export default connect()(AboutContainer); +export default AboutContainer; diff --git a/src/modules/events/EventDetail.js b/src/modules/events/EventDetail.js index 9426020..1fed83c 100644 --- a/src/modules/events/EventDetail.js +++ b/src/modules/events/EventDetail.js @@ -3,6 +3,7 @@ import { Linking, StyleSheet, ScrollView, Text, Platform, View, Image, Touchable import HTMLView from 'react-native-htmlview'; import moment from 'moment'; import { Icon } from 'native-base'; + import theme from '../../theme'; moment.locale('nb'); @@ -44,8 +45,8 @@ export default class EventDetail extends Component { _formatTicketText() { /* FIXME: This should be handled by API */ const { item } = this.props; - let reg_kr = ''; - let mem_kr = ''; + let regularPrice = ''; + let memberPrice = ''; if (item.price_regular === '0') { item.price_regular = 'Gratis'; } @@ -53,19 +54,19 @@ export default class EventDetail extends Component { item.price_member = 'Gratis'; } if (item.price_regular !== 'Gratis') { - reg_kr = ' kr'; + regularPrice = ' kr'; } if (item.price_member !== 'Gratis') { - mem_kr = ' kr'; + memberPrice = ' kr'; } let text = ''; if (item.price_regular && item.price_member) { - text = `Pris: ${item.price_regular}${reg_kr} / Medlemmer: ${item.price_member}${mem_kr}`; + text = `Pris: ${item.price_regular}${regularPrice} / Medlemmer: ${item.price_member}${memberPrice}`; } else if (item.price_regular) { - text = `Pris: ${item.price_regular}${reg_kr}`; + text = `Pris: ${item.price_regular}${regularPrice}`; } else if (item.price_member) { - text = `Pris (Medlemmer): ${item.price_member}${mem_kr}`; + text = `Pris (Medlemmer): ${item.price_member}${memberPrice}`; } else { return ''; } @@ -108,7 +109,7 @@ export default class EventDetail extends Component { const text = this._formatTicketText(); if (text === '') { - return; + return null; } if (item.ticket_url) { @@ -141,7 +142,7 @@ export default class EventDetail extends Component { showFacebutton() { const { item } = this.props; if (!item.facebook_url) { - return; + return null; } return ( @@ -162,6 +163,8 @@ export default class EventDetail extends Component { if (item.thumbnail.medium_large) { return ; } + + return null; } showYear() { @@ -170,6 +173,8 @@ export default class EventDetail extends Component { if (moment().year() !== moment(item.start_time).year()) { return {this._formatYear(item.start_time)}; } + + return null; } render() { diff --git a/src/modules/events/EventList.js b/src/modules/events/EventList.js index 57cd24c..fa5a307 100644 --- a/src/modules/events/EventList.js +++ b/src/modules/events/EventList.js @@ -1,6 +1,6 @@ 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 { Card, ListItem, CardItem, Body, Text, Right, Icon, Button, Content, Spinner } from 'native-base'; import React, { Component } from 'react'; import moment from 'moment'; import 'moment/locale/nb'; @@ -64,11 +64,7 @@ export default class EventList extends Component { return ( - { - this._onPressItem(item); - }} - > + Kunne ikke hente programmet... diff --git a/src/modules/events/EventListContainer.js b/src/modules/events/EventListContainer.js index 680cc03..bad24d5 100644 --- a/src/modules/events/EventListContainer.js +++ b/src/modules/events/EventListContainer.js @@ -6,6 +6,7 @@ import moment from 'moment'; import 'moment/locale/nb'; import EventList from './EventList'; +import { fetchWithTimeout } from '../../utils'; moment.locale('nb'); @@ -35,7 +36,7 @@ class EventListContainer extends Component { _toSectionFormat(events) { const sectioned = {}; - events.map((event) => { + events.forEach((event) => { const title = this._formatDate(event.start_time); if (title in sectioned) { sectioned[title].push(event); @@ -84,9 +85,9 @@ class EventListContainer extends Component { const url = `${Config.EVENT_API_URL}/wp-json/wp/v2/events?page=${page}&future=1`; this.setState({ loading: true }); - fetch(url) + fetchWithTimeout(url) .then((res) => { - this.setState({ totalPages: parseInt(res.headers.get('x-wp-totalpages')) }); + this.setState({ totalPages: parseInt(res.headers.get('x-wp-totalpages'), 10) }); return res.json(); }) .then((res) => { diff --git a/src/modules/login/Login.js b/src/modules/login/Login.js index 0f945da..2888a42 100644 --- a/src/modules/login/Login.js +++ b/src/modules/login/Login.js @@ -1,6 +1,6 @@ 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 { Linking, StyleSheet, View } from 'react-native'; +import { Container, Content, Form, Item, Input, Label, Spinner, Button, Text } from 'native-base'; import Config from 'react-native-config'; import theme from '../../theme'; @@ -26,7 +26,7 @@ export default class Login extends Component { returnKeyType="next" onChangeText={this.handleEmail} onSubmitEditing={() => { - this.refs.passwordInput._root.focus(); + this.passwordInputRef._root.focus(); }} /> @@ -44,7 +44,7 @@ export default class Login extends Component { returnKeyType="next" onChangeText={this.handleEmail} onSubmitEditing={() => { - this.refs.passwordInput._root.focus(); + this.passwordInputRef._root.focus(); }} /> @@ -57,7 +57,9 @@ export default class Login extends Component { { + this.passwordInputRef = ref; + }} secureTextEntry={true} autoCapitalize="none" autoCorrect={false} @@ -71,7 +73,9 @@ export default class Login extends Component { { + this.passwordInputRef = ref; + }} secureTextEntry={true} autoCapitalize="none" autoCorrect={false} @@ -125,11 +129,7 @@ export default class Login extends Component { return ; }; - showSpinner = () => { - if (this.props.isLoggingIn) { - return ; - } - }; + showSpinner = () => (this.props.isLoggingIn ? : null); handleEmail = (text) => { this.setState({ email: text }); diff --git a/src/modules/membership/Proof.js b/src/modules/membership/Proof.js index 03f7011..e42d4e3 100644 --- a/src/modules/membership/Proof.js +++ b/src/modules/membership/Proof.js @@ -44,7 +44,7 @@ export default class Proof extends Component { membershipValidTo() { if (!this.props.user.last_membership) { - return; + return null; } let validTo = this.props.user.last_membership.end_date; @@ -63,7 +63,7 @@ export default class Proof extends Component { membershipStatus() { if (!this.props.user.is_member) { if (!this.props.user.last_membership) { - return; + return null; } return ( @@ -86,7 +86,7 @@ export default class Proof extends Component { purchaseButton() { if (this.props.user.is_member) { - return; + return null; } return ( @@ -108,15 +108,24 @@ export default class Proof extends Component { } confetti() { - if (this.props.user.is_member) { - // Confetti!!!! - return (this._confettiView = node)} confettiCount={Number.MAX_SAFE_INTEGER} />; + if (!this.props.user.is_member) { + return null; } + + // Confetti!!!! + return ( + { + this._confettiView = node; + }} + confettiCount={Number.MAX_SAFE_INTEGER} + /> + ); } logo() { if (!this.props.user.is_member) { - return; + return null; } // FIXME: URL to config @@ -133,7 +142,7 @@ export default class Proof extends Component { chargeError() { if (!this.props.chargeError) { - return; + return null; } return ( diff --git a/src/modules/membership/ProofContainer.js b/src/modules/membership/ProofContainer.js index 06c722a..8ef56e2 100644 --- a/src/modules/membership/ProofContainer.js +++ b/src/modules/membership/ProofContainer.js @@ -62,7 +62,7 @@ class ProofContainer extends Component { membershipTypeSlug ); }, - (error) => { + () => { /* FIXME: Happens only if card dialog is canceled? */ } ); diff --git a/src/modules/register/UserRegister.js b/src/modules/register/UserRegister.js index 71babfb..42efe9f 100644 --- a/src/modules/register/UserRegister.js +++ b/src/modules/register/UserRegister.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; -import { Container, Header, Content, Form, Item, Input, Label, Spinner, Button, Text, Icon, Toast } from 'native-base'; +import { Container, 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 * as EmailValidator from 'email-validator'; import theme from '../../theme'; @@ -27,7 +27,7 @@ export default class UserRegister extends Component { const touched = new Set(this.state.touched).add(triggeredBy); /* No empty fields */ - for (const key of fieldNames) { + fieldNames.forEach((key) => { const value = this.state[key]; if (value === '') { errors[key] = 'kan ikke være tomt'; @@ -36,7 +36,7 @@ export default class UserRegister extends Component { } else { delete errors[key]; } - } + }); this.setState({ errors, touched }); } @@ -62,7 +62,7 @@ export default class UserRegister extends Component { }); }} onSubmitEditing={() => { - this.refs.lastNameInput._root.focus(); + this.lastNameInputRef._root.focus(); }} value={this.state.firstName} /> @@ -76,7 +76,9 @@ export default class UserRegister extends Component { { + this.lastNameInputRef = ref; + }} returnKeyType="next" onChangeText={(lastName) => { this.setState({ lastName }, () => { @@ -84,7 +86,7 @@ export default class UserRegister extends Component { }); }} onSubmitEditing={() => { - this.refs.emailInput._root.focus(); + this.emailInputRef._root.focus(); }} value={this.state.lastName} /> @@ -98,7 +100,9 @@ export default class UserRegister extends Component { { + this.emailInputRef = ref; + }} keyboardType="email-address" autoCapitalize="none" autoCorrect={false} @@ -109,7 +113,7 @@ export default class UserRegister extends Component { }); }} onSubmitEditing={() => { - this.refs.phoneInput._root.focus(); + this.phoneInputRef._root.focus(); }} value={this.state.email} /> @@ -123,7 +127,9 @@ export default class UserRegister extends Component { { + this.phoneInputRef = ref; + }} returnKeyType="next" keyboardType="phone-pad" onChangeText={(phoneNumber) => { @@ -132,7 +138,7 @@ export default class UserRegister extends Component { }); }} onSubmitEditing={() => { - this.refs.passwordInput._root.focus(); + this.passwordInputRef._root.focus(); }} value={this.state.phoneNumber} /> @@ -146,7 +152,9 @@ export default class UserRegister extends Component { { + this.passwordInputRef = ref; + }} secureTextEntry={true} autoCapitalize="none" autoCorrect={false} @@ -195,7 +203,7 @@ export default class UserRegister extends Component { showFieldError = (field) => { if (!this.fieldHasError(field)) { - return; + return null; } return ( @@ -205,9 +213,10 @@ export default class UserRegister extends Component { }; showSpinner = () => { - if (this.props.isRegisteringUser) { - return ; + if (!this.props.isRegisteringUser) { + return null; } + return ; }; render() { diff --git a/src/navigation.js b/src/navigation.js index e04782e..7184d39 100644 --- a/src/navigation.js +++ b/src/navigation.js @@ -62,11 +62,24 @@ const tabBarOptions = { const tabNav = createBottomTabNavigator(tabBarRoutes, tabBarOptions); -const DuskenNavigation = createStackNavigator({ - Root: tabNav, - Login: LoginScreen, - UserRegister: UserRegisterScreen, - EventDetail: EventDetailScreen, -}); +const DuskenNavigation = createStackNavigator( + { + Root: tabNav, + Login: LoginScreen, + UserRegister: UserRegisterScreen, + EventDetail: EventDetailScreen, + }, + { + defaultNavigationOptions: { + headerStyle: { + backgroundColor: '#f58220', + }, + headerTitleStyle: { + color: 'white', + }, + headerTintColor: 'white', + }, + } +); export default createAppContainer(DuskenNavigation); diff --git a/src/reducers.js b/src/reducers.js index aaf9731..31b17f2 100644 --- a/src/reducers.js +++ b/src/reducers.js @@ -33,9 +33,9 @@ const initialState = { function formatErrors(errs) { const _errs = {}; - for (const [key, value] of Object.entries(errs)) { + Object.entries(errs).forEach(([key, value]) => { _errs[snakeToCamelCase(key)] = Array.isArray(value) ? value.join('\n') : value; - } + }); return _errs; } @@ -97,7 +97,9 @@ export default function duskenApp(state = initialState, action) { }); case REGISTER_USER_SUCCESS: + // eslint-disable-next-line no-case-declarations const userData = action.data; + // eslint-disable-next-line no-case-declarations const token = action.data.auth_token; delete userData.auth_token; diff --git a/src/screens/AboutScreen.js b/src/screens/AboutScreen.js index 65a4be9..c6958c4 100644 --- a/src/screens/AboutScreen.js +++ b/src/screens/AboutScreen.js @@ -1,21 +1,21 @@ -import React, { Component } from 'react'; +import React from 'react'; import Icon from 'react-native-vector-icons/SimpleLineIcons'; import { StyleProvider } from 'native-base'; 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 }) => , - }; +const AboutScreen = () => ( + + + +); - render() { - return ( - - - - ); - } -} +const TabBarIcon = ({ tintColor }) => ; + +AboutScreen.navigationOptions = { + title: 'Om oss', + tabBarIcon: TabBarIcon, +}; + +export default AboutScreen; diff --git a/src/screens/EventDetailScreen.js b/src/screens/EventDetailScreen.js index 3697aeb..267ae4d 100644 --- a/src/screens/EventDetailScreen.js +++ b/src/screens/EventDetailScreen.js @@ -1,26 +1,21 @@ -import React, { Component } from 'react'; +import React from 'react'; import { StyleProvider } from 'native-base'; +import { useNavigationParam } from 'react-navigation-hooks/src/Hooks'; + 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', - }); +const EventDetailScreen = () => { + const item = useNavigationParam('item'); - render() { - const { params } = this.props.navigation.state; - return ( - - - - ); - } -} + return ( + + + + ); +}; +EventDetailScreen.navigationOptions = ({ navigation }) => ({ + title: navigation.state.params.item.title.decoded, +}); + +export default EventDetailScreen; diff --git a/src/screens/EventListScreen.js b/src/screens/EventListScreen.js index a911692..5f07d93 100644 --- a/src/screens/EventListScreen.js +++ b/src/screens/EventListScreen.js @@ -1,25 +1,27 @@ -import React, { Component } from 'react'; +import React from 'react'; import Icon from 'react-native-vector-icons/SimpleLineIcons'; import { StyleProvider } from 'native-base'; +import { useNavigation } from 'react-navigation-hooks'; 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 }) => , - }; +const EventsScreen = () => { + const { navigate } = useNavigation(); + return ( + + { + navigate('EventDetail', { item }); + }} + /> + + ); +}; - showEvent = (item) => { - const { navigate } = this.props.navigation; - navigate('EventDetail', { item }); - }; +const tabBarIcon = ({ tintColor }) => ; - render() { - return ( - - - - ); - } -} +EventsScreen.navigationOptions = { + title: 'Program', + tabBarIcon, +}; +export default EventsScreen; diff --git a/src/screens/LoginScreen.js b/src/screens/LoginScreen.js index a12ba87..2aa43f8 100644 --- a/src/screens/LoginScreen.js +++ b/src/screens/LoginScreen.js @@ -1,30 +1,19 @@ -import React, { Component } from 'react'; +import React from 'react'; import { StyleProvider } from 'native-base'; +import { useNavigation } from 'react-navigation-hooks'; 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', - }; - - onLogin = () => { - // This works since LoginScreen is allways navigated to from MembershipScreen - this.props.navigation.goBack(); - }; - - render() { - return ( - - - - ); - } -} +// navigation.goBack onLogin works since LoginScreen is allways navigated to from MembershipScreen +const LoginScreen = () => { + const navigation = useNavigation(); + return ( + + + + ); +}; +LoginScreen.navigationOptions = { + title: 'Logg inn', +}; +export default LoginScreen; diff --git a/src/screens/UserRegisterScreen.js b/src/screens/UserRegisterScreen.js index 8cfc626..d63c0fa 100644 --- a/src/screens/UserRegisterScreen.js +++ b/src/screens/UserRegisterScreen.js @@ -6,13 +6,6 @@ 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', }; onRegister = () => { diff --git a/src/utils.js b/src/utils.js index cd3930b..adb09b6 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,3 +1,4 @@ +/* global fetch */ export function snakeToCamelCase(val) { return val.replace(/_([a-z])/g, function(g) { return g[1].toUpperCase();