├── components ├── Listitem.jsx └── ListItemView.jsx ├── README.md ├── navigation-bar ├── index.jsx └── LinkingConfig.jsx ├── screens ├── DashboardPage.jsx ├── SplashLoginPage.jsx └── MapPage.jsx ├── assets ├── cat.jpg ├── icon.png ├── logo.png ├── favicon.png ├── google.png ├── google2.png └── splash.png ├── .expo-shared └── assets.json ├── .gitignore ├── server ├── models.js ├── server.js ├── apiRouter.js └── controllers │ ├── userController.js │ └── mapController.js ├── babel.config.js ├── app.json ├── LICENSE ├── package.json └── App.js /components/Listitem.jsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trail-Traffic-App -------------------------------------------------------------------------------- /components/ListItemView.jsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /navigation-bar/index.jsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /screens/DashboardPage.jsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /navigation-bar/LinkingConfig.jsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caseyescovedo/Trail-Traffic-App/HEAD/assets/cat.jpg -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caseyescovedo/Trail-Traffic-App/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caseyescovedo/Trail-Traffic-App/HEAD/assets/logo.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caseyescovedo/Trail-Traffic-App/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /assets/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caseyescovedo/Trail-Traffic-App/HEAD/assets/google.png -------------------------------------------------------------------------------- /assets/google2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caseyescovedo/Trail-Traffic-App/HEAD/assets/google2.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caseyescovedo/Trail-Traffic-App/HEAD/assets/splash.png -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | .env 12 | secrets.js 13 | 14 | # macOS 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /server/models.js: -------------------------------------------------------------------------------- 1 | const secret = require('../secrets.js'); 2 | const myURI = secret.postgres_uri; 3 | 4 | 5 | const URI = process.env.PG_URI || myURI; 6 | 7 | const { Pool } = require('pg'); 8 | 9 | const pool = new Pool({connectionString: URI}) 10 | 11 | module.exports = { 12 | query: function (text, params, callback) { 13 | return pool.query(text, params, callback); 14 | } 15 | }; -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | env: { 6 | production: { 7 | plugins: [["inline-dotenv",{ 8 | path: './.env' 9 | }]] 10 | }, 11 | development: { 12 | plugins: [["inline-dotenv",{ 13 | path: './.env' 14 | }]] 15 | } 16 | } 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const PORT = 5001; 3 | const app = express(); 4 | const apiRouter = require('./apiRouter'); 5 | 6 | app.use('/api', apiRouter, (req, res) => { 7 | console.log(res.locals.data); 8 | res.status(200).send(res.locals.data); 9 | }) 10 | 11 | app.use((err, req, res, next) => { 12 | let defaultErr = { 13 | log: 'Express error handler caught unknown middleware error', 14 | status: 400, 15 | message: { err: 'An error occurred' }, 16 | }; 17 | 18 | let errorObj = Object.assign(defaultErr, err); 19 | console.log(errorObj.log); 20 | return res.status(errorObj.status).send(errorObj.message); 21 | }); 22 | 23 | app.listen(PORT, () => console.log(`🚀 Listening on PORT ${PORT}`)); -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "trail-traffic", 4 | "slug": "trail-traffic", 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 | "config": { 22 | "googleSignIn": { 23 | "reservedClientId": "com.googleusercontent.apps.745034174485-4pdp6ssn55soecvjskqjn3u96k7699g1" 24 | } 25 | } 26 | }, 27 | "web": { 28 | "favicon": "./assets/favicon.png" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /server/apiRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const mapController = require('./controllers/mapController'); 4 | const userController = require('./controllers/userController'); 5 | 6 | 7 | router.get('/getData', mapController.getHeat, (req, res, next) => { 8 | return next(); 9 | }) 10 | 11 | // add user 12 | router.post('/addUser', userController.addUser, (req, res, next) => { 13 | return next(); 14 | }) 15 | 16 | // get faves 17 | router.get('/getFaves', userController.getFavorites, (req, res, next) => { 18 | return next(); 19 | }) 20 | 21 | // add faves 22 | router.post('/addFaves', userController.addFavorite, userController.getFavorites, (req, res, next) => { 23 | return next(); 24 | }) 25 | 26 | // delete faves 27 | router.delete('/deleteFaves', userController.deleteFavorite, userController.getFavorites, (req, res, next) => { 28 | return next(); 29 | }) 30 | 31 | 32 | module.exports = router; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Trail-Traffic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject", 9 | "dev": "concurrently \"expo start\" \"nodemon ./server/server.js\"" 10 | }, 11 | "dependencies": { 12 | "@react-native-community/google-signin": "^5.0.0", 13 | "@react-native-community/masked-view": "0.1.10", 14 | "@react-navigation/bottom-tabs": "^5.10.0", 15 | "@react-navigation/native": "^5.8.0", 16 | "@react-navigation/stack": "^5.10.0", 17 | "dotenv": "^8.2.0", 18 | "expo": "^39.0.3", 19 | "expo-font": "~8.3.0", 20 | "expo-google-app-auth": "^8.1.3", 21 | "expo-location": "~9.0.0", 22 | "expo-status-bar": "~1.0.2", 23 | "express": "^4.17.1", 24 | "node-fetch": "^2.6.1", 25 | "pg": "^8.4.2", 26 | "react": "^16.13.1", 27 | "react-dom": "16.13.1", 28 | "react-native": "https://github.com/expo/react-native/archive/sdk-39.0.4.tar.gz", 29 | "react-native-button": "^3.0.1", 30 | "react-native-elements": "^3.0.0-alpha.1", 31 | "react-native-gesture-handler": "~1.7.0", 32 | "react-native-maps": "^0.27.1", 33 | "react-native-paper": "^4.2.0", 34 | "react-native-reanimated": "~1.13.0", 35 | "react-native-safe-area-context": "3.1.4", 36 | "react-native-screens": "~2.10.1", 37 | "react-native-vector-icons": "^7.1.0", 38 | "react-native-web": "~0.13.12" 39 | }, 40 | "devDependencies": { 41 | "@babel/core": "~7.9.0", 42 | "babel-plugin-inline-dotenv": "^1.6.0", 43 | "concurrently": "^5.3.0", 44 | "nodemon": "2.0.6" 45 | }, 46 | "private": true 47 | } 48 | -------------------------------------------------------------------------------- /server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const { query } = require("express"); 2 | const db = require('../models.js'); 3 | const userController = {}; 4 | 5 | // ***** Add user ***** // 6 | userController.addUser = async (req, res, next) => { 7 | try { 8 | const queryText = 'INSERT INTO users (username) VALUES ($1) RETURNING *;'; 9 | const { username } = req.body; 10 | const values = [username]; 11 | 12 | await db.query(queryText, values, 13 | (err, response) => { 14 | res.locals.data = response.rows[0]; 15 | }) 16 | } catch(err) { 17 | return next(err); 18 | } 19 | } 20 | 21 | // ***** Get favorites, returns array of favorite names ***** // 22 | userController.getFavorites = async (req, res, next) => { 23 | try { 24 | const queryText = 'SELECT name FROM trails WHERE id = (SELECT trail_id FROM faves WHERE user_id=$1);'; 25 | const { user_id } = req.body; 26 | const values = [user_id]; 27 | await db.query(queryText, values, 28 | (err, response) => { 29 | res.locals.data = response.rows; 30 | }) 31 | } catch(err) { 32 | return next(err); 33 | } 34 | } 35 | 36 | // ***** Add favorite ***** // 37 | userController.addFavorite = async (req, res, next) => { 38 | try { 39 | const queryText = 'INSERT INTO faves (user_id, trail_id) VALUES ($1, $2) RETURNING *;'; 40 | const { user_id, trail_id } = req.body; 41 | const values = [user_id, trail_id]; 42 | 43 | await db.query(queryText, values, 44 | (err, response) => { 45 | return next(); 46 | } 47 | ) 48 | } catch(err) { 49 | return next(err); 50 | } 51 | } 52 | 53 | // ***** Delete favorite ***** // 54 | userController.deleteFavorite = async (req, res, next) => { 55 | try{ 56 | const queryText = 'DELETE FROM faves WHERE user_id = $1 AND trail_id = $2 RETURNING user_id;'; 57 | const { user_id, trail_id } = req.body; 58 | const values = [user_id, trail_id]; 59 | 60 | await db.query(queryText, values, 61 | (err, response) => { 62 | return next(); 63 | }) 64 | } catch(err) { 65 | return next(err); 66 | } 67 | } 68 | 69 | module.exports = userController; -------------------------------------------------------------------------------- /screens/SplashLoginPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, useEffect, useState } from "react"; 2 | import { 3 | StyleSheet, 4 | Text, 5 | View, 6 | Button, 7 | Image, 8 | Alert, 9 | Modal, 10 | TouchableHighlight, 11 | TouchableOpacity, 12 | ScrollView, 13 | SafeAreaView, 14 | TouchableWithoutFeedback 15 | } from "react-native"; 16 | import * as Google from "expo-google-app-auth"; 17 | import secret from "../secrets" 18 | 19 | const IOS_CLIENT_ID = secret.google_client_id; 20 | const FB_APP_ID = secret.facebook_app_id; 21 | 22 | export function Splash({ navigation }) { 23 | 24 | signInWithGoogle = async () => { 25 | try { 26 | const result = await Google.logInAsync({ 27 | iosClientId: IOS_CLIENT_ID, 28 | scopes: ["profile", "email"] 29 | }); 30 | 31 | if (result.type === "success") { 32 | console.log("LoginScreen.js.js 21 | ", result.user.givenName); 33 | this.props.navigation.navigate("Profile", { 34 | username: result.user.givenName 35 | }); 36 | return result.accessToken; 37 | } else { 38 | return { cancelled: true }; 39 | } 40 | } catch (e) { 41 | console.log('LoginScreen.js.js 30 | Error with login', e); 42 | return { error: true }; 43 | } 44 | }; 45 | 46 | return ( 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | {/* */} 61 | 62 | ); 63 | } 64 | 65 | const styles = StyleSheet.create({ 66 | container: { 67 | flex: 1, 68 | backgroundColor: "red", 69 | }, 70 | faveContainer: { 71 | flex: 1, 72 | backgroundColor: "#fff", 73 | }, 74 | map: { 75 | flex: 1, 76 | }, 77 | googleButton: { 78 | flexDirection: "row", 79 | alignItems: "center", 80 | backgroundColor: "#FFFFFF", 81 | borderWidth: 0.5, 82 | borderColor: "#fff", 83 | height: 40, 84 | borderRadius: 5, 85 | margin: 5, 86 | }, 87 | }) -------------------------------------------------------------------------------- /server/controllers/mapController.js: -------------------------------------------------------------------------------- 1 | const fetch = require("node-fetch"); 2 | const secret = require("../../secrets"); 3 | 4 | const mapController = {}; 5 | 6 | //assign API keys to variables 7 | const privateKey = secret.privateKey; 8 | const publicKey = secret.publicKey; 9 | 10 | //assign API forecast ids to locations 11 | const locations = { 12 | switzerFalls: secret.sw_falls_id, 13 | griffithPark: secret.griffith_park_id, 14 | elysianPark: secret.elysian_park_id, 15 | eatonCanyon: secret.eaton_canyon_id, 16 | runyonCanyon: secret.runyon_canyon_id, 17 | }; 18 | 19 | //convert units from the API measurement to the frontend Heatmap measurement 20 | const conversion = { 21 | "-2": 10, 22 | "-1": 30, 23 | 0: 50, 24 | 1: 70, 25 | 2: 90, 26 | }; 27 | 28 | const latlong = { 29 | switzerFalls: { latitude: 34.2638892, longitude: -118.1740902 }, 30 | griffithPark: { latitude: 34.1281475, longitude: -118.3010914 }, 31 | elysianPark: { latitude: 34.0820739, longitude: -118.2497133 }, 32 | eatonCanyon: { latitude: 34.1783564, longitude: -118.0966051 }, 33 | runyonCanyon: { latitude: 34.1193155, longitude: -118.353079 }, 34 | }; 35 | 36 | mapController.getHeat = (req, res, next) => { 37 | //init an empty array to hold promises that resolve to trailDataObjs 38 | const trailDataArray = []; 39 | 40 | //iterate through all locations 41 | for (let trail in locations) { 42 | //create a new promise for each location 43 | const trailDataPromise = new Promise((resolve, reject) => { 44 | //async operation: fetch request to API 45 | fetch(`https://besttime.app/api/v1/forecasts/now?api_key_public=${publicKey}&venue_id=${locations[trail]}`) 46 | .then((data) => { 47 | data.json().then((parsedData) => { 48 | // console.log('data returned from API', parsedData) 49 | const trailName = parsedData.venue_info.venue_name; 50 | const weight = conversion[parsedData.analysis.hour_analysis.intensity_nr]; 51 | const trailData = { 52 | trailName, 53 | heatMap: { 54 | latitude: latlong[trail].latitude, 55 | longitude: latlong[trail].longitude, 56 | weight, 57 | } 58 | }; 59 | //when promise resolves, trailDataObj is returned 60 | resolve(trailData); 61 | }); 62 | }) 63 | .catch((err) => { 64 | return next(err); 65 | }) 66 | }) 67 | //push promises into array 68 | trailDataArray.push(trailDataPromise) 69 | } 70 | 71 | //this waits for all promises to resolve to trailDataObjs 72 | Promise.all(trailDataArray) 73 | //then that array is saved into an object with all location names 74 | .then((trailDataArray) => { 75 | const heatMapStats = []; 76 | const trailNames = []; 77 | trailDataArray.forEach(dataObj => { 78 | console.log(dataObj); 79 | heatMapStats.push(dataObj.heatMap); 80 | trailNames.push(dataObj.trailName); 81 | }) 82 | const mapInfo = { 83 | heatMapStats, 84 | trailNames 85 | } 86 | res.locals.data = mapInfo; 87 | }) 88 | .then(() => next()) 89 | 90 | }; 91 | 92 | module.exports = mapController; 93 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { 3 | StyleSheet, 4 | Text, 5 | View, 6 | Button, 7 | Image, 8 | Alert, 9 | Modal, 10 | TouchableHighlight, 11 | TouchableOpacity, 12 | ScrollView, 13 | SafeAreaView, 14 | } from "react-native"; 15 | import { Card, ListItem } from "react-native-elements"; 16 | import { NavigationContainer } from "@react-navigation/native"; 17 | import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; 18 | import Ionicons from "react-native-vector-icons/Ionicons"; 19 | import Icon from "react-native-vector-icons/FontAwesome"; 20 | import { MapPage } from "./screens/MapPage.jsx"; 21 | import { Splash } from "./screens/SplashLoginPage.jsx"; 22 | 23 | // import { 24 | // GoogleSignin, 25 | // GoogleSigninButton, 26 | // statusCodes, 27 | // } from "@react-native-community/google-signin"; 28 | 29 | // GoogleSignin.configure(); 30 | 31 | //========================================= FAVORITES PAGE ================================================// 32 | 33 | function Favorites({ navigation }) { 34 | const [trails, setTrails] = useState([]); 35 | const [like, setDislike] = useState({ 36 | 0: true, 37 | 1: true, 38 | 2: true, 39 | 3: true, 40 | 4: true, 41 | }); 42 | 43 | useEffect(() => { 44 | fetch("http://47.232.193.179:5001/api/getData") 45 | .then((res) => res.json()) 46 | .then((res) => setTrails(res.trailNames)) 47 | .catch((err) => console.log(err)); 48 | }, []); 49 | 50 | return ( 51 | 52 | 53 | 54 |