Skip to content

Commit

Permalink
Improve login (verify JWT token), add the app state to Redux + variou…
Browse files Browse the repository at this point in the history
…s imprvoments
  • Loading branch information
GeorgianSorinMaxim committed Feb 11, 2021
1 parent 33c0ace commit a47846c
Show file tree
Hide file tree
Showing 47 changed files with 814 additions and 720 deletions.
5 changes: 3 additions & 2 deletions .eslint.json
Expand Up @@ -46,7 +46,6 @@
},
"rules": {
"import/newline-after-import": "error",
"import/no-cycle": "error",
"no-fallthrough": "off",
"no-return-await": "off",
"no-undef": "off",
Expand All @@ -63,6 +62,8 @@
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-empty-interface": "off"
"@typescript-eslint/no-empty-interface": "off",
"import/no-cycle": "off",
"camelcase": "off"
}
}
60 changes: 17 additions & 43 deletions App.tsx
Expand Up @@ -7,10 +7,8 @@ import { PersistGate } from "redux-persist/integration/react";

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


import { Root } from "./src/screens/Root";

// Redux store
Expand All @@ -23,48 +21,24 @@ const Loading = () => (
</View>
);

const App = () => {
const [ isLoadingComplete, setLoadingComplete ] = React.useState(false);
const [ initialNavigationState, setInitialNavigationState ] = React.useState();
const { getInitialState } = useLinking(navigationRef);

// Load any resources or data that we need prior to rendering the app
React.useEffect(() => {
const loadResourcesAndDataAsync = async () => {
try {
// Load our initial navigation state
setInitialNavigationState(await getInitialState());
} catch (e) {
// We might want to provide this error information to an error reporting service
console.warn(e);
} finally {
setLoadingComplete(true);
}
};

loadResourcesAndDataAsync();
}, []);

if (!isLoadingComplete) {
return null;
class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Provider store={store}>
<PersistGate loading={<Loading />} persistor={persistor}>
{Platform.OS === "ios" && <StatusBar barStyle="default" />}
<NavigationContainer
// @ts-ignore
ref={navigationRef}
>
<Root />
</NavigationContainer>
</PersistGate>
</Provider>
</View>
);
}

return (
<View style={styles.container}>
<Provider store={store}>
<PersistGate loading={<Loading />} persistor={persistor}>
{Platform.OS === "ios" && <StatusBar barStyle="default" />}
<NavigationContainer
// @ts-ignore
ref={navigationRef}
initialState={initialNavigationState}
>
<Root />
</NavigationContainer>
</PersistGate>
</Provider>
</View>
);
};

