Skip to content

Commit a47846c

Browse files
Improve login (verify JWT token), add the app state to Redux + various imprvoments
1 parent 33c0ace commit a47846c

47 files changed

Lines changed: 814 additions & 720 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslint.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
},
4747
"rules": {
4848
"import/newline-after-import": "error",
49-
"import/no-cycle": "error",
5049
"no-fallthrough": "off",
5150
"no-return-await": "off",
5251
"no-undef": "off",
@@ -63,6 +62,8 @@
6362
"@typescript-eslint/explicit-module-boundary-types": "off",
6463
"@typescript-eslint/no-unused-vars": "off",
6564
"@typescript-eslint/no-empty-function": "off",
66-
"@typescript-eslint/no-empty-interface": "off"
65+
"@typescript-eslint/no-empty-interface": "off",
66+
"import/no-cycle": "off",
67+
"camelcase": "off"
6768
}
6869
}

App.tsx

Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ import { PersistGate } from "redux-persist/integration/react";
77

88
// Navigation
99
import { NavigationContainer } from "@react-navigation/native";
10-
import useLinking from "./src/navigation/useLinking";
1110
import { navigationRef } from './src/navigation/RootNavigation';
1211

13-
1412
import { Root } from "./src/screens/Root";
1513

1614
// Redux store
@@ -23,48 +21,24 @@ const Loading = () => (
2321
</View>
2422
);
2523

