Merge branch 'nikolaik/render-html' into 'main'
Migrate to react-native-render-html See merge request edb/cnapp!1
This commit is contained in:
commit
4585a96f60
27 files changed
+5103
-652
No files matched your search
@@ -0,0 +1,32 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'@react-native',
|
||||
'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/prop-types': 'off', // FIXME: typescript
|
||||
'class-methods-use-this': 1, // FIXME: refactor
|
||||
'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
|
||||
'react-native/no-inline-styles': 'off', // FIXME: refactor
|
||||
},
|
||||
plugins: ['prettier'],
|
||||
parser: '@babel/eslint-parser',
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
};
|
||||
+1
-1
@@ -29,7 +29,7 @@ yarn-error.*
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
.env*
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
@@ -1,3 +1,3 @@
|
||||
import App from './src/App'
|
||||
import App from './src/App';
|
||||
|
||||
export default App
|
||||
export default App;
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
plugins: ['react-native-reanimated/plugin'],
|
||||
};
|
||||
@@ -1,13 +1,13 @@
|
||||
// FIXME: This is never loaded by expo
|
||||
import { AppRegistry } from 'react-native';
|
||||
import * as Sentry from '@sentry/react-native';
|
||||
import Config from 'react-native-config';
|
||||
// import * as Sentry from '@sentry/react-native';
|
||||
|
||||
import App from './src/App';
|
||||
|
||||
import { name as appName } from './app.json';
|
||||
|
||||
if (Config.SENTRY_DSN) {
|
||||
Sentry.init({ dsn: Config.SENTRY_DSN });
|
||||
}
|
||||
// if (Config.SENTRY_DSN) {
|
||||
// Sentry.init({ dsn: Config.SENTRY_DSN });
|
||||
// }
|
||||
|
||||
AppRegistry.registerComponent(appName, () => App);
|
||||
Generated
+4872
-508
File diff suppressed because it is too large.
Load diff
+21
-7
@@ -6,10 +6,12 @@
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web"
|
||||
"web": "expo start --web",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.17",
|
||||
"@expo/vector-icons": "^13.0.0",
|
||||
"@react-native-async-storage/async-storage": "1.18.2",
|
||||
"@react-navigation/bottom-tabs": "^6.5.8",
|
||||
"@react-navigation/native": "^6.1.7",
|
||||
@@ -24,23 +26,35 @@
|
||||
"native-base": "^3.4.28",
|
||||
"react": "18.2.0",
|
||||
"react-hook-form": "^7.45.2",
|
||||
"react-native": "0.72.3",
|
||||
"react-native": "0.72.4",
|
||||
"react-native-confetti": "^0.1.0",
|
||||
"react-native-gesture-handler": "~2.12.0",
|
||||
"react-native-htmlview": "^0.16.0",
|
||||
"react-native-gesture-handler": "2.12.0",
|
||||
"react-native-reanimated": "3.3.0",
|
||||
"react-native-render-html": "^6.3.4",
|
||||
"react-native-safe-area-context": "4.6.3",
|
||||
"react-native-screens": "~3.22.0",
|
||||
"react-native-svg": "13.9.0",
|
||||
"react-native-vector-icons": "^10.0.0",
|
||||
"react-native-ui-lib": "^7.8.0",
|
||||
"react-redux": "^8.1.2",
|
||||
"redux": "^4.2.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^2.4.2",
|
||||
"styled-components": "^6.0.6",
|
||||
"styled-system": "^5.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0"
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/eslint-parser": "^7.22.15",
|
||||
"@react-native/eslint-config": "^0.72.2",
|
||||
"@testing-library/react-native": "^12.3.0",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"prettier": "^3.0.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
@@ -3,6 +3,9 @@ import { Provider } from 'react-redux';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
import { ApolloClient, HttpLink, InMemoryCache, ApolloProvider } from '@apollo/client';
|
||||
import { StripeProvider } from '@stripe/stripe-react-native';
|
||||
import { LogBox } from 'react-native';
|
||||
|
||||
import { loadTheme } from './theme';
|
||||
|
||||
import Dusken from './Dusken';
|
||||
import { store, persistor } from './reduxStore';
|
||||
@@ -11,6 +14,10 @@ const apolloClient = new ApolloClient({
|
||||
link: new HttpLink({ uri: `${process.env.EXPO_PUBLIC_DUSKEN_API_URL}/api/graphql/` }),
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
// FIXME: Replace unmaintained native-base with https://wix.github.io/react-native-ui-lib/ which produces this warning
|
||||
LogBox.ignoreLogs(['In React 18, SSRProvider is not necessary and is a noop. You can remove it from your app.']);
|
||||
|
||||
loadTheme();
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
||||
+7
-4
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import Icon from 'react-native-vector-icons/SimpleLineIcons';
|
||||
import { SimpleLineIcons as Icon } from '@expo/vector-icons';
|
||||
|
||||
import EventList from './modules/events/EventList';
|
||||
import EventDetail from './modules/events/EventDetail';
|
||||
@@ -46,6 +46,9 @@ const UserStackScreen = () => (
|
||||
);
|
||||
|
||||
const HomeTabs = createBottomTabNavigator();
|
||||
const EventIcon = ({ color, size }) => <Icon name="event" size={size} color={color} style={{ marginTop: 5 }} />;
|
||||
const UserIcon = ({ color, size }) => <Icon name="user" size={size} color={color} style={{ marginTop: 5 }} />;
|
||||
const AboutIcon = ({ color, size }) => <Icon name="info" size={size} color={color} style={{ marginTop: 5 }} />;
|
||||
const Navigation = () => (
|
||||
<NavigationContainer>
|
||||
<HomeTabs.Navigator initialRouteName="EventStack">
|
||||
@@ -55,7 +58,7 @@ const Navigation = () => (
|
||||
options={{
|
||||
headerShown: false,
|
||||
tabBarLabel: 'Program',
|
||||
tabBarIcon: ({ color, size }) => <Icon name="event" size={size} color={color} style={{ marginTop: 5 }} />,
|
||||
tabBarIcon: EventIcon,
|
||||
}}
|
||||
/>
|
||||
<HomeTabs.Screen
|
||||
@@ -64,7 +67,7 @@ const Navigation = () => (
|
||||
options={{
|
||||
headerShown: false,
|
||||
tabBarLabel: 'Medlemskap',
|
||||
tabBarIcon: ({ color, size }) => <Icon name="user" size={size} color={color} style={{ marginTop: 5 }} />,
|
||||
tabBarIcon: UserIcon,
|
||||
}}
|
||||
/>
|
||||
<HomeTabs.Screen
|
||||
@@ -72,7 +75,7 @@ const Navigation = () => (
|
||||
component={About}
|
||||
options={{
|
||||
title: 'Om oss',
|
||||
tabBarIcon: ({ color, size }) => <Icon name="info" size={size} color={color} style={{ marginTop: 5 }} />,
|
||||
tabBarIcon: AboutIcon,
|
||||
}}
|
||||
/>
|
||||
</HomeTabs.Navigator>
|
||||
|
||||
+4
-4
@@ -44,7 +44,7 @@ export function requestLogin(username, password) {
|
||||
(data) => {
|
||||
console.log(data);
|
||||
dispatch(loginFailure(data || { non_field_errors: ['Kunne ikke logge inn, prøv igjen'] }));
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -75,7 +75,7 @@ export function requestUserData(authToken) {
|
||||
},
|
||||
(data) => {
|
||||
dispatch(userDataFailure(data || { non_field_errors: ['Kunne ikke hente brukerdata'] }));
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -111,7 +111,7 @@ export function requestRegisterUser({ firstName, lastName, email, phoneNumber, p
|
||||
},
|
||||
(data) => {
|
||||
dispatch(registerUserFailure(data || { non_field_errors: ['Kunne ikke registrere bruker, prøv igjen'] }));
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -146,7 +146,7 @@ export function requestMembershipCharge(authToken, stripeToken, membershipType)
|
||||
},
|
||||
(data) => {
|
||||
dispatch(membershipChargeFailure(data.detail || 'Kunne ikke belaste bankkortet, prøv igjen'));
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Alert, HStack, Text } from 'native-base';
|
||||
import { Text } from 'react-native';
|
||||
import { Alert, HStack } from 'native-base';
|
||||
|
||||
const FormErrors = ({ errors }) => {
|
||||
if (!Object.keys(errors).length || !['non_field_errors', 'detail'].some((errorField) => errorField in errors)) {
|
||||
|
||||
+16
-20
@@ -1,9 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Text, View, Platform, StyleSheet, Image, Linking, TouchableOpacity, ScrollView } from 'react-native';
|
||||
|
||||
import { Button, Text as NBText } from 'native-base';
|
||||
import FAIcon from 'react-native-vector-icons/FontAwesome5';
|
||||
import SimpleIcon from 'react-native-vector-icons/SimpleLineIcons';
|
||||
import { SimpleLineIcons as SimpleIcon, FontAwesome5 as FAIcon } from '@expo/vector-icons';
|
||||
import { Button } from 'react-native-ui-lib';
|
||||
import theme from '../../theme';
|
||||
|
||||
const CHATEAU_NEUF_ADDRESS = 'Det Norske Studentersamfund, Slemdalsveien 15, 0369 Oslo';
|
||||
@@ -71,11 +70,11 @@ const About = () => (
|
||||
<View style={[styles.section, { flex: 1, flexDirection: 'row' }]}>
|
||||
<TouchableOpacity style={styles.someBtn} onPress={openFacebook}>
|
||||
<FAIcon name="facebook" size={40} color="black" />
|
||||
<NBText>Facebook</NBText>
|
||||
<Text>Facebook</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.someBtn} onPress={openInstagram}>
|
||||
<FAIcon name="instagram" size={40} color="black" />
|
||||
<NBText>Instagram</NBText>
|
||||
<Text>Instagram</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
@@ -84,37 +83,33 @@ const About = () => (
|
||||
Det Norske Studentersamfund{'\n'}Slemdalsveien 15, 0369 Oslo
|
||||
</Text>
|
||||
<View style={styles.section}>
|
||||
<Button leftIcon={<SimpleIcon name="map" style={styles.icon} />} onPress={openMap}>
|
||||
<NBText> Vis i kart</NBText>
|
||||
<Button onPress={openMap}>
|
||||
<SimpleIcon name="map" style={styles.icon} />
|
||||
<Text> Vis i kart</Text>
|
||||
</Button>
|
||||
</View>
|
||||
<Text style={styles.header}>Åpningstider</Text>
|
||||
<View style={styles.section}>
|
||||
<Button
|
||||
leftIcon={<SimpleIcon name="clock" style={styles.icon} />}
|
||||
onPress={() => Linking.openURL('https://studentersamfundet.no/aapningstider/')}
|
||||
>
|
||||
<NBText>Vis åpningstider</NBText>
|
||||
<Button onPress={() => Linking.openURL('https://studentersamfundet.no/aapningstider/')}>
|
||||
<SimpleIcon name="clock" style={styles.icon} />
|
||||
<Text>Vis åpningstider</Text>
|
||||
</Button>
|
||||
</View>
|
||||
<Text style={styles.header}>Ta kontakt</Text>
|
||||
<View style={[styles.section, { flex: 1, flexDirection: 'row' }]}>
|
||||
<View>
|
||||
<Button
|
||||
leftIcon={<SimpleIcon name="phone" style={styles.icon} />}
|
||||
onPress={() => Linking.openURL('tel:+4794430002')}
|
||||
style={{ marginHorizontal: 10 }}
|
||||
>
|
||||
<NBText>Ring</NBText>
|
||||
<Button onPress={() => Linking.openURL('tel:+4794430002')} style={{ marginHorizontal: 10 }}>
|
||||
<SimpleIcon name="phone" style={styles.icon} />
|
||||
<Text>Ring</Text>
|
||||
</Button>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
leftIcon={<SimpleIcon name="envelope" style={styles.icon} />}
|
||||
onPress={() => Linking.openURL('mailto:ledelsen@studentersamfundet.no')}
|
||||
style={{ marginHorizontal: 10 }}
|
||||
>
|
||||
<NBText>Send e-post</NBText>
|
||||
<SimpleIcon name="envelope" style={styles.icon} />
|
||||
<Text>Send e-post</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
@@ -173,6 +168,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
icon: {
|
||||
fontSize: 16,
|
||||
marginRight: 4,
|
||||
},
|
||||
largeIcon: {
|
||||
fontSize: 40,
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Linking, StyleSheet, ScrollView, Text, Platform, View, Image, TouchableOpacity } from 'react-native';
|
||||
import HTMLView from 'react-native-htmlview';
|
||||
import {
|
||||
Linking,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
Text,
|
||||
Platform,
|
||||
View,
|
||||
Image,
|
||||
TouchableOpacity,
|
||||
useWindowDimensions,
|
||||
} from 'react-native';
|
||||
import RenderHtml from 'react-native-render-html';
|
||||
import moment from 'moment';
|
||||
import { Icon } from 'native-base';
|
||||
import { FontAwesome } from '@expo/vector-icons';
|
||||
|
||||
import theme from '../../theme';
|
||||
|
||||
@@ -90,7 +100,7 @@ const PriceAndTicket = ({ event }) => {
|
||||
return (
|
||||
<TouchableOpacity style={styles.metaInner} onPress={() => Linking.openURL(event.ticket_url)}>
|
||||
<View style={styles.metaIcon}>
|
||||
<Icon name="card" style={styles.icons} />
|
||||
<FontAwesome name="credit-card" style={styles.icons} />
|
||||
</View>
|
||||
<View style={{ flexDirection: 'column' }}>
|
||||
<Text style={styles.metaText}>{text}</Text>
|
||||
@@ -103,7 +113,7 @@ const PriceAndTicket = ({ event }) => {
|
||||
return (
|
||||
<View style={styles.metaInner}>
|
||||
<View style={styles.metaIcon}>
|
||||
<Icon name="card" style={styles.icons} />
|
||||
<FontAwesome name="credit-card" style={styles.icons} />
|
||||
</View>
|
||||
<View style={{ flexDirection: 'column' }}>
|
||||
<Text style={styles.metaText}>{text}</Text>
|
||||
@@ -121,7 +131,7 @@ const FaceButton = ({ event }) => {
|
||||
return (
|
||||
<TouchableOpacity onPress={() => onFacebookUrlPress(event.facebook_url)} style={styles.metaInner}>
|
||||
<View style={[styles.metaIcon, { marginTop: 0 }]}>
|
||||
<Icon name="logo-facebook" style={styles.icons} />
|
||||
<FontAwesome name="facebook-square" style={styles.icons} />
|
||||
</View>
|
||||
<View>
|
||||
<Text style={[styles.metaText, { paddingTop: 2 }]}>På Facebook</Text>
|
||||
@@ -147,53 +157,56 @@ const EventDetail = ({
|
||||
route: {
|
||||
params: { event },
|
||||
},
|
||||
}) => (
|
||||
<ScrollView>
|
||||
{event.thumbnail.medium_large && <Image source={{ uri: event.thumbnail.medium_large }} style={styles.image} />}
|
||||
<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}>{format(event.start_time, FORMAT_DAY)}</Text>
|
||||
<Text style={styles.month}>{format(event.start_time, FORMAT_MONTH)}</Text>
|
||||
<Year startTime={event.start_time} />
|
||||
}) => {
|
||||
const { width } = useWindowDimensions();
|
||||
return (
|
||||
<ScrollView>
|
||||
{event.thumbnail.medium_large && <Image source={{ uri: event.thumbnail.medium_large }} style={styles.image} />}
|
||||
<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}>{format(event.start_time, FORMAT_DAY)}</Text>
|
||||
<Text style={styles.month}>{format(event.start_time, FORMAT_MONTH)}</Text>
|
||||
<Year startTime={event.start_time} />
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title} numberOfLines={2}>
|
||||
{event.title.decoded}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title} numberOfLines={2}>
|
||||
{event.title.decoded}
|
||||
</Text>
|
||||
</View>
|
||||
{/* Time and calendar */}
|
||||
<TouchableOpacity style={styles.metaInner}>
|
||||
<View style={styles.metaIcon}>
|
||||
<FontAwesome name="clock-o" style={styles.icons} />
|
||||
</View>
|
||||
<View style={{ flexDirection: 'column' }}>
|
||||
<Text style={styles.metaText}>{formatTimeFull(event)}</Text>
|
||||
<Text style={styles.metaSubtitle}>{formatRelative(event.start_time)}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
{/* Location and maps */}
|
||||
<TouchableOpacity style={styles.metaInner} onPress={() => onLocationPress(event.venue)}>
|
||||
<View style={[styles.metaIcon, { paddingLeft: 1 }]}>
|
||||
<FontAwesome name="map-pin" style={styles.icons} />
|
||||
</View>
|
||||
<View style={{ flexDirection: 'column' }}>
|
||||
<Text style={styles.metaText}>{event.venue}</Text>
|
||||
<Text style={styles.metaSubtitle} numberOfLines={1}>
|
||||
<VenueLocation venue={event.venue} />
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<PriceAndTicket event={event} />
|
||||
<FaceButton event={event} />
|
||||
<RenderHtml contentWidth={width} source={{ html: event.content.rendered }} />
|
||||
</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}>{formatTimeFull(event)}</Text>
|
||||
<Text style={styles.metaSubtitle}>{formatRelative(event.start_time)}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
{/* Location and maps */}
|
||||
<TouchableOpacity style={styles.metaInner} onPress={() => onLocationPress(event.venue)}>
|
||||
<View style={[styles.metaIcon, { paddingLeft: 1 }]}>
|
||||
<Icon name="pin" style={styles.icons} />
|
||||
</View>
|
||||
<View style={{ flexDirection: 'column' }}>
|
||||
<Text style={styles.metaText}>{event.venue}</Text>
|
||||
<Text style={styles.metaSubtitle} numberOfLines={1}>
|
||||
<VenueLocation venue={event.venue} />
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<PriceAndTicket event={event} />
|
||||
<FaceButton event={event} />
|
||||
<HTMLView style={styles.content} stylesheet={HTMLStyles} value={event.content.rendered} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventDetail;
|
||||
|
||||
@@ -274,21 +287,6 @@ const styles = StyleSheet.create({
|
||||
paddingTop: 3,
|
||||
},
|
||||
icons: {
|
||||
fontSize: 16,
|
||||
},
|
||||
});
|
||||
|
||||
// FIXME: Ninja margin
|
||||
const HTMLStyles = StyleSheet.create({
|
||||
p: {
|
||||
fontSize: 16,
|
||||
marginBottom: -30,
|
||||
color: '#000',
|
||||
},
|
||||
ul: {
|
||||
fontSize: 16,
|
||||
color: '#000',
|
||||
marginTop: -20,
|
||||
marginBottom: -30,
|
||||
fontSize: 15,
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,7 @@
|
||||
import { StyleSheet, View, Platform, SectionList, TouchableOpacity } from 'react-native';
|
||||
import { Card, Text, Button, Spinner, ArrowForwardIcon, HStack } from 'native-base';
|
||||
import { Card, Text, Spinner, ArrowForwardIcon, HStack } from 'native-base';
|
||||
import { Button } from 'react-native-ui-lib';
|
||||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import 'moment/locale/nb';
|
||||
@@ -18,6 +20,7 @@ const renderSectionHeader = ({ section }) => (
|
||||
<Text style={styles.sectionTitle}>{section.title}</Text>
|
||||
</View>
|
||||
);
|
||||
const Header = () => <View style={styles.listHeader} />;
|
||||
|
||||
const EventList = () => {
|
||||
const { eventsSectioned, refreshing, handleRefresh, handleLoadMore, showEvent, error, loading } = useEventList();
|
||||
@@ -43,6 +46,8 @@ const EventList = () => {
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
// FIXME: Hoist component definition
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
const Footer = () =>
|
||||
!loading && (
|
||||
<View style={{ paddingVertical: 20 }}>
|
||||
@@ -80,9 +85,7 @@ const EventList = () => {
|
||||
initialNumToRender={10}
|
||||
ListFooterComponent={Footer}
|
||||
style={[styles.card, styles.list]}
|
||||
ListHeaderComponent={() => {
|
||||
return <View style={styles.listHeader} />;
|
||||
}}
|
||||
ListHeaderComponent={Header}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -77,6 +77,7 @@ const useEventList = () => {
|
||||
|
||||
useEffect(() => {
|
||||
fetchEvents();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Linking, StyleSheet, View } from 'react-native';
|
||||
import { ScrollView, Spinner, Button, Text } from 'native-base';
|
||||
import { ActivityIndicator, Linking, StyleSheet, View, Text } from 'react-native';
|
||||
import { ScrollView } from 'native-base';
|
||||
import { Button } from 'react-native-ui-lib';
|
||||
|
||||
import theme from '../../theme';
|
||||
import useLogin from './useLogin';
|
||||
@@ -36,7 +37,7 @@ const Login = () => {
|
||||
onSubmitEditing={onSubmit}
|
||||
returnKeyType="send"
|
||||
/>
|
||||
<Button full onPress={onSubmit} style={styles.loginButton}>
|
||||
<Button fullWidth onPress={onSubmit} style={styles.loginButton}>
|
||||
<Text>Logg inn</Text>
|
||||
</Button>
|
||||
<Button
|
||||
@@ -49,7 +50,7 @@ const Login = () => {
|
||||
>
|
||||
<Text>Glemt passord?</Text>
|
||||
</Button>
|
||||
{isLoggingIn && <Spinner color="#f58220" />}
|
||||
{isLoggingIn && <ActivityIndicator color="#f58220" />}
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useNavigation } from '@react-navigation/native';
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { requestLogin } from '../../api';
|
||||
|
||||
const useLogin = () => {
|
||||
@@ -31,7 +32,7 @@ const useLogin = () => {
|
||||
// navigation.goBack onLogin works since LoginScreen is allways navigated to from MembershipScreen
|
||||
navigation.goBack();
|
||||
}
|
||||
}, [isAuthenticated]);
|
||||
}, [isAuthenticated, navigation]);
|
||||
|
||||
return { onSubmit, isLoggingIn, control, setFocus, errors: { ...errors, ...loginError } };
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { Spinner } from 'native-base';
|
||||
import { View, StyleSheet, ActivityIndicator } from 'react-native';
|
||||
|
||||
import theme from '../../theme';
|
||||
import useMembership from './useMembership';
|
||||
@@ -14,7 +13,7 @@ const Membership = () => {
|
||||
if (isFetchingUserData) {
|
||||
return (
|
||||
<View style={styles.card}>
|
||||
<Spinner color="#f58220" />
|
||||
<ActivityIndicator color="#f58220" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ScrollView, Text, View, StyleSheet } from 'react-native';
|
||||
import { Button, Text as NBText } from 'native-base';
|
||||
import { Button } from 'react-native-ui-lib';
|
||||
import theme from '../../theme';
|
||||
|
||||
const MembershipInfo = ({ onRegister, onLogin }) => (
|
||||
@@ -12,8 +12,8 @@ const MembershipInfo = ({ onRegister, onLogin }) => (
|
||||
Her kan du bli medlem i Det Norske Studentersamfund - Chateau Neuf og ha medlemsbeviset ditt på telefonen
|
||||
</Text>
|
||||
<View style={styles.button}>
|
||||
<Button onPress={onRegister} full>
|
||||
<NBText>Bli medlem</NBText>
|
||||
<Button onPress={onRegister} fullWidth>
|
||||
<Text>Bli medlem</Text>
|
||||
</Button>
|
||||
</View>
|
||||
<Text style={styles.header}>Hva får jeg?</Text>
|
||||
@@ -23,8 +23,8 @@ const MembershipInfo = ({ onRegister, onLogin }) => (
|
||||
</Text>
|
||||
<Text style={styles.em}>Allerede medlem?</Text>
|
||||
<View style={styles.button}>
|
||||
<Button onPress={onLogin} full>
|
||||
<NBText>Logg inn</NBText>
|
||||
<Button onPress={onLogin} fullWidth>
|
||||
<Text>Logg inn</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Image, ScrollView, StyleSheet, Text, TouchableOpacity, View, RefreshControl } from 'react-native';
|
||||
import { Button, Text as NBText, Spinner } from 'native-base';
|
||||
import {
|
||||
Image,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
RefreshControl,
|
||||
ActivityIndicator,
|
||||
} from 'react-native';
|
||||
import { Button } from 'react-native-ui-lib';
|
||||
import Confetti from 'react-native-confetti';
|
||||
|
||||
import theme from '../../theme';
|
||||
@@ -55,14 +64,14 @@ const PurchaseButton = ({ user, isChargingMembership, isLoadingMembershipType, o
|
||||
|
||||
return (
|
||||
<View>
|
||||
{isChargingMembership && <Spinner color="#f58220" />}
|
||||
{isChargingMembership && <ActivityIndicator color="#f58220" />}
|
||||
<Button
|
||||
onPress={onChargePress}
|
||||
full
|
||||
fullWidth
|
||||
disabled={isChargingMembership || isLoadingMembershipType}
|
||||
style={styles.purchaseButton}
|
||||
>
|
||||
<NBText>{`Kjøp medlemskap (${membershipPrice} NOK)`}</NBText>
|
||||
<Text>{`Kjøp medlemskap (${membershipPrice} NOK)`}</Text>
|
||||
</Button>
|
||||
<View style={styles.purchaseText}>
|
||||
<Text style={styles.purchaseTextInner}>Medlemskapet er gyldig i ett år</Text>
|
||||
@@ -128,9 +137,10 @@ const Proof = ({ onLogoutPress, user, fetchUser, isFetchingUserData }) => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const currentRef = confettiRef.current;
|
||||
return () => {
|
||||
if (confettiRef && confettiRef.current) {
|
||||
confettiRef.current.stopConfetti();
|
||||
if (currentRef) {
|
||||
currentRef.stopConfetti();
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -172,7 +182,7 @@ const Proof = ({ onLogoutPress, user, fetchUser, isFetchingUserData }) => {
|
||||
</View>
|
||||
<View>
|
||||
<Button onPress={onLogoutPress} style={styles.logoutButton} small>
|
||||
<NBText>Logg ut</NBText>
|
||||
<Text>Logg ut</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { logout } from '../../actions';
|
||||
@@ -25,9 +25,9 @@ const useMembership = () => {
|
||||
};
|
||||
const onRegisterPress = () => navigation.navigate('UserRegister');
|
||||
const onLoginPress = () => navigation.navigate('Login');
|
||||
const fetchUser = () => {
|
||||
const fetchUser = useCallback(() => {
|
||||
dispatch(requestUserData(userToken));
|
||||
};
|
||||
}, [dispatch, userToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (userToken !== prevUserToken && userToken) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: fix lint violations
|
||||
/* eslint-disable no-unused-vars, react-hooks/exhaustive-deps */
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
@@ -40,6 +42,7 @@ const useProof = ({ user, fetchUser }) => {
|
||||
|
||||
const membershipTypeSlug = data?.membershipTypes?.[0]?.slug ?? 'standard';
|
||||
|
||||
// TODO: Implement stripe payment
|
||||
// stripe.paymentRequestWithCardForm(options).then(
|
||||
// (token) => {
|
||||
// // returns a token.tokenId which should be saved
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Spinner, Button, Text, ScrollView } from 'native-base';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { ScrollView } from 'native-base';
|
||||
import { Button } from 'react-native-ui-lib';
|
||||
|
||||
import { ActivityIndicator, StyleSheet, View, Text } from 'react-native';
|
||||
import * as EmailValidator from 'email-validator';
|
||||
|
||||
import theme from '../../theme';
|
||||
@@ -68,10 +70,10 @@ const UserRegister = () => {
|
||||
<View style={styles.errorBox}>
|
||||
<FormErrors errors={errors} />
|
||||
</View>
|
||||
<Button full disabled={isDisabled} onPress={onSubmit} style={styles.registerButton}>
|
||||
<Button fullWidth disabled={isDisabled} onPress={onSubmit} style={styles.registerButton}>
|
||||
<Text>Registrer meg</Text>
|
||||
</Button>
|
||||
{isRegisteringUser && <Spinner color="#f58220" />}
|
||||
{isRegisteringUser && <ActivityIndicator color="#f58220" />}
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ const useUserRegister = () => {
|
||||
if (isAuthenticatedPrev !== isAuthenticated && isAuthenticated) {
|
||||
navigation.goBack();
|
||||
}
|
||||
}, [isAuthenticatedPrev, isAuthenticated]);
|
||||
}, [isAuthenticatedPrev, isAuthenticated, navigation]);
|
||||
|
||||
return { onSubmit, isRegisteringUser, registerError, control, setFocus, errors };
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@ function formatErrors(errs) {
|
||||
return _errs;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line default-param-last
|
||||
export default function duskenApp(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
/* Login */
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Platform } from 'react-native';
|
||||
import { Colors } from 'react-native-ui-lib';
|
||||
|
||||
const colors = {
|
||||
primary: '#f58220',
|
||||
danger: '#d9534f',
|
||||
};
|
||||
|
||||
@@ -20,4 +22,9 @@ const card = {
|
||||
margin: 8,
|
||||
backgroundColor: '#fff',
|
||||
};
|
||||
|
||||
export function loadTheme() {
|
||||
Colors.loadDesignTokens({ primaryColor: colors.primary });
|
||||
}
|
||||
|
||||
export default { card, colors };
|
||||
+5
-1
@@ -9,7 +9,11 @@ export function fetchWithTimeout(url, options, timeout = 10000) {
|
||||
// FIXME: show timeout message in UI
|
||||
return Promise.race([
|
||||
fetch(url, options),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('Fetch request timed out')), timeout)),
|
||||
new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('Fetch request timed out'));
|
||||
}, timeout);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user