const styles = StyleSheet.create({
Expand Down
2 changes: 1 addition & 1 deletion config/config.json
@@ -1,5 +1,5 @@
{
"API": {
"dataURL": "https://storage.googleapis.com/nandos-engineering-public/coding-challenge-rn/restaurantlist.json"
"dataURL": "https://raw.githubusercontent.com/GeorgianSorinMaxim/json-data/gh-pages/list-of-universities.json"
}
}
1 change: 1 addition & 0 deletions firebase/config.js → firebase.js
@@ -1,4 +1,5 @@
import * as firebase from "firebase";

import "@firebase/auth";
import "@firebase/firestore";

Expand Down
17 changes: 13 additions & 4 deletions readme.MD
Expand Up @@ -60,7 +60,7 @@
- Eslint (for TypeScript) is used in the project for checking the JS/TS errors.
- TypeScript is used in the project.
- react-native-vector-icons has been used to improve the UI apprerance using icons in the navigator and in other UI elements.
- Icons: https://ionicons.com
- Icons: https://infinitered.github.io/ionicons-version-3-search/

### Components
- Button
Expand All @@ -76,10 +76,14 @@
- Screen header with title and left / right buttons
- Screen container
- WebView container with `react-native-webview` and `uri-js`
- Progress indicators and spinners with `react-native-progess`

### TODO Integrations
- Analytics (GA) and Crashlytics with `react-native-firebase`, `react-native-device-info` and `react-native-uuid`
### TODO
- WIP: Auto-login with token verification with `jwt-decode`
- Use AppCenter CI to build and sign the app
- UI improvments for login / signup forms with `react-native-keyboard-aware-scroll-view`
- Fetch data from Firebase Database using with `react-native-firebase`
- Logging and tracking service: Analytics (GA) and Crashlytics with `react-native-firebase`, `react-native-device-info` and `react-native-uuid`
- Progress indicators and spinners with `react-native-progess`
- 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/
- Run the app in different environments: dev, prod, uat with `react-native-config`
- Allow to natively share urls with `react-native-share`
Expand All @@ -91,9 +95,14 @@
- Render maps with `react-native-maps`
- Render PDFs in-app with `react-native-pdf`
- Ask your users to rate the app with `react-native-rate`
- Render SVGs with `react-native-svg` and `react-native-svg-transformer`
- Feature flagging with Firebase
- Push notifications with Firebase / Azure
- Local notifications
- SPIKE Apollo Client
- Manage manage the local data using Apollo Client (with apollo-link-state).
- Access the Apollo cache with apollo-cache-inmemory.


### TODOs before going live
- Manual test the app on multiple devices on both platforms (or use AWS Device Farm).
Expand Down
4 changes: 2 additions & 2 deletions src/api/firebaseApi.js
@@ -1,6 +1,6 @@
import { DateTime } from "luxon";

import { firebase } from "../../firebase/config";
import { firebase } from "../../firebase";

export class FirebaseApi {
constructor() {
Expand Down Expand Up @@ -95,7 +95,7 @@ export class FirebaseApi {
}
}

async getIdToken() {
async getToken() {
const { currentUser } = firebase.auth();

if (currentUser) {
Expand Down
Binary file removed src/assets/images/picturePlaceholder.png
Binary file not shown.
Binary file removed src/assets/images/picturePlaceholder@2x.png
Binary file not shown.
Binary file removed src/assets/images/picturePlaceholder@3x.png
Binary file not shown.
58 changes: 45 additions & 13 deletions src/components/Card.tsx
@@ -1,13 +1,15 @@
import * as React from "react";
import { Linking, StyleSheet, TouchableOpacity, View } from "react-native";
import { Image, Linking, StyleSheet, TouchableOpacity, View } from "react-native";

import ImagePlaceholder from "./ImagePlaceholder";

import Colors from "../constants/Colors";
import BodyText from "./BodyText";

import { Restaurant } from "../store/types/state";
import { University } from "../store/types/state";

interface Props {
item: Restaurant;
item: University;
}

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

render() {
const { item } = this.props;
const { streetAddress, postalCode, addressLocality } = item.geo.address;
const address = `${streetAddress}, ${postalCode}, ${addressLocality}`;
const { imageUrl, geo } = item;

let address = "No address";
if (geo && geo.address) {
const { streetAddress, postalCode, addressLocality } = geo.address;
address = `${streetAddress}, ${postalCode}, ${addressLocality}`;
}

return (
<TouchableOpacity onPress={() => this.onItemTap(item.url)}>
<View style={styles.item}>
<BodyText style={styles.restaurantName}>{item.name}</BodyText>
<BodyText style={styles.address}>{address}</BodyText>
<View style={styles.row}>
<View style={styles.leftColumn}>
<BodyText style={styles.universityName}>{item.name}</BodyText>
<BodyText style={styles.address}>{address}</BodyText>
</View>
<View style={styles.rightColumn}>
{!imageUrl || imageUrl.includes("svg") ? (
<ImagePlaceholder />
) : (
<Image source={{ uri: imageUrl }} style={styles.image} />
)}
</View>
</View>
</TouchableOpacity>
);
}
}

const styles = StyleSheet.create({
item: {
row: {
marginBottom: 12,
borderRadius: 6,
width: "100%",
backgroundColor: Colors.red,
padding: 12,
display: "flex",
alignItems: "flex-start",
borderColor: Colors.grey,
borderWidth: 1,
flexDirection: "row",
justifyContent: "space-between",
},
leftColumn: {
width: "60%",
},
restaurantName: {
rightColumn: {
width: "40%",
alignItems: "flex-end",
},
universityName: {
marginTop: 12,
fontWeight: "bold",
color: Colors.white,
color: Colors.black,
overflow: "hidden",
},
address: {
color: Colors.white,
color: Colors.black,
overflow: "hidden",
paddingTop: 12,
},
image: {
width: 100,
height: 100,
resizeMode: "contain",
},
});

export default Card;
4 changes: 4 additions & 0 deletions src/components/Header.tsx
Expand Up @@ -85,14 +85,17 @@ const styles = StyleSheet.create({
header: {
zIndex: 10,
elevation: 0, // no shadow for Android
// @ts-ignore
height: normalizeText(isBigScreen ? 80 : 60),
width: "100%",
display: "flex",
// @ts-ignore
paddingBottom: isBigScreen && isIOS ? normalizeText(10) : 0,
flexDirection: "row",
alignItems: "center",
borderBottomWidth: 1,
justifyContent: "center",
// @ts-ignore
paddingTop: isIOS ? (isBigScreen ? normalizeText(40) : normalizeText(10)) : 0,
},
leftColumn: {
Expand All @@ -111,6 +114,7 @@ const styles = StyleSheet.create({
justifyContent: "center",
},
titleText: {
// @ts-ignore
fontSize: normalizeText(14),
},
rightColumn: {
Expand Down
37 changes: 37 additions & 0 deletions src/components/ImagePlaceholder.tsx
@@ -0,0 +1,37 @@
import React, { FC } from "react";
import { StyleSheet, View } from "react-native";

import Colors from "../constants/Colors";

import Icon from "react-native-vector-icons/Ionicons";

interface Props {
size?: number;
}

const ImagePlaceholder: FC<Props> = (props: Props) => {
return (
<View style={styles.container}>
<Icon size={props.size || 35} name="image-outline" style={styles.icon} />
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.veryLightGrey,
width: 100,
height: 100,
alignItems: "center",
justifyContent: "center",
},
icon: {
padding: 0,
margin: 0,
alignSelf: "center",
color: Colors.grey,
},
});

export default ImagePlaceholder;
2 changes: 1 addition & 1 deletion src/components/__tests__/ButtonWithIcon.spec.tsx
Expand Up @@ -6,8 +6,8 @@ import ButtonWithIcon from "../ButtonWithIcon";
it(`renders correctly`, () => {
const defaultProps = {
label: "Google",
icon: "Icon",
onPress: () => {},
isLastOption: false,
};
const tree = renderer.create(<ButtonWithIcon {...defaultProps} />).toJSON();

Expand Down

0 comments on commit a47846c

Please sign in to comment.