26-
const App = () => {
27-
const [ isLoadingComplete, setLoadingComplete ] = React.useState(false);
28-
const [ initialNavigationState, setInitialNavigationState ] = React.useState();
29-
const { getInitialState } = useLinking(navigationRef);
30-
31-
// Load any resources or data that we need prior to rendering the app
32-
React.useEffect(() => {
33-
const loadResourcesAndDataAsync = async () => {
34-
try {
35-
// Load our initial navigation state
36-
setInitialNavigationState(await getInitialState());
37-
} catch (e) {
38-
// We might want to provide this error information to an error reporting service
39-
console.warn(e);
40-
} finally {
41-
setLoadingComplete(true);
42-
}
43-
};
44-
45-
loadResourcesAndDataAsync();
46-
}, []);
47-
48-
if (!isLoadingComplete) {
49-
return null;
24+
class App extends React.Component {
25+
render() {
26+
return (
27+
<View style={styles.container}>
28+
<Provider store={store}>
29+
<PersistGate loading={<Loading />} persistor={persistor}>
30+
{Platform.OS === "ios" && <StatusBar barStyle="default" />}
31+
<NavigationContainer
32+
// @ts-ignore
33+
ref={navigationRef}
34+
>
35+
<Root />
36+
</NavigationContainer>
37+
</PersistGate>
38+
</Provider>
39+
</View>
40+
);
5041
}
51-
52-
return (
53-
<View style={styles.container}>
54-
<Provider store={store}>
55-
<PersistGate loading={<Loading />} persistor={persistor}>
56-
{Platform.OS === "ios" && <StatusBar barStyle="default" />}
57-
<NavigationContainer
58-
// @ts-ignore
59-
ref={navigationRef}
60-
initialState={initialNavigationState}
61-
>
62-
<Root />
63-
</NavigationContainer>
64-
</PersistGate>
65-
</Provider>
66-
</View>
67-
);
6842
};
6943

7044
const styles = StyleSheet.create({

config/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"API": {
3-
"dataURL": "https://storage.googleapis.com/nandos-engineering-public/coding-challenge-rn/restaurantlist.json"
3+
"dataURL": "https://raw.githubusercontent.com/GeorgianSorinMaxim/json-data/gh-pages/list-of-universities.json"
44
}
55
}

firebase/config.js renamed to firebase.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as firebase from "firebase";
2+
23
import "@firebase/auth";
34
import "@firebase/firestore";
45

readme.MD

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
- Eslint (for TypeScript) is used in the project for checking the JS/TS errors.
6161
- TypeScript is used in the project.
6262
- react-native-vector-icons has been used to improve the UI apprerance using icons in the navigator and in other UI elements.
63-
- Icons: https://ionicons.com
63+
- Icons: https://infinitered.github.io/ionicons-version-3-search/
6464

6565
### Components
6666
- Button
@@ -76,10 +76,14 @@
7676
- Screen header with title and left / right buttons
7777
- Screen container
7878
- WebView container with `react-native-webview` and `uri-js`
79-
- Progress indicators and spinners with `react-native-progess`
8079

81-
### TODO Integrations
82-
- Analytics (GA) and Crashlytics with `react-native-firebase`, `react-native-device-info` and `react-native-uuid`
80+
### TODO
81+
- WIP: Auto-login with token verification with `jwt-decode`
82+
- Use AppCenter CI to build and sign the app
83+
- UI improvments for login / signup forms with `react-native-keyboard-aware-scroll-view`
84+
- Fetch data from Firebase Database using with `react-native-firebase`
85+
- Logging and tracking service: Analytics (GA) and Crashlytics with `react-native-firebase`, `react-native-device-info` and `react-native-uuid`
86+
- Progress indicators and spinners with `react-native-progess`
8387
- Use biometric authentication and store user creds in the KeyChain and KeyStore with `react-native-secure-key-store` - https://www.freecodecamp.org/news/how-to-implement-secure-biometric-authentication-on-mobile-devices-4dc518558c5c/
8488
- Run the app in different environments: dev, prod, uat with `react-native-config`
8589
- Allow to natively share urls with `react-native-share`
@@ -91,9 +95,14 @@
9195
- Render maps with `react-native-maps`
9296
- Render PDFs in-app with `react-native-pdf`
9397
- Ask your users to rate the app with `react-native-rate`
98+
- Render SVGs with `react-native-svg` and `react-native-svg-transformer`
9499
- Feature flagging with Firebase
95100
- Push notifications with Firebase / Azure
96101
- Local notifications
102+
- SPIKE Apollo Client
103+
- Manage manage the local data using Apollo Client (with apollo-link-state).
104+
- Access the Apollo cache with apollo-cache-inmemory.
105+
97106

98107
### TODOs before going live
99108
- Manual test the app on multiple devices on both platforms (or use AWS Device Farm).

src/api/firebaseApi.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { DateTime } from "luxon";
22

3-
import { firebase } from "../../firebase/config";
3+
import { firebase } from "../../firebase";
44

55
export class FirebaseApi {
66
constructor() {
@@ -95,7 +95,7 @@ export class FirebaseApi {
9595
}
9696
}
9797

98-
async getIdToken() {
98+
async getToken() {
9999
const { currentUser } = firebase.auth();
100100

101101
if (currentUser) {
-8.72 KB
Binary file not shown.
-25.1 KB
Binary file not shown.
-49.1 KB
Binary file not shown.

src/components/Card.tsx

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import * as React from "react";
2-
import { Linking, StyleSheet, TouchableOpacity, View } from "react-native";
2+
import { Image, Linking, StyleSheet, TouchableOpacity, View } from "react-native";
3+
4+
import ImagePlaceholder from "./ImagePlaceholder";
35

46
import Colors from "../constants/Colors";
57
import BodyText from "./BodyText";
68

7-
import { Restaurant } from "../store/types/state";
9+
import { University } from "../store/types/state";
810

911
interface Props {
10-
item: Restaurant;
12+
item: University;
1113
}
1214

1315
interface State {}
@@ -23,40 +25,70 @@ class Card extends React.Component<Props, State> {
2325

2426
render() {
2527
const { item } = this.props;
26-
const { streetAddress, postalCode, addressLocality } = item.geo.address;
27-
const address = `${streetAddress}, ${postalCode}, ${addressLocality}`;
28+
const { imageUrl, geo } = item;
29+
30+
let address = "No address";
31+
if (geo && geo.address) {
32+
const { streetAddress, postalCode, addressLocality } = geo.address;
33+
address = `${streetAddress}, ${postalCode}, ${addressLocality}`;
34+
}
2835

2936
return (
3037
<TouchableOpacity onPress={() => this.onItemTap(item.url)}>
31-
<View style={styles.item}>
32-
<BodyText style={styles.restaurantName}>{item.name}</BodyText>
33-
<BodyText style={styles.address}>{address}</BodyText>
38+
<View style={styles.row}>
39+
<View style={styles.leftColumn}>
40+
<BodyText style={styles.universityName}>{item.name}</BodyText>
41+
<BodyText style={styles.address}>{address}</BodyText>
42+
</View>
43+
<View style={styles.rightColumn}>
44+
{!imageUrl || imageUrl.includes("svg") ? (
45+
<ImagePlaceholder />
46+
) : (
47+
<Image source={{ uri: imageUrl }} style={styles.image} />
48+
)}
49+
</View>
3450
</View>
3551
</TouchableOpacity>
3652
);
3753
}
3854
}
3955

4056
const styles = StyleSheet.create({
41-
item: {
57+
row: {
4258
marginBottom: 12,
4359
borderRadius: 6,
4460
width: "100%",
45-
backgroundColor: Colors.red,
4661
padding: 12,
4762
display: "flex",
4863
alignItems: "flex-start",
64+
borderColor: Colors.grey,
65+
borderWidth: 1,
66+
flexDirection: "row",
67+
justifyContent: "space-between",
68+
},
69+
leftColumn: {
70+
width: "60%",
4971
},
50-
restaurantName: {
72+
rightColumn: {
73+
width: "40%",
74+
alignItems: "flex-end",
75+
},
76+
universityName: {
77+
marginTop: 12,
5178
fontWeight: "bold",
52-
color: Colors.white,
79+
color: Colors.black,
5380
overflow: "hidden",
5481
},
5582
address: {
56-
color: Colors.white,
83+
color: Colors.black,
5784
overflow: "hidden",
5885
paddingTop: 12,
5986
},
87+
image: {
88+
width: 100,
89+
height: 100,
90+
resizeMode: "contain",
91+
},
6092
});
6193

6294
export default Card;

0 commit comments

Comments
 (0)