├── .expo-shared └── assets.json ├── .gitignore ├── App.js ├── app.json ├── assets ├── adaptive-icon.png ├── favicon.png ├── icon.png └── splash.png ├── babel.config.js ├── package-lock.json ├── package.json └── src ├── api └── yelp.js ├── assets └── images │ ├── burger.png │ ├── cake.png │ ├── pasta.png │ ├── pizza.png │ ├── smoothies.png │ └── steak.png ├── components ├── Categories.js ├── Header.js ├── ItemCategory.js ├── RestaurantItem.js ├── Restaurants.js └── SearchInput.js ├── hooks └── useRestaurants.js └── screens ├── HomeScreen.js └── RestaurantScreen.js /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | 13 | # macOS 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import { createAppContainer } from "react-navigation"; 2 | import { createStackNavigator } from "react-navigation-stack"; 3 | import HomeScreen from "./src/screens/HomeScreen"; 4 | import RestaurantScreen from "./src/screens/RestaurantScreen"; 5 | 6 | const navigator = createStackNavigator( 7 | { 8 | Home: HomeScreen, 9 | Restaurant: RestaurantScreen, 10 | }, 11 | { 12 | initialRouteName: "Home", 13 | defaultNavigationOptions: { 14 | title: "BusinessSearch", 15 | }, 16 | } 17 | ); 18 | 19 | export default createAppContainer(navigator); 20 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "restaurants", 4 | "slug": "restaurants", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "splash": { 9 | "image": "./assets/splash.png", 10 | "resizeMode": "contain", 11 | "backgroundColor": "#ffffff" 12 | }, 13 | "updates": { 14 | "fallbackToCacheTimeout": 0 15 | }, 16 | "assetBundlePatterns": [ 17 | "**/*" 18 | ], 19 | "ios": { 20 | "supportsTablet": true 21 | }, 22 | "android": { 23 | "adaptiveIcon": { 24 | "foregroundImage": "./assets/adaptive-icon.png", 25 | "backgroundColor": "#FFFFFF" 26 | } 27 | }, 28 | "web": { 29 | "favicon": "./assets/favicon.png" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harblaith7/React-Native-Crash-Course/dbf1ff8253916fecd030fb6c7a550f1760fe6104/assets/adaptive-icon.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harblaith7/React-Native-Crash-Course/dbf1ff8253916fecd030fb6c7a550f1760fe6104/assets/favicon.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harblaith7/React-Native-Crash-Course/dbf1ff8253916fecd030fb6c7a550f1760fe6104/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harblaith7/React-Native-Crash-Course/dbf1ff8253916fecd030fb6c7a550f1760fe6104/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restaurants", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web", 10 | "eject": "expo eject" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.26.0", 14 | "expo": "~44.0.0", 15 | "expo-status-bar": "~1.2.0", 16 | "react": "17.0.1", 17 | "react-dom": "17.0.1", 18 | "react-native": "0.64.3", 19 | "react-native-web": "0.17.1", 20 | "react-navigation": "^4.4.4", 21 | "react-navigation-stack": "^2.10.4" 22 | }, 23 | "devDependencies": { 24 | "@babel/core": "^7.12.9" 25 | }, 26 | "private": true 27 | } 28 | -------------------------------------------------------------------------------- /src/api/yelp.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default axios.create({ 4 | baseURL: "https://api.yelp.com/v3/businesses", 5 | headers: { 6 | Authorization: `Bearer fuefE5XzFBOYQrWACxEa1NzsOuUyCd9iKXGlotUC-9H4l5WHkveHLsd5yk60F7KGX1Mj9hKZ4M7eZYlMg0UfxdtYpTexTmCHPvGlCfPtKbHgThE90LtoQYyRfIMbYnYx`, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /src/assets/images/burger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harblaith7/React-Native-Crash-Course/dbf1ff8253916fecd030fb6c7a550f1760fe6104/src/assets/images/burger.png -------------------------------------------------------------------------------- /src/assets/images/cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harblaith7/React-Native-Crash-Course/dbf1ff8253916fecd030fb6c7a550f1760fe6104/src/assets/images/cake.png -------------------------------------------------------------------------------- /src/assets/images/pasta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harblaith7/React-Native-Crash-Course/dbf1ff8253916fecd030fb6c7a550f1760fe6104/src/assets/images/pasta.png -------------------------------------------------------------------------------- /src/assets/images/pizza.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harblaith7/React-Native-Crash-Course/dbf1ff8253916fecd030fb6c7a550f1760fe6104/src/assets/images/pizza.png -------------------------------------------------------------------------------- /src/assets/images/smoothies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harblaith7/React-Native-Crash-Course/dbf1ff8253916fecd030fb6c7a550f1760fe6104/src/assets/images/smoothies.png -------------------------------------------------------------------------------- /src/assets/images/steak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harblaith7/React-Native-Crash-Course/dbf1ff8253916fecd030fb6c7a550f1760fe6104/src/assets/images/steak.png -------------------------------------------------------------------------------- /src/components/Categories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { FlatList, View } from "react-native"; 3 | import ItemCategory from "./ItemCategory"; 4 | 5 | export default function Categories({ commonCategories, setTerm, term }) { 6 | return ( 7 | 8 | category.name} 11 | renderItem={({ item, index }) => ( 12 | setTerm(item.name)} 17 | /> 18 | )} 19 | horizontal 20 | showsHorizontalScrollIndicator={false} 21 | /> 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { StyleSheet, Text, View } from "react-native"; 3 | 4 | export default function Header({ upperText, lowerText }) { 5 | return ( 6 | 7 | {upperText} 8 | {lowerText} 9 | 10 | ); 11 | } 12 | 13 | const styles = StyleSheet.create({ 14 | header1: { 15 | fontSize: 35, 16 | marginTop: 60, 17 | marginHorizontal: 25, 18 | }, 19 | header2: { 20 | fontSize: 40, 21 | marginHorizontal: 25, 22 | fontWeight: "bold", 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/ItemCategory.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Image, StyleSheet, Text, TouchableOpacity, View } from "react-native"; 3 | 4 | export default function ItemCategory({ 5 | index, 6 | category, 7 | active, 8 | onPressCategoryItem, 9 | }) { 10 | return ( 11 | 12 | 21 | 22 | 23 | 24 | {category.name} 25 | 26 | 27 | ); 28 | } 29 | 30 | const styles = StyleSheet.create({ 31 | container: { 32 | width: 70, 33 | height: 100, 34 | borderRadius: 500, 35 | alignItems: "center", 36 | justifyContent: "center", 37 | shadowOffset: { width: 5, height: 5 }, 38 | shadowColor: "black", 39 | shadowOpacity: 0.1, 40 | elevation: 3, 41 | marginBottom: 7, 42 | }, 43 | image: { 44 | width: 35, 45 | height: 35, 46 | }, 47 | imageContainer: { 48 | width: 50, 49 | height: 50, 50 | backgroundColor: "white", 51 | alignItems: "center", 52 | justifyContent: "center", 53 | borderRadius: 500, 54 | marginBottom: 5, 55 | }, 56 | header: { 57 | fontWeight: "bold", 58 | }, 59 | }); 60 | -------------------------------------------------------------------------------- /src/components/RestaurantItem.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Image, StyleSheet, Text, View } from "react-native"; 3 | 4 | export default function RestaurantItem({ restaurant }) { 5 | return ( 6 | 7 | 8 | 9 | {restaurant.name} 10 | 11 | {restaurant.rating} stars 12 | {restaurant.price} 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | const styles = StyleSheet.create({ 20 | container: { 21 | alignSelf: "stretch", 22 | height: 100, 23 | borderRadius: 500, 24 | shadowOffset: { width: 1, height: 3 }, 25 | shadowColor: "black", 26 | shadowOpacity: 0.1, 27 | elevation: 3, 28 | backgroundColor: "white", 29 | marginVertical: 10, 30 | flexDirection: "row", 31 | alignItems: "center", 32 | }, 33 | image: { 34 | width: 75, 35 | height: 75, 36 | borderRadius: 400, 37 | marginLeft: 10, 38 | }, 39 | infoContainer: { 40 | flex: 1, 41 | paddingHorizontal: 10, 42 | }, 43 | header: { 44 | fontSize: 18, 45 | fontWeight: "bold", 46 | marginBottom: 4, 47 | }, 48 | info: { 49 | fontWeight: "bold", 50 | flexDirection: "row", 51 | }, 52 | rating: { 53 | marginRight: 20, 54 | }, 55 | money: { 56 | color: "gold", 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /src/components/Restaurants.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | StyleSheet, 4 | View, 5 | Text, 6 | ActivityIndicator, 7 | FlatList, 8 | TouchableOpacity, 9 | } from "react-native"; 10 | import { withNavigation } from "react-navigation"; 11 | import RestaurantItem from "./RestaurantItem"; 12 | 13 | function Restaurants({ data, error, loading, navigation }) { 14 | if (loading) return ; 15 | 16 | return ( 17 | 18 | Top Restaurants 19 | 20 | restaurant.id} 23 | renderItem={({ item, index }) => ( 24 | navigation.navigate("Restaurant", { id: item.id })} 26 | > 27 | 28 | 29 | )} 30 | /> 31 | 32 | ); 33 | } 34 | 35 | const styles = StyleSheet.create({ 36 | container: { 37 | marginHorizontal: 25, 38 | marginVertical: 15, 39 | flex: 1, 40 | }, 41 | header: { 42 | fontWeight: "bold", 43 | fontSize: 20, 44 | paddingBottom: 10, 45 | }, 46 | }); 47 | 48 | export default withNavigation(Restaurants); 49 | -------------------------------------------------------------------------------- /src/components/SearchInput.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { TextInput, View, StyleSheet } from "react-native"; 3 | import { FontAwesome } from "@expo/vector-icons"; 4 | 5 | export default function SearchInput({ setTerm }) { 6 | const [input, setInput] = useState(""); 7 | 8 | return ( 9 | 10 | 11 | setInput(text)} 16 | onEndEditing={() => { 17 | if (input) setTerm(input); 18 | setInput(""); 19 | }} 20 | /> 21 | 22 | ); 23 | } 24 | 25 | const styles = StyleSheet.create({ 26 | inputContainer: { 27 | marginTop: 5, 28 | marginHorizontal: 25, 29 | backgroundColor: "white", 30 | padding: 15, 31 | borderRadius: 40, 32 | flexDirection: "row", 33 | }, 34 | elevation: { 35 | shadowOffset: { width: 5, height: 5 }, 36 | shadowColor: "black", 37 | shadowOpacity: 0.1, 38 | elevation: 3, 39 | }, 40 | input: { 41 | fontSize: 20, 42 | marginLeft: 10, 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /src/hooks/useRestaurants.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import yelp from "../api/yelp"; 3 | 4 | export default (initialTerm = "Burger") => { 5 | const [results, setResults] = useState({ 6 | data: null, 7 | loading: false, 8 | error: null, 9 | }); 10 | 11 | const searchRestaurants = async (searchTerm) => { 12 | setResults({ 13 | data: null, 14 | loading: true, 15 | error: null, 16 | }); 17 | try { 18 | const response = await yelp.get("/search", { 19 | params: { 20 | limit: 15, 21 | term: searchTerm, 22 | location: "Ottawa", 23 | }, 24 | }); 25 | setResults({ 26 | data: response.data.businesses, 27 | loading: false, 28 | error: null, 29 | }); 30 | } catch (error) { 31 | setResults({ 32 | data: null, 33 | loading: false, 34 | error: "Something went wrong", 35 | }); 36 | } 37 | }; 38 | 39 | return [results, searchRestaurants]; 40 | }; 41 | -------------------------------------------------------------------------------- /src/screens/HomeScreen.js: -------------------------------------------------------------------------------- 1 | import { StatusBar } from "expo-status-bar"; 2 | import { StyleSheet, View } from "react-native"; 3 | 4 | import { useEffect, useState } from "react"; 5 | import useRestaurants from "../hooks/useRestaurants"; 6 | import Header from "../components/Header"; 7 | import SearchInput from "../components/SearchInput"; 8 | import Categories from "../components/Categories"; 9 | import Restaurants from "../components/Restaurants"; 10 | 11 | export default function HomeScreen() { 12 | const [term, setTerm] = useState("Burger"); 13 | const [commonCategories] = useState([ 14 | { 15 | name: "Burger", 16 | img: require("../assets/images/burger.png"), 17 | }, 18 | { 19 | name: "Pizza", 20 | img: require("../assets/images/pizza.png"), 21 | }, 22 | { 23 | name: "Dessert", 24 | img: require("../assets/images/cake.png"), 25 | }, 26 | { 27 | name: "Drinks", 28 | img: require("../assets/images/smoothies.png"), 29 | }, 30 | { 31 | name: "Steak", 32 | img: require("../assets/images/steak.png"), 33 | }, 34 | { 35 | name: "Pasta", 36 | img: require("../assets/images/pasta.png"), 37 | }, 38 | ]); 39 | 40 | const [{ data, loading, error }, searchResaurants] = useRestaurants(term); 41 | 42 | useEffect(() => { 43 | searchResaurants(term); 44 | }, [term]); 45 | 46 | return ( 47 | 48 |
49 | 50 | 55 | 56 | 57 | 58 | ); 59 | } 60 | 61 | const styles = StyleSheet.create({ 62 | container: { 63 | flex: 1, 64 | backgroundColor: "rgb(253,253,253)", 65 | }, 66 | header1: { 67 | fontSize: 35, 68 | marginTop: 60, 69 | marginHorizontal: 25, 70 | }, 71 | header2: { 72 | fontSize: 40, 73 | marginHorizontal: 25, 74 | fontWeight: "bold", 75 | }, 76 | }); 77 | -------------------------------------------------------------------------------- /src/screens/RestaurantScreen.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { 3 | Dimensions, 4 | FlatList, 5 | Image, 6 | StyleSheet, 7 | Text, 8 | View, 9 | } from "react-native"; 10 | import yelp from "../api/yelp"; 11 | 12 | function RestaurantScreen({ navigation }) { 13 | const id = navigation.getParam("id"); 14 | const [results, setResults] = useState({ 15 | data: null, 16 | loading: false, 17 | error: null, 18 | }); 19 | 20 | useEffect(() => { 21 | fetchRestaurant(); 22 | }, []); 23 | 24 | const dimensions = Dimensions.get("window"); 25 | const imageHeight = Math.round((dimensions.width * 9) / 16); 26 | const imageWidth = dimensions.width; 27 | 28 | const fetchRestaurant = async () => { 29 | setResults({ 30 | data: null, 31 | loading: true, 32 | error: null, 33 | }); 34 | try { 35 | const response = await yelp.get(`/${id}`); 36 | 37 | setResults({ 38 | data: response.data, 39 | loading: false, 40 | error: null, 41 | }); 42 | } catch (error) { 43 | setResults({ 44 | data: null, 45 | loading: false, 46 | error: "something went wrong", 47 | }); 48 | } 49 | }; 50 | 51 | if (results.loading) 52 | return ( 53 | 54 | Inside loading 55 | 56 | ); 57 | 58 | if (results.error) 59 | return ( 60 | 61 | Inside error 62 | 63 | ); 64 | 65 | return ( 66 | 67 | {results.data && ( 68 | photo} 71 | renderItem={({ item }) => { 72 | console.log({ item }); 73 | return ( 74 | 78 | ); 79 | }} 80 | /> 81 | )} 82 | 83 | ); 84 | } 85 | 86 | const styles = StyleSheet.create({ 87 | image: { 88 | flex: 1, 89 | }, 90 | }); 91 | 92 | export default RestaurantScreen; 93 | --------------------------------------------------------------------------------