├── client ├── src │ ├── routes │ │ ├── maps │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── directions │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── signin │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── settings │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── account │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── signout │ │ │ └── index.js │ │ ├── profile │ │ │ ├── style.css │ │ │ └── index.js │ │ └── home │ │ │ ├── style.css │ │ │ └── index.js │ ├── components │ │ ├── Logo │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── LeafletOsmMap │ │ │ ├── style.css │ │ │ └── MapPane.js │ │ ├── Search │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── SearchResults │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── SavedPinsCard │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── SearchHistoryCard │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── ForgotPasswordForm │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── Nav │ │ │ ├── style.css │ │ │ └── index.js │ │ ├── app.js │ │ └── AccountForm │ │ │ ├── style.css │ │ │ └── index.js │ ├── assets │ │ ├── favicon.ico │ │ ├── logos │ │ │ ├── navi_logo.png │ │ │ ├── navi_logo_lg.png │ │ │ └── navi_logo_small.png │ │ ├── font │ │ │ ├── OpenSans-Bold.ttf │ │ │ ├── OpenSans-Light.ttf │ │ │ ├── OpenSans-Italic.ttf │ │ │ ├── OpenSans-Regular.ttf │ │ │ ├── OpenSans-BoldItalic.ttf │ │ │ ├── OpenSans-ExtraBold.ttf │ │ │ ├── OpenSans-SemiBold.ttf │ │ │ ├── OpenSans-LightItalic.ttf │ │ │ ├── OpenSans-ExtraBoldItalic.ttf │ │ │ └── OpenSans-SemiBoldItalic.ttf │ │ ├── icons │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon-48x48.png │ │ │ ├── favicon-96x96.png │ │ │ ├── favicon-128x128.png │ │ │ ├── favicon-144x144.png │ │ │ ├── favicon-256x256.png │ │ │ ├── favicon-384x384.png │ │ │ ├── favicon-512x512.png │ │ │ ├── leaflet │ │ │ │ ├── layers.png │ │ │ │ ├── layers-2x.png │ │ │ │ ├── marker-icon.png │ │ │ │ ├── marker-icon-2x.png │ │ │ │ ├── marker-icon-fav.png │ │ │ │ ├── marker-shadow.png │ │ │ │ ├── marker-icon-fav-2x.png │ │ │ │ ├── drawable-hdpi │ │ │ │ │ ├── darkLogo.png │ │ │ │ │ ├── ProfileIcon.png │ │ │ │ │ ├── whiteLogo.png │ │ │ │ │ ├── NaviHomeIcon.png │ │ │ │ │ ├── DestinationPin.png │ │ │ │ │ ├── FindLocationPin.png │ │ │ │ │ ├── Get Directions.png │ │ │ │ │ ├── CurrentLocationPin.png │ │ │ │ │ ├── FoundLocationSpot.png │ │ │ │ │ └── ChangeDirectionsIcon.png │ │ │ │ ├── drawable-ldpi │ │ │ │ │ ├── darkLogo.png │ │ │ │ │ ├── ProfileIcon.png │ │ │ │ │ ├── whiteLogo.png │ │ │ │ │ ├── NaviHomeIcon.png │ │ │ │ │ ├── DestinationPin.png │ │ │ │ │ ├── FindLocationPin.png │ │ │ │ │ ├── Get Directions.png │ │ │ │ │ ├── CurrentLocationPin.png │ │ │ │ │ ├── FoundLocationSpot.png │ │ │ │ │ └── ChangeDirectionsIcon.png │ │ │ │ ├── drawable-mdpi │ │ │ │ │ ├── darkLogo.png │ │ │ │ │ ├── ProfileIcon.png │ │ │ │ │ ├── whiteLogo.png │ │ │ │ │ ├── NaviHomeIcon.png │ │ │ │ │ ├── DestinationPin.png │ │ │ │ │ ├── FindLocationPin.png │ │ │ │ │ ├── Get Directions.png │ │ │ │ │ ├── CurrentLocationPin.png │ │ │ │ │ ├── FoundLocationSpot.png │ │ │ │ │ └── ChangeDirectionsIcon.png │ │ │ │ ├── drawable-xhdpi │ │ │ │ │ ├── darkLogo.png │ │ │ │ │ ├── whiteLogo.png │ │ │ │ │ ├── NaviHomeIcon.png │ │ │ │ │ ├── ProfileIcon.png │ │ │ │ │ ├── DestinationPin.png │ │ │ │ │ ├── FindLocationPin.png │ │ │ │ │ ├── Get Directions.png │ │ │ │ │ ├── FoundLocationSpot.png │ │ │ │ │ ├── ChangeDirectionsIcon.png │ │ │ │ │ └── CurrentLocationPin.png │ │ │ │ ├── drawable-xxhdpi │ │ │ │ │ ├── darkLogo.png │ │ │ │ │ ├── whiteLogo.png │ │ │ │ │ ├── ProfileIcon.png │ │ │ │ │ ├── DestinationPin.png │ │ │ │ │ ├── Get Directions.png │ │ │ │ │ ├── NaviHomeIcon.png │ │ │ │ │ ├── FindLocationPin.png │ │ │ │ │ ├── CurrentLocationPin.png │ │ │ │ │ ├── FoundLocationSpot.png │ │ │ │ │ └── ChangeDirectionsIcon.png │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ │ ├── darkLogo.png │ │ │ │ │ ├── whiteLogo.png │ │ │ │ │ ├── NaviHomeIcon.png │ │ │ │ │ ├── ProfileIcon.png │ │ │ │ │ ├── DestinationPin.png │ │ │ │ │ ├── FindLocationPin.png │ │ │ │ │ ├── Get Directions.png │ │ │ │ │ ├── CurrentLocationPin.png │ │ │ │ │ ├── FoundLocationSpot.png │ │ │ │ │ └── ChangeDirectionsIcon.png │ │ │ │ └── SVG │ │ │ │ │ ├── DestinationPin.svg │ │ │ │ │ ├── ProfileIcon.svg │ │ │ │ │ ├── FindLocationPin.svg │ │ │ │ │ ├── FoundLocationSpot.svg │ │ │ │ │ ├── CurrentLocationPin.svg │ │ │ │ │ ├── NaviHomeIcon.svg │ │ │ │ │ ├── ChangeDirectionsIcon.svg │ │ │ │ │ ├── Get Directions.svg │ │ │ │ │ ├── whiteLogo.svg │ │ │ │ │ └── darkLogo.svg │ │ │ ├── mstile-150x150.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ └── favicon-package │ │ │ │ ├── favicon.ico │ │ │ │ ├── favicon-16x16.png │ │ │ │ ├── favicon-32x32.png │ │ │ │ ├── mstile-150x150.png │ │ │ │ ├── apple-touch-icon.png │ │ │ │ ├── android-chrome-192x192.png │ │ │ │ ├── android-chrome-256x256.png │ │ │ │ ├── browserconfig.xml │ │ │ │ ├── site.webmanifest │ │ │ │ └── safari-pinned-tab.svg │ │ └── images │ │ │ └── background-image.jpg │ ├── index.js │ ├── js │ │ ├── utilities.js │ │ ├── server-requests-utils.js │ │ ├── saved-places.js │ │ └── validate-account-form.js │ ├── style │ │ └── index.css │ ├── manifest.json │ └── sw.js ├── config │ ├── prod.config.js │ ├── dev.config.js │ ├── test.config.js │ └── index.js ├── .eslintrc.json ├── README.md ├── jest.config.js ├── .gitignore ├── package.json └── tests │ └── AccountForm.test.js ├── images ├── blue-theme.png └── green-theme.png ├── .eslintrc.json ├── docs ├── style.css ├── color-scheme.md ├── index.html ├── leaflet.html ├── learning.html ├── CONTRIBUTORS.html ├── README.md └── CONTRIBUTING.md ├── routes ├── index.js ├── map.js ├── users.js └── search.js ├── config ├── index.js ├── prod.config.js ├── test.config.js ├── dev.config.js └── secrets-example..json ├── models ├── users.js ├── search-history.js ├── saved-pins.js └── saved-directions.js ├── server.js ├── tests ├── server.test.js ├── apiRoutes.js ├── seed │ └── seed.js ├── saved-pins-test.js └── users-test.js ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── color-scheme.md ├── .gitignore ├── app.js ├── package.json ├── controllers ├── utils-controller.js ├── search-history-controller.js ├── saved-directions-controller.js └── saved-pins-controller.js ├── CODE_OF_CONDUCT.md ├── README.md └── CONTRIBUTING.md /client/src/routes/maps/style.css: -------------------------------------------------------------------------------- 1 | .maps { 2 | width: 100vw; 3 | display: block; 4 | } 5 | -------------------------------------------------------------------------------- /images/blue-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/images/blue-theme.png -------------------------------------------------------------------------------- /images/green-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/images/green-theme.png -------------------------------------------------------------------------------- /client/config/prod.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | APP_NAME: 'navi', 3 | API_SERVER: '' 4 | }; -------------------------------------------------------------------------------- /client/src/components/Logo/style.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | padding: 5px; 3 | text-align: center; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /client/src/routes/directions/style.css: -------------------------------------------------------------------------------- 1 | .directions { 2 | height: 95vh; 3 | width: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /client/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/favicon.ico -------------------------------------------------------------------------------- /client/config/dev.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | APP_NAME: 'navi', 3 | API_SERVER: 'http://localhost:8081', 4 | }; 5 | -------------------------------------------------------------------------------- /client/src/assets/logos/navi_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/logos/navi_logo.png -------------------------------------------------------------------------------- /client/config/test.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | APP_NAME: 'navi', 3 | API_SERVER: 'http://localhost:8081', 4 | }; 5 | -------------------------------------------------------------------------------- /client/src/assets/font/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/font/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /client/src/assets/font/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/font/OpenSans-Light.ttf -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-16x16.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-32x32.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-48x48.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-96x96.png -------------------------------------------------------------------------------- /client/src/assets/logos/navi_logo_lg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/logos/navi_logo_lg.png -------------------------------------------------------------------------------- /client/src/components/LeafletOsmMap/style.css: -------------------------------------------------------------------------------- 1 | .fullscreen { 2 | height: inherit; 3 | width: inherit; 4 | display: block; 5 | } 6 | -------------------------------------------------------------------------------- /client/src/assets/font/OpenSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/font/OpenSans-Italic.ttf -------------------------------------------------------------------------------- /client/src/assets/font/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/font/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-128x128.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-144x144.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-256x256.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-384x384.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-512x512.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/layers.png -------------------------------------------------------------------------------- /client/src/assets/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/mstile-150x150.png -------------------------------------------------------------------------------- /client/src/assets/logos/navi_logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/logos/navi_logo_small.png -------------------------------------------------------------------------------- /client/src/assets/font/OpenSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/font/OpenSans-BoldItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/font/OpenSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/font/OpenSans-ExtraBold.ttf -------------------------------------------------------------------------------- /client/src/assets/font/OpenSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/font/OpenSans-SemiBold.ttf -------------------------------------------------------------------------------- /client/src/assets/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/layers-2x.png -------------------------------------------------------------------------------- /client/src/assets/images/background-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/images/background-image.jpg -------------------------------------------------------------------------------- /client/src/routes/signin/style.css: -------------------------------------------------------------------------------- 1 | .signin { 2 | padding: 56px 20px; 3 | min-height: 100%; 4 | width: 100%; 5 | } 6 | 7 | .main { 8 | } 9 | -------------------------------------------------------------------------------- /client/src/assets/font/OpenSans-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/font/OpenSans-LightItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/marker-icon.png -------------------------------------------------------------------------------- /client/src/assets/font/OpenSans-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/font/OpenSans-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/font/OpenSans-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/font/OpenSans-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /client/src/assets/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-package/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-package/favicon.ico -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/marker-icon-2x.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/marker-icon-fav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/marker-icon-fav.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/marker-shadow.png -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "env": { 4 | "node": true, 5 | "mocha": true, 6 | "jasmine": true 7 | } 8 | } -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/marker-icon-fav-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/marker-icon-fav-2x.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-package/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-package/favicon-16x16.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-package/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-package/favicon-32x32.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-package/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-package/mstile-150x150.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-hdpi/darkLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-hdpi/darkLogo.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-ldpi/darkLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-ldpi/darkLogo.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-mdpi/darkLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-mdpi/darkLogo.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-package/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-package/apple-touch-icon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-hdpi/ProfileIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-hdpi/ProfileIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-hdpi/whiteLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-hdpi/whiteLogo.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-ldpi/ProfileIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-ldpi/ProfileIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-ldpi/whiteLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-ldpi/whiteLogo.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-mdpi/ProfileIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-mdpi/ProfileIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-mdpi/whiteLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-mdpi/whiteLogo.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xhdpi/darkLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xhdpi/darkLogo.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xhdpi/whiteLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xhdpi/whiteLogo.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxhdpi/darkLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxhdpi/darkLogo.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxhdpi/whiteLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxhdpi/whiteLogo.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxxhdpi/darkLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxxhdpi/darkLogo.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-hdpi/NaviHomeIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-hdpi/NaviHomeIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-ldpi/NaviHomeIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-ldpi/NaviHomeIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-mdpi/NaviHomeIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-mdpi/NaviHomeIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xhdpi/NaviHomeIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xhdpi/NaviHomeIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xhdpi/ProfileIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xhdpi/ProfileIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxhdpi/ProfileIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxhdpi/ProfileIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxxhdpi/whiteLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxxhdpi/whiteLogo.png -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "plugins": [ 4 | "react", 5 | "jsx-a11ly", 6 | "Import", 7 | "react-compat" 8 | ] 9 | } -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-package/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-package/android-chrome-192x192.png -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-package/android-chrome-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/favicon-package/android-chrome-256x256.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-hdpi/DestinationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-hdpi/DestinationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-hdpi/FindLocationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-hdpi/FindLocationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-hdpi/Get Directions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-hdpi/Get Directions.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-ldpi/DestinationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-ldpi/DestinationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-ldpi/FindLocationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-ldpi/FindLocationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-ldpi/Get Directions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-ldpi/Get Directions.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-mdpi/DestinationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-mdpi/DestinationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-mdpi/FindLocationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-mdpi/FindLocationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-mdpi/Get Directions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-mdpi/Get Directions.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xhdpi/DestinationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xhdpi/DestinationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xhdpi/FindLocationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xhdpi/FindLocationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xhdpi/Get Directions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xhdpi/Get Directions.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxhdpi/DestinationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxhdpi/DestinationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxhdpi/Get Directions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxhdpi/Get Directions.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxhdpi/NaviHomeIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxhdpi/NaviHomeIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxxhdpi/NaviHomeIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxxhdpi/NaviHomeIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxxhdpi/ProfileIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxxhdpi/ProfileIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-hdpi/CurrentLocationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-hdpi/CurrentLocationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-hdpi/FoundLocationSpot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-hdpi/FoundLocationSpot.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-ldpi/CurrentLocationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-ldpi/CurrentLocationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-ldpi/FoundLocationSpot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-ldpi/FoundLocationSpot.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-mdpi/CurrentLocationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-mdpi/CurrentLocationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-mdpi/FoundLocationSpot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-mdpi/FoundLocationSpot.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xhdpi/FoundLocationSpot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xhdpi/FoundLocationSpot.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxhdpi/FindLocationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxhdpi/FindLocationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxxhdpi/DestinationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxxhdpi/DestinationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxxhdpi/FindLocationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxxhdpi/FindLocationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxxhdpi/Get Directions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxxhdpi/Get Directions.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-hdpi/ChangeDirectionsIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-hdpi/ChangeDirectionsIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-ldpi/ChangeDirectionsIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-ldpi/ChangeDirectionsIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-mdpi/ChangeDirectionsIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-mdpi/ChangeDirectionsIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xhdpi/ChangeDirectionsIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xhdpi/ChangeDirectionsIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xhdpi/CurrentLocationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xhdpi/CurrentLocationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxhdpi/CurrentLocationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxhdpi/CurrentLocationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxhdpi/FoundLocationSpot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxhdpi/FoundLocationSpot.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxxhdpi/CurrentLocationPin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxxhdpi/CurrentLocationPin.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxxhdpi/FoundLocationSpot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxxhdpi/FoundLocationSpot.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxhdpi/ChangeDirectionsIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxhdpi/ChangeDirectionsIcon.png -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/drawable-xxxhdpi/ChangeDirectionsIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDevPath/Navi/HEAD/client/src/assets/icons/leaflet/drawable-xxxhdpi/ChangeDirectionsIcon.png -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | width: 100px; 3 | } 4 | 5 | .txt-cntr{ 6 | text-align: center; 7 | } 8 | 9 | #inspiration{ 10 | display: block; 11 | height: 50px; 12 | width: 50px; 13 | } -------------------------------------------------------------------------------- /client/src/routes/settings/style.css: -------------------------------------------------------------------------------- 1 | .register { 2 | padding: 56px 20px; 3 | min-height: 100%; 4 | width: 100%; 5 | } 6 | 7 | .logo { 8 | padding: 5px; 9 | } 10 | 11 | .main { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /client/src/routes/account/style.css: -------------------------------------------------------------------------------- 1 | .inherit { 2 | height: inherit; 3 | width: inherit; 4 | display: inherit; 5 | } 6 | 7 | .main { 8 | display: block; 9 | width: 100vw; 10 | position: fixed; 11 | top: 7vh; 12 | } 13 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | let express = require('express'); 2 | 3 | let router = express.Router(); 4 | 5 | /* GET home page. */ 6 | router.get('/', (req, res, next) => { 7 | res.send('Hello World!'); 8 | }); 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./prod.config.js'); 3 | } else if (process.env.NODE_ENV === 'test') { 4 | module.exports = require('./test.config.js'); 5 | } else { 6 | module.exports = require('./dev.config.js'); 7 | } 8 | -------------------------------------------------------------------------------- /config/prod.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: process.env.NODE_ENV || 'production', 3 | HOST: process.env.HOST, 4 | PORT: process.env.PORT, 5 | DB_URL: process.env.MONGODB_URI, 6 | GOOGLE_API_KEY: process.env.GOOGLE_API_KEY, 7 | JWT_KEY: process.env.JWT_KEY, 8 | }; 9 | -------------------------------------------------------------------------------- /client/config/index.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./prod.config.js'); 3 | } else if (process.env.NODE_ENV === 'test') { 4 | module.exports = require('./test.config.js'); 5 | } else { 6 | module.exports = require('./dev.config.js'); 7 | } -------------------------------------------------------------------------------- /client/src/components/Search/style.css: -------------------------------------------------------------------------------- 1 | .form { 2 | width: 100%; 3 | background-color: transparent; 4 | z-index: 1000; 5 | display: block; 6 | position: fixed; 7 | top: 10vh; 8 | } 9 | 10 | .search { 11 | width: 80vw; 12 | text-align: center; 13 | display: inherit; 14 | margin: auto; 15 | } -------------------------------------------------------------------------------- /client/src/components/SearchResults/style.css: -------------------------------------------------------------------------------- 1 | ul { 2 | display: inherit; 3 | padding: 0vw 12vw; 4 | } 5 | 6 | .list { 7 | height: 2vh; 8 | width: 100%; 9 | } 10 | 11 | .mainText { 12 | font-weight: bold; 13 | margin-bottom: 0px; 14 | } 15 | 16 | .secondaryText { 17 | margin-top: 0px; 18 | } 19 | -------------------------------------------------------------------------------- /models/users.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const usersSchema = new mongoose.Schema({ 4 | name: { type: String, required: true }, 5 | email: { type: String, required: true }, 6 | password: { type: String, required: true }, 7 | }); 8 | 9 | module.exports = mongoose.model('Users', usersSchema); 10 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | 3 | const config = require('./config'); 4 | const app = require('./app.js'); 5 | 6 | const server = http.createServer(app); 7 | 8 | server.listen(config.PORT, () => { 9 | console.info(`Server is running at ${config.HOST}:${config.PORT}`); 10 | }); 11 | 12 | module.exports = server; 13 | -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-package/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #ffc40d 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/server.test.js: -------------------------------------------------------------------------------- 1 | // it('test runner should work', (done) => { 2 | // const foo = 'bar'; 3 | // const beverages = { tea: ['chai', 'matcha', 'oolong'] }; 4 | 5 | // expect(foo).to.be.a('string'); 6 | // expect(foo).to.equal('bar'); 7 | // expect(foo).to.have.lengthOf(3); 8 | // expect(beverages).to.have.property('tea').with.lengthOf(3); 9 | // done(); 10 | // }); 11 | -------------------------------------------------------------------------------- /client/src/components/LeafletOsmMap/MapPane.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | 3 | export default class MapPane extends Component { 4 | render() { 5 | const styles = { 6 | height: this.props.paneHeight, // HAVE TO SET HEIGHT TO RENDER MAP 7 | } 8 | return ( 9 |
10 | ) 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /config/test.config.js: -------------------------------------------------------------------------------- 1 | const SECRETS = require('./secrets.json'); // nodejs will auto read json 2 | 3 | module.exports = { 4 | NODE_ENV: process.env.NODE_ENV || 'test', 5 | HOST: process.env.HOST || 'http://localhost', 6 | PORT: process.env.PORT || 8081, 7 | DB_URL: SECRETS.mongodb_test.db_url, 8 | GOOGLE_API_KEY: SECRETS.google_maps.api_key, 9 | JWT_KEY: SECRETS.jwt.key, 10 | }; 11 | -------------------------------------------------------------------------------- /models/search-history.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | const searchHistorySchema = new Schema({ 5 | query: { type: String, required: true }, 6 | save_date: { type: Date, default: Date.now }, 7 | user: { type: Schema.Types.ObjectId, ref: 'User' }, 8 | }); 9 | 10 | module.exports = mongoose.model('SearchHistory', searchHistorySchema); 11 | -------------------------------------------------------------------------------- /config/dev.config.js: -------------------------------------------------------------------------------- 1 | const SECRETS = require('./secrets.json'); // nodejs will auto read json 2 | 3 | module.exports = { 4 | NODE_ENV: process.env.NODE_ENV || 'development', 5 | HOST: process.env.HOST || 'http://localhost', 6 | PORT: process.env.PORT || 8081, 7 | DB_URL: SECRETS.mongodb_dev.db_url, 8 | GOOGLE_API_KEY: SECRETS.google_maps.api_key, 9 | JWT_KEY: SECRETS.jwt.key, 10 | }; 11 | -------------------------------------------------------------------------------- /client/src/components/Logo/index.js: -------------------------------------------------------------------------------- 1 | import {h, Component} from 'preact'; 2 | import style from './style'; 3 | 4 | const { APP_NAME } = require('../../../config'); 5 | 6 | export default class Logo extends Component { 7 | render(){ 8 | return ( 9 |
10 | 11 |

Welcome!

12 |
13 | ); 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /client/src/routes/signout/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { logout } from "../../js/validate-account-form"; 3 | import Home from '../home'; 4 | 5 | export default class SignOut extends Component { 6 | componentWillMount() { 7 | logout(); 8 | } 9 | 10 | render() { 11 | window.history.pushState({url: '/'}, 'Home', '/'); 12 | return () 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # {{ name }} 2 | 3 | ## CLI Commands 4 | 5 | ``` bash 6 | # install dependencies 7 | npm install 8 | 9 | # serve with hot reload at localhost:8080 10 | npm run dev 11 | 12 | # build for production with minification 13 | npm run build 14 | 15 | # test the production build locally 16 | npm run serve 17 | ``` 18 | 19 | For detailed explanation on how things work, checkout the [CLI Readme](https://github.com/developit/preact-cli/blob/master/README.md). -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Issue Number: 2 | 3 | ## Issue Description: 4 | 5 | 6 | ### Summary of solution: 7 | 1. 8 | 2. 9 | 3. 10 | 11 | ### Can this issue be closed? 12 | 13 | ### Should any new issues be added as a result of this solution? 14 | 15 | ### Have you named your branch in a descriptive way? Remember to name your branch in a unique and descriptive manner in order to properly reflect the issue or feature. 16 | 17 | ### Thanks for contributing! 18 | -------------------------------------------------------------------------------- /client/src/components/SavedPinsCard/style.css: -------------------------------------------------------------------------------- 1 | .savedPinsCard { 2 | border-radius: 3px; 3 | border-width: 2px; 4 | border-style: groove; 5 | margin-bottom: 10px; 6 | padding-left: 5px; 7 | } 8 | 9 | #savedPinsCardTitle { 10 | display: block; 11 | font-size: 1em; 12 | font-weight: bold; 13 | text-align: center; 14 | color: purple; 15 | margin: 0 auto; 16 | } 17 | 18 | .savedPinsContainer{ 19 | text-align: center; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /client/src/components/SearchHistoryCard/style.css: -------------------------------------------------------------------------------- 1 | .searchHistoryCard { 2 | border-radius: 3px; 3 | border-width: 2px; 4 | border-style: groove; 5 | margin-bottom: 10px; 6 | padding-left: 5px; 7 | } 8 | 9 | #searchHistoryCardTitle { 10 | display: block; 11 | font-size: 1em; 12 | font-weight: bold; 13 | text-align: center; 14 | color: purple; 15 | margin: 0 auto; 16 | } 17 | 18 | .searchHistoriesContainer{ 19 | text-align: center; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Do you want to request a feature or report a bug? 2 | 3 | If this is a feature request, what is motivation or use case for changing the behavior? 4 | 5 | If the current behavior is a bug, please provide the steps to reproduce. 6 | 7 | What is the expected behavior? 8 | 9 | What is the current behavior? 10 | 11 | Please mention other relevant information. 12 | 13 | Node version 14 | npm version 15 | Operating system 16 | CLI version 17 | Browser 18 | Device 19 | -------------------------------------------------------------------------------- /client/src/routes/profile/style.css: -------------------------------------------------------------------------------- 1 | .profile { 2 | padding: 5vh 5vw; 3 | width: 100%; 4 | } 5 | 6 | .successMessage{ 7 | text-align: center; 8 | font-weight: bold; 9 | padding: 5px; 10 | } 11 | 12 | .link { 13 | padding: 5px; 14 | } 15 | 16 | .link a { 17 | text-decoration: none; 18 | } 19 | 20 | .links_container{ 21 | text-align: center; 22 | } 23 | 24 | form { 25 | text-align: center; 26 | margin-bottom: 2vh; 27 | } 28 | 29 | fieldset { 30 | border-radius: 3px; 31 | } 32 | -------------------------------------------------------------------------------- /client/src/routes/settings/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import style from './style'; 3 | import AccountForm from "../../components/AccountForm"; 4 | 5 | 6 | export default class Settings extends Component { 7 | 8 | render() { 9 | return ( 10 |
11 |
Reset password
12 |
Sign Out
13 |
14 | ); 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /client/src/routes/maps/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from "preact"; 2 | import style from "./style"; 3 | import MapContainer from '../../components/LeafletOsmMap'; 4 | 5 | export default class MapExplorer extends Component { 6 | render() { 7 | return ( 8 |
9 | 11 |
12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /models/saved-pins.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const savedPinsSchema = new mongoose.Schema({ 4 | lat: { 5 | type: Number, min: -90, max: 90, required: true, 6 | }, 7 | lng: { 8 | type: Number, min: -180, max: 180, required: true, 9 | }, 10 | place_id: { type: String, default: '' }, 11 | desc: { type: String, default: '' }, 12 | save_date: { type: Date, default: Date.now }, 13 | user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, 14 | }); 15 | 16 | module.exports = mongoose.model('SavedPins', savedPinsSchema); 17 | -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-package/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Navi", 3 | "short_name": "Navi", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-256x256.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#29c0d1", 17 | "background_color": "#F7FFF7", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /tests/apiRoutes.js: -------------------------------------------------------------------------------- 1 | // api routes tests 2 | // const request = require('supertest'); 3 | 4 | // const app = require('../app'); 5 | 6 | // describe('API Routes', function() { 7 | // describe('GET /api/users/current', function() { 8 | // it('does not send a user if not logged in', async (done) => { 9 | // try { 10 | // const res = await request(app) 11 | // .get('/api/users/current') 12 | // .expect(401); 13 | // expect(res).to.be.an('object').notify(done); 14 | // } catch (error) { 15 | // throw error; 16 | // } 17 | // }); 18 | // }); 19 | // }); 20 | -------------------------------------------------------------------------------- /client/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "moduleNameMapper": { 3 | "^react-dom/server$": "/node_modules/preact-render-to-string/dist/index.js", 4 | "^react-addons-test-utils$": "/node_modules/preact-test-utils/lib/index.js", 5 | "^react$": "/node_modules/preact-compat-enzyme/lib/index.js", 6 | "^react-dom$": "/node_modules/preact-compat-enzyme/lib/index.js" 7 | }, 8 | "transform": {".*": "/node_modules/jest-css-modules"}, 9 | "setupFiles": [require.resolve('regenerator-runtime/runtime')], 10 | "snapshotSerializers": [ "preact-render-spy/snapshot" ] 11 | } -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/SVG/DestinationPin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import './style'; 2 | import App from './components/app'; 3 | 4 | /** 5 | * Register service worker anytime the main app component runs 6 | */ 7 | if ('serviceWorker' in navigator) { 8 | // register only when window load event fires to minimize performance on mobile devices 9 | window.addEventListener('load', function() { 10 | navigator.serviceWorker.register('/sw.js').then(function(registration) { 11 | // Registration was successful 12 | console.log('ServiceWorker registration successful with scope: ', registration.scope); 13 | }, function(err) { 14 | // registration failed :( 15 | console.log('ServiceWorker registration failed: ', err); 16 | }); 17 | }); 18 | } 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /models/saved-directions.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const Schema = mongoose.Schema; 4 | const savedDirectionsSchema = new Schema({ 5 | origin: { type: Schema.Types.Mixed, required: true }, 6 | destination: { type: Schema.Types.Mixed, required: true }, 7 | waypoints: { type: [Schema.Types.Mixed], required: true }, // array of {lat, lng} waypoints 8 | // routes: { type: [Schema.Types.Mixed], required: true }, 9 | directions: { type: [String], required: true}, // string/html turn directions for waypoints 10 | save_date: { type: Date, default: Date.now }, 11 | user: { type: Schema.Types.ObjectId, ref: 'User' }, 12 | label: { type: String }, // use provided label 13 | }); 14 | 15 | module.exports = mongoose.model('SavedDirections', savedDirectionsSchema); 16 | -------------------------------------------------------------------------------- /routes/map.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const router = express.Router(); 4 | 5 | // require controller modules 6 | const googleApiController = require('../controllers/google-api-controller'); 7 | 8 | /** 9 | * TODO - hook up url route end points to constroller functions 10 | */ 11 | 12 | // temporary hook to confirm base routing is working 13 | router.get('/', (req, res) => { 14 | res.status(404).send({ 15 | success: false, 16 | error: 'Not a valid end point!', 17 | }); 18 | }); 19 | 20 | router.get('/geolocation', googleApiController.getGeolocation); 21 | router.post('/staticmap', googleApiController.getStaticMap); 22 | router.post('/directions', googleApiController.directions); 23 | router.post('/geocode', googleApiController.geocode); 24 | 25 | module.exports = router; 26 | -------------------------------------------------------------------------------- /client/src/components/ForgotPasswordForm/style.css: -------------------------------------------------------------------------------- 1 | .forgot-password-form { 2 | width: inherit; 3 | height: inherit; 4 | margin-top: 15vh; 5 | } 6 | 7 | .forgot-password-form > h2 { 8 | margin: auto; 9 | text-align: center; 10 | } 11 | 12 | .forgot-password-form > p { 13 | padding: 3vw; 14 | text-align: center; 15 | font-size: 1.1em; 16 | } 17 | 18 | .form { 19 | display: grid; 20 | padding: 0 5vh; 21 | align-content: flex-start; 22 | } 23 | 24 | .formChild { 25 | display: block; 26 | margin: 2vh auto; 27 | width: 100%; 28 | padding: 3vw; 29 | border-color: lightslategray; 30 | border-radius: 8px; 31 | font-size: 1.2em; 32 | } 33 | 34 | .register { 35 | display: block; 36 | position: fixed; 37 | bottom: 5vh; 38 | } 39 | 40 | .register > p { 41 | text-align: center; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /client/src/js/utilities.js: -------------------------------------------------------------------------------- 1 | import {h, Component} from "preact"; 2 | import {makeRequest} from "./server-requests-utils"; 3 | 4 | function createButton(label, cssClass, container,id) { 5 | var btn = L.DomUtil.create('button', cssClass, container); 6 | btn.setAttribute('type', 'button'); 7 | btn.setAttribute('id',id); 8 | btn.innerHTML = label; 9 | return btn; 10 | } 11 | 12 | 13 | function createLabel(label, cssClass, container) { 14 | var displayLabel = L.DomUtil.create('div',cssClass, container); 15 | displayLabel.innerHTML = label; 16 | return displayLabel; 17 | } 18 | 19 | function createInput(input, cssClass, container) { 20 | var input = L.DomUtil.create('input', cssClass, container); 21 | input.setAttribute('type', 'input'); 22 | input.setAttribute('placeholder', 'Enter Description'); 23 | return input; 24 | } 25 | 26 | //Export Statements 27 | export {createLabel, createButton, createInput}; 28 | -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/SVG/ProfileIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /client/src/components/ForgotPasswordForm/index.js: -------------------------------------------------------------------------------- 1 | import {h, Component} from 'preact'; 2 | import {Link} from 'preact-router/match'; 3 | import PropTypes from 'prop-types'; 4 | import style from './style'; 5 | 6 | export default class ForgotPasswordForm extends Component { 7 | render() { 8 | return ( 9 |
10 |

Forgot Your Password?

11 |

Enter your email address below to reset your password.

12 |
13 | 15 | 16 |
17 |

Not a member? Sign up!

18 |
); 19 | } 20 | } 21 | 22 | ForgotPasswordForm.propTypes = { 23 | appName: PropTypes.string 24 | } 25 | -------------------------------------------------------------------------------- /client/src/routes/signin/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import style from './style'; 3 | import PropTypes from 'prop-types'; 4 | import SigninForm from '../../components/signinForm'; 5 | import ForgotPasswordForm from '../../components/ForgotPasswordForm'; 6 | import ResetPasswordForm from '../../components/ResetPasswordForm'; 7 | 8 | 9 | export default class Signin extends Component { 10 | 11 | render() { 12 | let renderedForm; 13 | if(this.props.path == '/forgot-password') { 14 | renderedForm = ( 15 | 16 | ) 17 | }else if(this.props.path == '/reset-password'){ 18 | renderedForm = ( 19 | 20 | ) 21 | }else{ 22 | renderedForm = ( 23 | 24 | ) 25 | } 26 | return ( 27 |
28 |
29 | {renderedForm} 30 |
31 |
32 | ); 33 | } 34 | } 35 | 36 | Signin.propTypes = { 37 | appName: PropTypes.string, 38 | } 39 | -------------------------------------------------------------------------------- /config/secrets-example..json: -------------------------------------------------------------------------------- 1 | { 2 | "google_maps": { 3 | "_comment": "Personal API key for google maps API servce", 4 | "api_key": "" 5 | 6 | }, 7 | 8 | "mongodb_dev": { 9 | "_comment": "Database URL path for dev", 10 | "db_url": "mongodb://localhost/app_db" 11 | }, 12 | 13 | "mongodb_test": { 14 | "_comment": "Database URL path for testing", 15 | "db_url": "mongodb://localhost/app_db_test" 16 | }, 17 | 18 | "mongodb_prod": { 19 | "_comment": "Database URL path for prod", 20 | "db_url": "" 21 | }, 22 | 23 | "jwt": { 24 | "_comment": "Private key with passphrase object to be used for generating tokens", 25 | "key": "passphrase" 26 | }, 27 | 28 | "other_secret_type": { 29 | "_comment": "Any API keys or other password-type strings can be stored like this.", 30 | "specific_secret_component":"component", 31 | "second_secret_component": "component" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/SVG/FindLocationPin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/SVG/FoundLocationSpot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /client/src/components/Nav/style.css: -------------------------------------------------------------------------------- 1 | .nav { 2 | width: 100%; 3 | display: block; 4 | /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#28c0d1+0,10416c+100 */ 5 | background: #28c0d1; /* Old browsers */ 6 | background: -moz-linear-gradient(-45deg, #28c0d1 0%, #10416c 100%); /* FF3.6-15 */ 7 | background: -webkit-linear-gradient(-45deg, #28c0d1 0%,#10416c 100%); /* Chrome10-25,Safari5.1-6 */ 8 | background: linear-gradient(135deg, #28c0d1 0%,#10416c 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 9 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#28c0d1', endColorstr='#10416c',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */ 10 | } 11 | 12 | .profileIcon { 13 | 14 | height: inherit; 15 | position: fixed; 16 | right: 0px; 17 | width: 40px; 18 | margin: 0 10px; 19 | } 20 | 21 | .homeIcon { 22 | 23 | height: inherit; 24 | width: 40px; 25 | margin: 0 10px; 26 | } 27 | 28 | @media only screen and (min-width: 800px) { 29 | 30 | } -------------------------------------------------------------------------------- /client/src/components/Nav/index.js: -------------------------------------------------------------------------------- 1 | import {h, Component} from 'preact'; 2 | import { route } from 'preact-router'; 3 | import style from './style.css'; 4 | 5 | export default class Nav extends Component { 6 | constructor(props) { 7 | super(props); 8 | 9 | this.routeToHome = this.routeToHome.bind(this); 10 | this.routeToProfile = this.routeToProfile.bind(this); 11 | } 12 | 13 | routeToHome() { 14 | route('/', true); 15 | }; 16 | 17 | routeToProfile() { 18 | route('/profile', true); 19 | }; 20 | 21 | render() { 22 | const styles = { 23 | height: this.props.navHeight, 24 | } 25 | return ( 26 |
27 | Home screen icon 29 | Profile page icon 31 |
32 | ); 33 | } 34 | } -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const usersController = require('../controllers/users-controller'); 3 | const { verifyToken } = require('../controllers/utils-controller'); 4 | 5 | const router = express.Router(); 6 | 7 | /** 8 | * Users endpoints 9 | */ 10 | /** 11 | * @description Handle requests to users main end point 12 | * 13 | * @api {GET} /users 14 | * @return {success: false, error: err} Not a valid end point 15 | */ 16 | router.get('/', (req, res) => { 17 | res.status(404).send({ 18 | success: false, 19 | error: 'Not a valid end point!', 20 | }); 21 | }); 22 | 23 | /** 24 | * Valid Users endpoints 25 | */ 26 | router.post('/register', usersController.registerUser); 27 | router.get('/user', verifyToken, usersController.getUser); 28 | router.post('/login', usersController.loginUser); 29 | router.get('/logout', usersController.logoutUser); 30 | router.post('/reset-password', verifyToken, usersController.resetPassword); 31 | router.post('/update', verifyToken, usersController.update); 32 | 33 | module.exports = router; 34 | -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/SVG/CurrentLocationPin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/SVG/NaviHomeIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bryan Sharpley 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 | -------------------------------------------------------------------------------- /client/src/routes/home/style.css: -------------------------------------------------------------------------------- 1 | .home { 2 | width: 100%; 3 | display: block; 4 | } 5 | 6 | .welcome { 7 | padding-top: 3vh; 8 | display: inherit; 9 | } 10 | 11 | .welcome > h2 { 12 | text-align: center; 13 | margin: 3vh auto auto; 14 | letter-spacing:3px; 15 | } 16 | 17 | .welcome > img { 18 | display: inherit; 19 | margin: auto; 20 | width: 50vw; 21 | } 22 | 23 | .search { 24 | padding-top: 5vh; 25 | display: inherit; 26 | } 27 | 28 | .search > p { 29 | text-align: center; 30 | font-size: 1.2em; 31 | } 32 | 33 | .search > form { 34 | position: static; 35 | } 36 | 37 | .mapLink { 38 | color: #087E8B; 39 | text-decoration:none; 40 | font-size:15px; 41 | font-family:'Open Sans', arial, sans-serif; 42 | } 43 | 44 | .pin { 45 | width: 38px; 46 | height: 38px; 47 | } 48 | .myLocation{ 49 | position: absolute; 50 | bottom: 25px; 51 | right: 0px; 52 | height: 30px; 53 | width: 150px; 54 | } 55 | @media only screen and (min-width: 800px){ 56 | .welcome > img { 57 | width: 20vw; 58 | } 59 | .search{ 60 | width: 35vw; 61 | margin: 0 auto; 62 | } 63 | .search input{ 64 | width: 100%; 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /client/src/assets/icons/favicon-package/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/SVG/ChangeDirectionsIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /client/src/routes/account/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import style from './style'; 3 | import PropTypes from 'prop-types'; 4 | import AccountForm from '../../components/AccountForm'; 5 | import ForgotPasswordForm from '../../components/ForgotPasswordForm'; 6 | import { BASE_ENDPOINTS } from "../../js/server-requests-utils"; 7 | 8 | 9 | 10 | export default class Account extends Component { 11 | 12 | render() { 13 | let renderedForm; 14 | if(this.props.path == '/forgot-password') { 15 | renderedForm = ( 16 | 17 | ) 18 | }else if(this.props.path == '/register'){ 19 | renderedForm = ( 20 | 21 | ) 22 | }else if(this.props.path == '/reset-password'){ 23 | renderedForm = ( 24 | 25 | ) 26 | }else{ 27 | renderedForm = ( 28 | 29 | ) 30 | } 31 | 32 | const styles = { 33 | height: this.props.paneHeight 34 | } 35 | 36 | return ( 37 |
38 | {renderedForm} 39 |
40 | ); 41 | } 42 | } 43 | 44 | Account.propTypes = { 45 | appName: PropTypes.string, 46 | } 47 | -------------------------------------------------------------------------------- /color-scheme.md: -------------------------------------------------------------------------------- 1 | # Color Palettes 2 | 3 | Here's a couple color palettes we could potentially use for the app. Let me know what you all think. I'll add the Adobe Color CC links as well so you have easier access to the values. 4 | 5 | I placed the primary colors in the center. With neutrals on the right and a contrasting color on the left. 6 | 7 | I wanted to use a green or blue since there are connections to adventure and nature with using a map. 8 | 9 | Green Theme Values: 10 | 11 | |             |RGB     |HEX | 12 | |--------------|------------------|-------| 13 | |Primary |rgb(59, 138, 59) |#3B8A3B| 14 | |Primary Light |rgb(112, 156, 96) |#709C60| 15 | |Primary Accent|rgb(255, 250, 204)|#FFFACC| 16 | |Light |rgb(240, 235, 240)|#F0EBF0| 17 | |Dark         |rgb(41, 36, 41) |#292429| 18 | 19 | Blue Theme Values: 20 | 21 | |             |RGB     |HEX | 22 | |--------------|------------------|-------| 23 | |Primary       |rgb(44, 91, 97) |#2C5B61| 24 | |Primary Light |rgb(90, 152, 161) |#5A98A1| 25 | |Primary Accent|rgb(255, 252, 222)|#FFFCDE| 26 | |Light |rgb(240, 235, 240)|#F0EBF0| 27 | |Dark         |rgb(41, 36, 41) |#292429| 28 | 29 |
30 | 31 |
32 | 33 |
34 | 35 |
36 | -------------------------------------------------------------------------------- /docs/color-scheme.md: -------------------------------------------------------------------------------- 1 | # Color Palettes 2 | 3 | Here's a couple color palettes we could potentially use for the app. Let me know what you all think. I'll add the Adobe Color CC links as well so you have easier access to the values. 4 | 5 | I placed the primary colors in the center. With neutrals on the right and a contrasting color on the left. 6 | 7 | I wanted to use a green or blue since there are connections to adventure and nature with using a map. 8 | 9 | Green Theme Values: 10 | 11 | |             |RGB     |HEX | 12 | |--------------|------------------|-------| 13 | |Primary |rgb(59, 138, 59) |#3B8A3B| 14 | |Primary Light |rgb(112, 156, 96) |#709C60| 15 | |Primary Accent|rgb(255, 250, 204)|#FFFACC| 16 | |Light |rgb(240, 235, 240)|#F0EBF0| 17 | |Dark         |rgb(41, 36, 41) |#292429| 18 | 19 | Blue Theme Values: 20 | 21 | |             |RGB     |HEX | 22 | |--------------|------------------|-------| 23 | |Primary       |rgb(44, 91, 97) |#2C5B61| 24 | |Primary Light |rgb(90, 152, 161) |#5A98A1| 25 | |Primary Accent|rgb(255, 252, 222)|#FFFCDE| 26 | |Light |rgb(240, 235, 240)|#F0EBF0| 27 | |Dark         |rgb(41, 36, 41) |#292429| 28 | 29 |
30 | 31 |
32 | 33 |
34 | 35 |
36 | -------------------------------------------------------------------------------- /client/src/routes/profile/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import style from './style'; 3 | 4 | import Logo from '../../components/Logo'; 5 | import SavedPinsCard from '../../components/SavedPinsCard'; 6 | import SearchHistoryCard from '../../components/SearchHistoryCard'; 7 | import { setStateUserOrRedirectToSignIn } from "../../js/validate-account-form"; 8 | 9 | export default class Profile extends Component { 10 | 11 | constructor() { 12 | super(); 13 | this.state = { 14 | user: {}, 15 | isSignedIn: false, 16 | }; 17 | setStateUserOrRedirectToSignIn(this); 18 | } 19 | 20 | render({ success }, { user, time }) { 21 | return ( 22 |
23 | 24 |
{success}
25 |
26 |
27 | User Info 28 | Name: {user.name}
29 | Email: {user.email}
30 |
31 |
32 | 33 | 34 | 39 |
40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /client/src/style/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Open Sans"; 3 | src: url(../assets/font/OpenSans-Regular.ttf); 4 | } 5 | @font-face { 6 | font-family: "Open Sans Bold"; 7 | src: url(../assets/font/OpenSans-Bold.ttf); 8 | } 9 | @font-face { 10 | font-family: "Open Sans Light"; 11 | src: url(../assets/font/OpenSans-Light.ttf); 12 | } 13 | 14 | html, body { 15 | height: 100%; 16 | width: 100%; 17 | padding: 0; 18 | margin: 0; 19 | background: #FAFAFA; 20 | font-family: 'Open Sans Light', arial, sans-serif; 21 | font-weight: 400; 22 | color: #444; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | * { 28 | box-sizing: border-box; 29 | } 30 | 31 | #app { 32 | height: 100%; 33 | background-image: url(../assets/images/background-image.jpg); 34 | background-repeat:no-repeat; 35 | background-size:cover; 36 | } 37 | input{ 38 | border-radius: 10px; 39 | border: 1px solid #95989A; 40 | background-color: #ffffff; 41 | font-size: 16px; 42 | line-height:20px; 43 | padding: 13px; 44 | } 45 | input.markerInput{ 46 | border-radius: 10px; 47 | border: 1px solid #95989A; 48 | background-color: #ffffff; 49 | font-size: 10px; 50 | line-height:10px; 51 | padding: 5px; 52 | display:block; 53 | margin-bottom:4px; 54 | width:100%; 55 | } 56 | .center{ 57 | width:100%; 58 | align-content: center; 59 | text-align:center; 60 | margin-bottom:4px; 61 | display:block; 62 | } 63 | .logo{ 64 | width: 50vw; 65 | } 66 | 67 | @media only screen and (min-width: 800px) 68 | .logo { 69 | width: 20vw; 70 | } 71 | -------------------------------------------------------------------------------- /client/src/components/SearchResults/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import style from './style.css'; 3 | import { makeRequest, BASE_ENDPOINTS } from '../../js/server-requests-utils'; 4 | const OK_STATUS = 'OK' 5 | 6 | export default class UnorderedList extends Component { 7 | constructor(props) { 8 | super(props); 9 | 10 | this.handleClick = this.handleClick.bind(this); 11 | } 12 | 13 | /** 14 | * On search prediction selection , fetch the geolocation details and 15 | * update the map with a marker on that location 16 | * 17 | * @param {*} prediction 18 | */ 19 | handleClick(event, prediction) { 20 | event.preventDefault(); 21 | makeRequest('POST', BASE_ENDPOINTS.geocode, '', { 22 | input: prediction 23 | }).then((response) => { 24 | console.log(response); 25 | if(response.data.status == OK_STATUS) 26 | { 27 | const [searchResult] = response.data.results; 28 | this.props.onClicked(searchResult); 29 | } 30 | else 31 | alert(response.data.status); 32 | }); 33 | } 34 | 35 | render() { 36 | return ( 37 |
    38 | {this.props.predictions.map((prediction, index) => { 39 | return
    this.handleClick(e, prediction)} > 40 |

    {this.props.descSubfields[index].mainText}

    41 |

    {this.props.descSubfields[index].secondaryText}

    42 |
    43 | })} 44 |
45 | ) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Navi: An open source project built for Grow with Google 4 | 5 | 6 | 7 | 8 | ## How to contribute 9 | Thanks for your interest in contributing to Offline Google Maps Navigator! Contributing to open source projects like this one can be a rewarding way to learn, teach, and build experience. 10 | Not only that, contributing is a great way to get involved with social coding. We are excited to see what amazing contributions you will make, as well as how your contributions will benefit others. 11 | Here is how you can [contribute](CONTRIBUTING.md) and here is how you can get [set up](README.md) with the code base 12 | 13 | ## Beginners 14 | This project has a special section for those who are just starting out on their open source journey. Check it out [here](beginners.html) and open issues for any changes 15 | 16 | ## Learning Resources 17 | This is where the team captures lessons learned, best practices and "gotchas" that may be of help to others - check it [out](learning.html) 18 | 19 | ## API and other Documentation 20 | You will find more detailed documentation here including all the APIs used, associated instructions and end points 21 | 22 | ## Meet the team 23 | Here are the [people](https://github.com/TheDevPath/Navi/graphs/contributors) behind this project. 24 | 25 | Special thanks to all the woderful [contributors](CONTRIBUTORS.html). 26 | 27 | ![Logo](https://user-images.githubusercontent.com/35308564/36317506-4407c0aa-130b-11e8-88a2-af1cca16eaba.PNG "Logo") 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /client/src/components/SearchHistoryCard/index.js: -------------------------------------------------------------------------------- 1 | import {h, Component} from 'preact'; 2 | import style from './style'; 3 | import {makeRequest, token} from "../../js/server-requests-utils"; 4 | import { BASE_ENDPOINTS } from "../../js/server-requests-utils"; 5 | 6 | export default class SearchHistoryCard extends Component { 7 | constructor() { 8 | super(); 9 | this.state = { 10 | user: {}, 11 | searchHistory: [], 12 | }; 13 | this.getSearchHistory = this.getSearchHistory.bind(this); 14 | this.displayHistory = this.displayHistory.bind(this); 15 | } 16 | 17 | componentWillMount() { 18 | this.setState({user: this.props.user}); 19 | this.getSearchHistory(); 20 | } 21 | 22 | displayHistory(searchHistory) { 23 | let histories = []; 24 | searchHistory.map( search => { 25 | histories.push(
{search.query}
); 26 | }); 27 | if(histories.length == 0) histories.push(
No saved searches
); 28 | this.setState({histories: histories}); 29 | } 30 | 31 | getSearchHistory() { 32 | makeRequest('GET', BASE_ENDPOINTS.savedHistory) 33 | .then(function (response) { 34 | this.displayHistory(response.data.searchHistory); 35 | }.bind(this)) 36 | .catch(function (error) { 37 | if (error.response === undefined) { 38 | this.setState({searchHistory: error}); 39 | } else { 40 | this.setState({searchHistory: error.response.data}); 41 | } 42 | }.bind(this)); 43 | } 44 | 45 | render({}, {user, histories}) { 46 | 47 | 48 | return ( 49 |
50 | 51 | Search History
52 | 53 | 54 |
{histories}
55 | 56 |
57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/SVG/Get Directions.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /client/src/components/SavedPinsCard/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import style from './style'; 3 | import { makeRequest, token } from "../../js/server-requests-utils"; 4 | import { BASE_ENDPOINTS } from "../../js/server-requests-utils"; 5 | 6 | export default class SavedPinsCard extends Component { 7 | constructor() { 8 | super(); 9 | this.state = { 10 | user: {}, 11 | savedPins: [], 12 | }; 13 | this.getSavedPins = this.getSavedPins.bind(this); 14 | this.displayPins = this.displayPins.bind(this); 15 | this.handleClick = this.handleClick.bind(this); 16 | } 17 | 18 | componentWillMount() { 19 | this.setState({ user: this.props.user }); 20 | this.getSavedPins(); 21 | } 22 | 23 | handleClick(pin) { 24 | this.props.setSelectedPin(pin); 25 | }; 26 | 27 | displayPins(savedPins) { 28 | let pins = []; 29 | savedPins.map(pin => { 30 | pins.push(); 31 | }); 32 | if (pins.length == 0) pins.push(
No saved pins
); 33 | this.setState({ pins: pins }); 34 | } 35 | 36 | getSavedPins() { 37 | makeRequest('GET', BASE_ENDPOINTS.savedPins) 38 | .then(function (response) { 39 | this.displayPins(response.data.savedPins); 40 | }.bind(this)) 41 | .catch(function (error) { 42 | if (error.response === undefined) { 43 | this.setState({ savedPins: error }); 44 | } else { 45 | this.setState({ savedPins: error.response.data }); 46 | } 47 | }.bind(this)); 48 | } 49 | 50 | render({ }, { user, pins }) { 51 | 52 | 53 | return ( 54 |
55 | 56 | Saved Pins
57 | 58 | 59 |
{pins}
60 | 61 |
62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docs/leaflet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Navi: An open source project built for Grow with Google 4 | 5 | 6 | 7 | 8 | ## <img alt="Leaflet" style="width:200px;" src="http://leafletjs.com/docs/images/logo.png"> 9 | ### Features 10 | - Tile and popup fade animation 11 | - Very nice default design for markers, popups and map controls 12 | - Retina resolution support 13 | - Customization Features 14 | - Performance Features 15 | - Map Controls 16 | - Browser Support 17 | - Mobile 18 | - Lightweight 19 | - No external dependencies 20 | 21 | ### Usage 22 | 23 | #### For this project 24 | This project is using leaflet.js in combination with Open Street Maps to create maps and to drop markers on the map 25 | 26 | #### General usage 27 | Map is the central class of the API — it is used to create a map on a page and manipulate it. 28 | 29 | Usage example 30 | 31 | 32 | 33 | // initialize the map on the "map" div with a given center and zoom 34 | 35 | var map = L.map('map', { 36 | 37 | center: [51.505, -0.09], zoom: 13 38 | 39 | }); 40 | 41 | Factory |Description| 42 | ---------------- | ---------------- | 43 | `L.map(<String> id, <Map options> options?)` | Instantiates a map object given the DOM ID of a <div> element and optionally an object literal with Map options.| 44 | `L.map(<HTMLElement> el, <Map options> options?)` | Instantiates a map object given an instance of a <div> HTML element and optionally an object literal with Map options [*(more)*](http://leafletjs.com/reference-1.3.0.html)| 45 | 46 | 47 | ### Resources 48 | - [Leaflet quick start](http://leafletjs.com/examples/quick-start/) 49 | - [Leaflet on Mobile](http://leafletjs.com/examples/mobile/) 50 | - [Markers with custom icons](http://leafletjs.com/examples/custom-icons/) 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # PROJECT SPECIFIC 2 | 3 | # Distribution / Build 4 | build/ 5 | 6 | # GENERAL IGNORES 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories / package management 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Typescript v1 declaration files 46 | typings/ 47 | 48 | # Optional npm cache directory 49 | .npm 50 | 51 | # Optional eslint cache 52 | .eslintcache 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | # Output of 'npm pack' 58 | *.tgz 59 | 60 | # Yarn Integrity file 61 | .yarn-integrity 62 | 63 | # dotenv environment variables file 64 | .env 65 | 66 | # MAC OS X SPECIFIC IGNORES 67 | # General 68 | .DS_Store 69 | .AppleDouble 70 | .LSOverride 71 | 72 | # Icon must end with two \r 73 | Icon 74 | 75 | # Thumbnails 76 | ._* 77 | 78 | # Files that might appear in the root of a volume 79 | .DocumentRevisions-V100 80 | .fseventsd 81 | .Spotlight-V100 82 | .TemporaryItems 83 | .Trashes 84 | .VolumeIcon.icns 85 | .com.apple.timemachine.donotpresent 86 | 87 | # Directories potentially created on remote AFP share 88 | .AppleDB 89 | .AppleDesktop 90 | Network Trash Folder 91 | Temporary Items 92 | .apdisk 93 | 94 | # VSCODE IGNORES 95 | .vscode/* 96 | !.vscode/settings.json 97 | !.vscode/tasks.json 98 | !.vscode/launch.json 99 | !.vscode/extensions.json 100 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "navi-client", 4 | "version": "0.0.0", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "if-env NODE_ENV=production && npm run -s serve || npm run -s dev", 8 | "build": "preact build --no-prerender", 9 | "serve": "preact build --no-prerender && preact serve", 10 | "dev": "preact watch", 11 | "lint": "eslint src", 12 | "test": "jest" 13 | }, 14 | "eslintConfig": { 15 | "extends": "eslint-config-airbnb" 16 | }, 17 | "eslintIgnore": [ 18 | "build/*" 19 | ], 20 | "babel": { 21 | "presets": [ 22 | "env" 23 | ], 24 | "plugins": [ 25 | [ 26 | "transform-react-jsx", 27 | { 28 | "pragma": "h" 29 | } 30 | ], 31 | "transform-decorators-legacy" 32 | ] 33 | }, 34 | "devDependencies": { 35 | "eslint": "^4.9.0", 36 | "eslint-config-airbnb": "^16.1.0", 37 | "eslint-plugin-import": "^2.7.0", 38 | "eslint-plugin-jsx-a11y": "^6.0.2", 39 | "eslint-plugin-react": "^7.4.0", 40 | "eslint-plugin-react-compat": "0.0.3", 41 | "if-env": "^1.0.0", 42 | "ignore-styles": "^5.0.1", 43 | "jest": "^22.4.2", 44 | "jest-css-modules": "^1.1.0", 45 | "preact-cli": "^2.0.1", 46 | "preact-compat-enzyme": "^0.2.5", 47 | "preact-render-spy": "^1.2.2", 48 | "preact-test-utils": "^0.1.3", 49 | "regenerator-runtime": "^0.11.0" 50 | }, 51 | "dependencies": { 52 | "axios": "^0.18.0", 53 | "leaflet": "^1.3.1", 54 | "leaflet-routing-machine": "^3.2.8", 55 | "linkstate": "^1.1.1", 56 | "pouchdb-browser": "^6.4.3", 57 | "preact": "^8.2.6", 58 | "preact-compat": "^3.17.0", 59 | "preact-render-to-string": "^3.7.0", 60 | "preact-router": "^2.5.7", 61 | "prop-types": "^15.6.0", 62 | "universal-cookie": "^2.1.2" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # GENERAL IGNORES 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (https://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories / package management 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # MAC OS X SPECIFIC IGNORES 62 | # General 63 | .DS_Store 64 | .AppleDouble 65 | .LSOverride 66 | 67 | # Icon must end with two \r 68 | Icon 69 | 70 | # Thumbnails 71 | ._* 72 | 73 | # Files that might appear in the root of a volume 74 | .DocumentRevisions-V100 75 | .fseventsd 76 | .Spotlight-V100 77 | .TemporaryItems 78 | .Trashes 79 | .VolumeIcon.icns 80 | .com.apple.timemachine.donotpresent 81 | 82 | # Directories potentially created on remote AFP share 83 | .AppleDB 84 | .AppleDesktop 85 | Network Trash Folder 86 | Temporary Items 87 | .apdisk 88 | 89 | # VSCODE IGNORES 90 | .vscode/ 91 | .vscode/* 92 | !.vscode/settings.json 93 | !.vscode/tasks.json 94 | !.vscode/launch.json 95 | !.vscode/extensions.json 96 | 97 | # MongoDB DB path 98 | data/* 99 | 100 | # secrets.json ***SHOULD NEVER BE PUSHED INTO GITHUB*** 101 | secrets.json 102 | -------------------------------------------------------------------------------- /client/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "navi", 3 | "name": "Navi - a navigator app for the Udacity Grow with Google project", 4 | "start_url": "/?utm_source=homescreen", 5 | "display": "standalone", 6 | "orientation": "portrait", 7 | "background_color": "#2196F3", 8 | "theme_color": "#2196F3", 9 | "permissions": ["geolocation"], 10 | "icons": [{ 11 | "src": "client/src/assets/icons/android-chrome-192x192.png", 12 | "sizes": "128x128", 13 | "type": "image/png" 14 | }, { 15 | "src": "client/src/assets/icons/android-chrome-512x512.png", 16 | "sizes": "144x144", 17 | "type": "image/png" 18 | }, { 19 | "src": "client/src/assets/icons/apple-touch-icon.png", 20 | "sizes": "152x152", 21 | "type": "image/png" 22 | }, { 23 | "src": "client/src/assets/icons/favicon-16x16.png", 24 | "sizes": "128X128", 25 | "type": "image/png" 26 | }, { 27 | "src": "client/src/assets/icons/favicon-16x16.png", 28 | "sizes": "144X144", 29 | "type": "image/png" 30 | }, { 31 | "src": "client/src/assets/icons/favicon-16x16.png", 32 | "sizes": "16X16", 33 | "type": "image/png" 34 | }, { 35 | "src": "client/src/assets/icons/favicon-32x32.png", 36 | "sizes": "256x256", 37 | "type": "image/png" 38 | },{ 39 | "src": "client/src/assets/icons/mstile-150x150.png", 40 | "type": "image/png", 41 | "sizes": "32X32" 42 | }, 43 | { 44 | "src": "client/src/assets/icons/favicon-16x16.png", 45 | "sizes": "384X384", 46 | "type": "image/png" 47 | }, 48 | { 49 | "src": "client/src/assets/icons/fmstile-150x150.png", 50 | "sizes": "48X48", 51 | "type": "image/png" 52 | }, { 53 | "src": "client/src/assets/icons/favicon-150x150.png", 54 | "sizes": "512X512", 55 | "type": "image/png" 56 | }, { 57 | "src": "client/src/assets/icons/favicon-150x150.png", 58 | "sizes": "96X96", 59 | "type": "image/png" 60 | }, { 61 | "src": "client/src/assets/icons/mstile-150x150.png", 62 | "sizes": "150X150", 63 | "type": "image/png" 64 | } 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /tests/seed/seed.js: -------------------------------------------------------------------------------- 1 | const { ObjectID } = require('mongodb'); 2 | const jwt = require('jsonwebtoken'); 3 | const { JWT_KEY } = require('../../config'); 4 | const server = require('../../server'); 5 | 6 | const User = require('../../models/users'); 7 | const SavedPins = require('../../models/saved-pins'); 8 | const SearchHistory = require('../../models/search-history'); 9 | 10 | const userOneId = new ObjectID(); 11 | const userTwoId = new ObjectID(); 12 | const users = [{ 13 | _id: userOneId, 14 | name: 'mallek', 15 | email: 'mallek@example.com', 16 | password: 'userOnePass1!', 17 | tokens: [{ 18 | access: 'auth', 19 | token: jwt.sign({ id: userOneId }, JWT_KEY).toString(), 20 | }], 21 | }, { 22 | _id: userTwoId, 23 | name: 'user', 24 | email: 'user@example.com', 25 | password: 'userTwoPass2!', 26 | }, { 27 | // /This user is for creating new test users only 28 | name: 'Taco Test', 29 | email: 'test@testing.com', 30 | password: 'passcode!1', 31 | }, 32 | ]; 33 | 34 | const pins = [{ 35 | _id: new ObjectID(), 36 | lat: 1, 37 | lng: 2, 38 | place_id: 'testing pin1', 39 | user: userOneId, 40 | }, { 41 | _id: new ObjectID(), 42 | lat: 20, 43 | lng: 30, 44 | place_id: 'testing again', 45 | user: userTwoId, 46 | }]; 47 | 48 | const populatePins = (done) => { 49 | SavedPins.remove({}).then(() => SavedPins.insertMany(pins)).then(() => done()); 50 | }; 51 | 52 | const populateUsers = function (done) { 53 | User.remove({}).then(() => { 54 | const userOne = new User(users[0]).save(); 55 | const userTwo = new User(users[1]).save(); 56 | 57 | return Promise.all([userOne, userTwo]); 58 | }).then(() => done()); 59 | }; 60 | 61 | const deleteTestUser = function (done) { 62 | User.remove({ 63 | email: 'test@testing.com', 64 | }).then(() => done()); 65 | }; 66 | 67 | const stopServer = (done) => { 68 | server.close(); 69 | done(); 70 | }; 71 | 72 | const userObjectWithToken = user => { 73 | const _id = new ObjectID(); 74 | const tokens = [{ 75 | access: 'auth', 76 | token: jwt.sign({ id: _id }, JWT_KEY).toString(), 77 | }]; 78 | return Object.assign(user, { tokens: tokens, _id: _id }); 79 | }; 80 | 81 | module.exports = { 82 | pins, 83 | populatePins, 84 | users, 85 | populateUsers, 86 | deleteTestUser, 87 | stopServer, 88 | userObjectWithToken 89 | }; 90 | -------------------------------------------------------------------------------- /routes/search.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const router = express.Router(); 4 | 5 | // require controller modules 6 | const savedDirectionsController = require('../controllers/saved-directions-controller'); 7 | const savedPinsController = require('../controllers/saved-pins-controller'); 8 | const searchHistoryController = require('../controllers/search-history-controller'); 9 | const { verifyToken } = require('../controllers/utils-controller'); 10 | const { autocomplete, placeDetails, textSearch } = require('../controllers/google-api-controller'); 11 | 12 | /** 13 | * @description Handle requests to search main end point 14 | * 15 | * @api {GET} /search 16 | * @return {success: false, error: err} Not a valid end point 17 | */ 18 | router.get('/', (req, res) => { 19 | res.status(404).send({ 20 | success: false, 21 | error: 'Not a valid end point!', 22 | }); 23 | }); 24 | 25 | /** 26 | * Saved pins end points 27 | */ 28 | router.get('/savedpins', verifyToken, savedPinsController.getSavedPins); 29 | router.get('/savedpins/:id', verifyToken, savedPinsController.getSavedPinsById); 30 | router.post('/savedpins', verifyToken, savedPinsController.postSavedPins); 31 | router.delete('/savedpins', verifyToken, savedPinsController.deleteSavedPins); 32 | router.delete('/savedpins/:id', verifyToken, savedPinsController.deleteSavedPinsById); 33 | 34 | /** 35 | * Search and Places query endpoints 36 | */ 37 | router.get('/places/:id', placeDetails); 38 | router.post('/autocomplete', autocomplete); 39 | router.get('/textsearch', textSearch); 40 | router.post('/textsearch', textSearch); 41 | 42 | /** 43 | * Saved search history end points 44 | */ 45 | router.get('/history', verifyToken, searchHistoryController.getSearchHistory); 46 | router.get('/history/recent/:num', verifyToken, searchHistoryController.getRecent); 47 | router.post('/history/:query', verifyToken, searchHistoryController.saveQuery); 48 | router.delete('/history', verifyToken, searchHistoryController.deleteSearchHistory); 49 | 50 | /** 51 | * Saved directions end points 52 | */ 53 | router.get('/directions', verifyToken, savedDirectionsController.getSavedDirections); 54 | router.get('/directions/recent/:num', verifyToken, savedDirectionsController.getRecentDirections); 55 | router.post('/directions', verifyToken, savedDirectionsController.saveDirections); 56 | router.delete('/directions', verifyToken, savedDirectionsController.deleteSavedDirections); 57 | 58 | module.exports = router; 59 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | // Package Dependencies 2 | const bodyParser = require('body-parser'); 3 | const express = require('express'); 4 | const helmet = require('helmet'); 5 | const mongoose = require('mongoose'); 6 | const morgan = require('morgan'); 7 | 8 | // Local Dependencies 9 | const { DB_URL, NODE_ENV: ENV } = require('./config'); 10 | 11 | // Instantiate Express Server 12 | const app = express(); 13 | 14 | /** 15 | * MONGODB CONNECTION 16 | */ 17 | 18 | // Setup MongoDB connection using the global promise library and then get connection 19 | mongoose.connect(DB_URL, { promiseLibrary: global.Promise }, (error) => { 20 | if (error) { 21 | console.log(`MongoDB connection error: ${error}`); 22 | 23 | // should consider alternative to exiting the app due to db conn issue 24 | process.exit(1); 25 | } 26 | }); 27 | 28 | const db = mongoose.connection; 29 | 30 | /** 31 | * MIDDLEWARE 32 | */ 33 | 34 | // Set security-related HTTP headers (https://expressjs.com/en/advanced/best-practice-security.html#use-helmet) 35 | app.use(helmet()); 36 | 37 | // Allow access on headers and avoid CORS issues 38 | app.use((req, res, next) => { 39 | res.header('Access-Control-Allow-Origin', '*'); 40 | res.header( 41 | 'Access-Control-Allow-Headers', 42 | 'Origin, X-Requested-With, Content-Type, Access, Authorization, x-access-token', 43 | ); 44 | 45 | if (req.method === 'OPTIONS') { 46 | res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, PATCH, DELETE'); 47 | 48 | res.header( 49 | 'Access-Control-Allow-Headers', 50 | 'Origin, X-Requested-With, Content-Type, Access, Authorization, x-access-token', 51 | ); 52 | 53 | return res.status(200).json({}); 54 | } 55 | 56 | next(); 57 | }); 58 | 59 | // Parse incoming requests 60 | app.use(bodyParser.json()); 61 | app.use(bodyParser.urlencoded({ extended: false })); 62 | 63 | // Log every request to the console 64 | app.use(morgan('dev')); 65 | 66 | /** 67 | * ROUTES 68 | */ 69 | 70 | // API Routes 71 | // app.use('/', require('./routes/index')); 72 | app.use('/map', require('./routes/map')); 73 | app.use('/search', require('./routes/search')); 74 | app.use('/users', require('./routes/users')); 75 | 76 | // TODO: Create additional routes as necessary 77 | 78 | // Serve static assets and index.html in production 79 | if (ENV === 'production') { 80 | // Serve static assets 81 | app.use(express.static('client/build')); 82 | 83 | // Serve index.html file if no other routes were matched 84 | const { resolve } = require('path'); 85 | 86 | app.get('**', (req, res) => { 87 | res.sendFile(resolve(__dirname, 'client', 'build', 'index.html')); 88 | }); 89 | } 90 | 91 | module.exports = app; 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "navi", 3 | "version": "0.0.1", 4 | "description": "Open source project for Grow with Google Udacity Scholarship Challenge - Navigation app using offline first strategy and google maps api", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "dev": "concurrently \"npm run server\" \"npm run client\"", 9 | "client": "npm run start --prefix client", 10 | "server": "nodemon server.js", 11 | "server:inspect": "nodemon --inspect server.js", 12 | "test": "concurrently \"npm run test-server\" \"npm run test-client\"", 13 | "test-server": "cross-env NODE_ENV=test mocha tests --timeout 10000 -r chai/register-expect", 14 | "test-watch": "nodemon --exec 'npm test'", 15 | "test-client": "npm test --prefix client", 16 | "heroku-postbuild": "cd client/ && npm install && npm install --only=dev --no-shrinkwrap && npm run build" 17 | }, 18 | "engines": { 19 | "node": ">=8.9.0" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/TheDevPath/Navi.git" 24 | }, 25 | "contributors": [ 26 | "Bryan Sharpley (https://github.com/motosharpley)", 27 | "Christopher Gates (https://github.com/tophergates)", 28 | "Raegan Millhollin (https://github.com/desdemonhu)", 29 | "Peter Matthews (https://github.com/pjdmatts)", 30 | "John Kwening (https://github.com/jkwening)", 31 | "Vasanth (https://github.com/Vasanthkesavan)", 32 | "Travis Haley (https://github.com/Mallek", 33 | "Eddie Ford (https://github.com/seckboy)", 34 | "Chris Young (https://github.com/someyoungideas)" 35 | ], 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/TheDevPath/googleMaps-offline-navigator/issues" 39 | }, 40 | "devDependencies": { 41 | "babel-eslint": "^8.2.1", 42 | "chai": "^4.1.2", 43 | "chai-http": "^3.0.0", 44 | "concurrently": "^3.5.1", 45 | "cross-env": "^5.1.3", 46 | "eslint": "^4.16.0", 47 | "eslint-config-airbnb-base": "^12.1.0", 48 | "eslint-plugin-import": "^2.8.0", 49 | "mocha": "^5.0.0", 50 | "nodemon": "^1.14.11", 51 | "superagent": "^3.8.2", 52 | "supertest": "^3.0.0" 53 | }, 54 | "dependencies": { 55 | "@google/maps": "^0.4.5", 56 | "bcryptjs": "^2.4.3", 57 | "body-parser": "^1.18.2", 58 | "express": "^4.16.2", 59 | "helmet": "^3.9.0", 60 | "jsonwebtoken": "^8.1.1", 61 | "mongodb": "^3.0.2", 62 | "mongoose": "^5.0.1", 63 | "morgan": "^1.9.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /client/src/routes/home/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import style from './style'; 3 | import { route } from 'preact-router'; 4 | import Search from '../../components/Search'; 5 | import SearchResults from '../../components/SearchResults'; 6 | 7 | export default class Home extends Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | busyMessage: 'Loading your location...', 13 | userPosition: { 14 | lat: '', 15 | lng: '' 16 | }, 17 | } 18 | 19 | this.routeToMap = this.routeToMap.bind(this); 20 | this.resetSelectedPin = this.resetSelectedPin.bind(this); 21 | } 22 | 23 | componentDidMount() { 24 | if (this.state.userPosition.lat && this.state.userPosition.lng) { 25 | this._setBusyMessage(''); 26 | return; 27 | } 28 | 29 | if ('geolocation' in navigator === false) { 30 | this._setBusyMessage('Could not find your location'); 31 | return; 32 | } 33 | 34 | const self = this; 35 | navigator.geolocation.getCurrentPosition( 36 | (position) => { 37 | this.setState({ 38 | busyMessage: '', 39 | userPosition: { 40 | lat: position.coords.latitude, 41 | lng: position.coords.longitude 42 | } 43 | }); 44 | 45 | console.log('\tuser position found: ', this.state.userPosition); 46 | }, 47 | (err) = { 48 | if (err.code && err.code === 1) 49 | self._setBusyMessage('Cound not find your location'); 50 | else 51 | self._setBusyMessage('Error occurred while getting your location'); 52 | }, 53 | { 54 | enableHighAccuracy: false, 55 | timeout: 60000, 56 | maximumAge: Infinity 57 | }); 58 | } 59 | 60 | routeToMap() { 61 | this.resetSelectedPin(); 62 | route('/maps', true); 63 | } 64 | 65 | resetSelectedPin() { 66 | this.props.selectedPin(null); 67 | } 68 | 69 | _setBusyMessage(message) { 70 | this.setState({ busyMessage: message }); 71 | } 72 | 73 | render() { 74 | return ( 75 |
76 |
77 |

WELCOME TO

78 | 79 |
80 |
81 |

Where can we take you today?

82 | 84 | 85 | 86 |

{this.state.busyMessage || ''}

87 |
88 |
89 | where am I? 90 | 92 |
93 |
94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /client/src/js/server-requests-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Library for handling frontend requests to server api endpoints 3 | */ 4 | 5 | // node_modules imports 6 | import axios from 'axios'; 7 | import Cookies from "universal-cookie"; 8 | 9 | // app module imports 10 | import { API_SERVER } from '../../config'; 11 | 12 | // module constants 13 | const TOKEN_COOKIE = 'token'; 14 | export const BASE_ENDPOINTS = { // Key-value pairs for existing base server endpoints 15 | savedPins: '/search/savedpins', 16 | savedDirections: '/search/directions', 17 | savedHistory: '/search/history', 18 | autocomplete: '/search/autocomplete', 19 | places: '/search/places', 20 | user: '/users/user', 21 | userLogin: '/users/login', 22 | userLogout: '/users/logout', 23 | userRegister: '/users/register', 24 | textsearch: '/search/textsearch', 25 | userReset: '/users/reset-password', 26 | userUpdate: '/users/update', 27 | geocode: '/map/geocode', 28 | } 29 | 30 | export const token = { 31 | setCookie: val => { 32 | const cookies = new Cookies(); 33 | cookies.set(TOKEN_COOKIE, val, {path: '/'}); 34 | }, 35 | 36 | deleteCookie: () => { 37 | const cookies = new Cookies(); 38 | cookies.remove(TOKEN_COOKIE); 39 | }, 40 | 41 | getCookie: () => { 42 | const cookies = new Cookies(); 43 | return cookies.get(TOKEN_COOKIE); 44 | } 45 | 46 | } 47 | 48 | /* makeRequest(...) 49 | 50 | __include__ import {makeRequest} from "../../js/server-requests-utils" 51 | __exmample__ makeRequest('GET','savedPins').then(res => {return res.data}) 52 | 53 | __output__ Promise that returns an object with the following keys: 54 | * config 55 | * data: response body 56 | * headers 57 | * request 58 | * status 59 | * statusText 60 | 61 | * See: https://www.npmjs.com/package/axios#response-schema for more on the output 62 | * 63 | * Configuration of Axios for making server requests 64 | * See 'Creating an instance' and 'Request Config' sections for more information: 65 | * https://www.npmjs.com/package/axios 66 | */ 67 | 68 | export const makeRequest = (method='GET', baseEndPoint, endPointAddon='', bodyData={}, params={}, headers={}) => { 69 | 70 | const validMethod = ['GET', 'POST', 'DELETE', 'PATCH','PUT']; //determined by axios 71 | 72 | //if it's not a valid method, return rejected promise 73 | if (!validMethod.includes(method)) { 74 | return new Promise(function (res,rej) { 75 | rej(TypeError(`Invalid request method: ${method}`)); 76 | }) 77 | } 78 | 79 | const url = ((BASE_ENDPOINTS[baseEndPoint] || baseEndPoint) + endPointAddon).trim(); 80 | headers['x-access-token'] = token.getCookie(); 81 | const config = {method, 82 | url, 83 | params, 84 | headers, 85 | baseURL: API_SERVER, 86 | data: bodyData 87 | } 88 | 89 | console.log('makeRequest().config: ', config); 90 | 91 | return axios.request(config); 92 | } 93 | -------------------------------------------------------------------------------- /client/src/components/app.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import { Router } from 'preact-router'; 3 | import Match from 'preact-router/match'; 4 | 5 | // import components 6 | import Nav from './Nav'; 7 | import Logo from './Logo'; 8 | 9 | // import routes 10 | import Home from '../routes/home'; 11 | import Profile from '../routes/profile'; 12 | import Directions from '../routes/directions'; 13 | import Maps from '../routes/maps'; 14 | import Account from '../routes/account'; 15 | import SignOut from '../routes/signout'; 16 | import Settings from '../routes/settings'; 17 | 18 | // Available screen real state after factoring space for navbar 19 | const AVAIL_PANE_HEIGHT = screen.availHeight * 0.93; 20 | 21 | export default class App extends Component { 22 | constructor() { 23 | super(); 24 | this.state = { 25 | navbarHeight: screen.availHeight - AVAIL_PANE_HEIGHT, 26 | userPosition: null, 27 | searchResult: null, 28 | selectedPin: null, 29 | }; 30 | 31 | this.updateSearchResult = this.updateSearchResult.bind(this); 32 | this.setUserPosition = this.setUserPosition.bind(this); 33 | this.setSelectedPin = this.setSelectedPin.bind(this); 34 | } 35 | 36 | /** Gets fired when the route changes. 37 | * @param {Object} event "change" event from [preact-router](http://git.io/preact-router) 38 | * @param {string} event.url The newly routed URL 39 | */ 40 | handleRoute = e => { 41 | this.currentUrl = e.url; 42 | }; 43 | 44 | updateSearchResult(placeDetail) { 45 | this.setState({ searchResult: placeDetail }); 46 | } 47 | 48 | setUserPosition(userPosition) { 49 | this.setState({ userPosition, }); 50 | } 51 | 52 | setSelectedPin(selectedPin) { 53 | this.setState({ selectedPin }); 54 | } 55 | 56 | render() { 57 | return ( 58 |
59 |
77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /controllers/utils-controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility controller module. 3 | * @module controller/utils-controller 4 | */ 5 | 6 | const jwt = require('jsonwebtoken'); 7 | const { JWT_KEY } = require('../config'); 8 | 9 | /** 10 | * @description Authorization middleware for verifying user access rights 11 | * 12 | * @apiError 401 {request error} Unauthorized - no token provided. 13 | * @apiError 403 {request error} Unable to authenticate user. 14 | * 15 | * @param {string} req.headers['x-access-token'] - request header key used for 16 | * tracking token 17 | */ 18 | exports.verifyToken = (req, res, next) => { 19 | const token = req.headers['x-access-token']; 20 | 21 | if (!token) { 22 | return res.status(401).send({ 23 | auth: false, 24 | message: 'No token provided.', 25 | }); 26 | } 27 | 28 | jwt.verify(token, JWT_KEY, (err, decoded) => { 29 | if (err) { 30 | return res.status(403).send({ 31 | auth: false, 32 | message: 'Failed to authenticate token.', 33 | }); 34 | } 35 | 36 | // if good, save to request for next route 37 | req.userId = decoded.id; 38 | req.token = token; 39 | next(); 40 | }); 41 | }; 42 | 43 | /** 44 | * @description Utility function for generating query string from an object with 45 | * key-value pairs for params needed for http request using native node https. 46 | * 47 | * @param {Object} paramsObject: key-value pairs for params needed for request 48 | * to be parsed as query string 49 | */ 50 | exports.convertToQueryString = (paramsObject) => { 51 | const str = []; 52 | const keys = Object.keys(paramsObject); 53 | keys.forEach((element) => { 54 | str.push(`${encodeURIComponent(element)}=${encodeURIComponent(paramsObject[element])}`); 55 | }); 56 | return str.join('&'); 57 | }; 58 | 59 | /** 60 | * @description Utility function for processing google places autocomplete 61 | * results. 62 | * 63 | * @param {Object} queryResult: JSON response containing two root elememts: 64 | * - status: contains metadata on the request along with status codes 65 | * - predictions: an array of query predictions 66 | * 67 | * @returns {[Ojbect]} An array of suggestions as object with two root elements: 68 | * - prediction: the predicted query 69 | * - placeID: if prediction is a place else '' 70 | */ 71 | exports.processAutocomplete = (queryResult) => { 72 | const descriptions = []; 73 | const placeIds = []; 74 | const descSubfields = []; 75 | 76 | queryResult.predictions.forEach((result) => { 77 | const { description } = result; 78 | const placeId = result.place_id || ''; 79 | const subfields = { 80 | mainText: result.structured_formatting.main_text, 81 | secondaryText: result.structured_formatting.secondary_text, 82 | }; 83 | 84 | descriptions.push(description); 85 | placeIds.push(placeId); 86 | descSubfields.push(subfields); 87 | }); 88 | return { descriptions, placeIds, descSubfields }; 89 | }; 90 | -------------------------------------------------------------------------------- /client/src/sw.js: -------------------------------------------------------------------------------- 1 | const CACHE_VERSION = 1; 2 | 3 | const CACHE_NAME = `mapE_v${CACHE_VERSION}`; 4 | const urlsToCache = [ 5 | '/', 6 | '/bundle.js', 7 | '/index.js', 8 | '/sw.js', 9 | '/style/index.css', 10 | '/assets/icons/leaflet/marker-icon.png', 11 | '/assets/icons/leaflet/marker-shadow.png', 12 | ]; 13 | 14 | self.addEventListener('install', function(event) { 15 | // Perform install steps 16 | event.waitUntil( 17 | caches.open(CACHE_NAME) 18 | .then(function(cache) { 19 | console.log('Opened cache'); 20 | return cache.addAll(urlsToCache); 21 | }).catch(function(err) { 22 | console.log('Cache install failed: ', err); 23 | }) 24 | ); 25 | }); 26 | 27 | self.addEventListener('activate', event => { 28 | // delete any caches that aren't CACHE_NAME 29 | // which will get rid of previous caches 30 | event.waitUntil( 31 | caches.keys().then(keys => Promise.all( 32 | keys.map(key => { 33 | if (key !== CACHE_NAME) { 34 | return caches.delete(key); 35 | } 36 | }) 37 | )).then(() => { 38 | console.log(`${CACHE_NAME} is now ready to handle fetches!`); 39 | }) 40 | ); 41 | }); 42 | 43 | self.addEventListener('fetch', function(event) { 44 | const re = new RegExp('signin|register'); 45 | if (event.request.url.match(re)) { 46 | console.log("DONOTCACHE:" + event.request.url); 47 | return event.respondWith(function () { 48 | return fetch(event.request); 49 | }()); 50 | } 51 | event.respondWith( 52 | caches.match(event.request) 53 | .then(function(response) { 54 | // Cache hit - return response 55 | if (response) { 56 | return response; 57 | } 58 | 59 | // IMPORTANT: Clone the request. A request is a stream and 60 | // can only be consumed once. Since we are consuming this 61 | // once by cache and once by the browser for fetch, we need 62 | // to clone the response. 63 | const fetchRequest = event.request.clone(); 64 | 65 | return fetch(fetchRequest).then( 66 | function(response) { 67 | // Check if we received a valid response 68 | if(!response || response.status !== 200 || response.type !== 'basic') { 69 | return response; 70 | } 71 | 72 | // IMPORTANT: Clone the response. A response is a stream 73 | // and because we want the browser to consume the response 74 | // as well as the cache consuming the response, we need 75 | // to clone it so we have two streams. 76 | const responseToCache = response.clone(); 77 | 78 | caches.open(CACHE_NAME) 79 | .then(function(cache) { 80 | cache.put(event.request, responseToCache); 81 | }); 82 | 83 | return response; 84 | } 85 | ); 86 | }) 87 | ); 88 | }); 89 | -------------------------------------------------------------------------------- /client/src/components/AccountForm/style.css: -------------------------------------------------------------------------------- 1 | .inherit { 2 | display: inherit; 3 | height: inherit; 4 | width: inherit; 5 | } 6 | 7 | .display { 8 | 9 | margin: 10vh auto 0; 10 | } 11 | 12 | .logo { 13 | display: inherit; 14 | margin: auto; 15 | padding: 2vh; 16 | width: 50vw; 17 | } 18 | 19 | .form { 20 | display: grid; 21 | padding: 0 5vh; 22 | align-content: flex-start; 23 | } 24 | 25 | .form > p { 26 | margin: 1vh; 27 | font-size: 1.2em; 28 | } 29 | 30 | 31 | 32 | .formChildReset { 33 | display: block; 34 | margin: 0.7vh auto; 35 | width: 100%; 36 | padding: 3vw; 37 | border-color: lightslategray; 38 | border-radius: 8px; 39 | font-size: 1.2em; 40 | } 41 | 42 | .strike { 43 | display: block; 44 | text-align: center; 45 | overflow: hidden; 46 | white-space: nowrap; 47 | } 48 | 49 | .strike > span { 50 | position: relative; 51 | display: inline-block; 52 | color: #087E8B; 53 | font-weight:bold; 54 | text-transform:lowercase; 55 | font-family: 'Open Sans Bold'; 56 | } 57 | 58 | .strike > span:before, 59 | .strike > span:after { 60 | content: ""; 61 | position: absolute; 62 | top: 50%; 63 | width: 9999px; 64 | height: 1px; 65 | background: black; 66 | } 67 | 68 | .strike > span:before { 69 | right: 100%; 70 | margin-right: 15px; 71 | } 72 | 73 | .strike > span:after { 74 | left: 100%; 75 | margin-left: 15px; 76 | } 77 | 78 | .link2 { 79 | display: block; 80 | position: fixed; 81 | right: 5vw; 82 | bottom: 1vh; 83 | font-size: 1.1em; 84 | color: blue; 85 | } 86 | .link2 a, a{ 87 | color: #087E8B; 88 | text-decoration: none; 89 | font-size: 15px; 90 | font-family: 'Open Sans', arial, sans-serif; 91 | font-weight:bold; 92 | } 93 | button { 94 | background-color: #28C0D1; 95 | color: #ffffff; 96 | text-transform:uppercase; 97 | font-weight:bold; 98 | border-radius: 10px; 99 | font-size: 16px; 100 | line-height: 20px; 101 | padding: 13px; 102 | width: 100%; 103 | border:0; 104 | margin:20px 0 40px; 105 | } 106 | button:hover{ 107 | background: #28c0d1; 108 | background: linear-gradient(135deg, #28c0d1 0%,#10416c 100%); 109 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#28c0d1', endColorstr='#10416c',GradientType=1 ); 110 | } 111 | 112 | .regBtn { 113 | background-color: #10416C; 114 | } 115 | input { 116 | border-radius: 10px; 117 | border: 1px solid #95989A; 118 | background-color: #ffffff; 119 | font-size: 16px; 120 | line-height: 20px; 121 | padding: 13px; 122 | margin: 10px 0; 123 | } 124 | @media only screen and (min-width: 800px) { 125 | 126 | .logo{ 127 | width: 40vw; 128 | } 129 | .display { 130 | width: 50vw; 131 | 132 | } 133 | } 134 | 135 | @media only screen and (min-width: 1200px) { 136 | 137 | .logo{ 138 | width: 20vw; 139 | } 140 | .display { 141 | width: 35vw; 142 | 143 | } 144 | } -------------------------------------------------------------------------------- /client/src/components/Search/index.js: -------------------------------------------------------------------------------- 1 | import { h , Component, cloneElement } from 'preact'; 2 | import style from './style'; 3 | import { route } from 'preact-router'; 4 | import { makeRequest, BASE_ENDPOINTS } from '../../js/server-requests-utils'; 5 | const OK_STATUS = 'OK'; 6 | 7 | export default class Search extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | value: '', 12 | predictions: [], 13 | placeIDs: [], 14 | descSubfields: [], 15 | marker: null 16 | }; 17 | 18 | this.handleChange = this.handleChange.bind(this); 19 | this.handleSubmit = this.handleSubmit.bind(this); 20 | this.handleSelectedPlace = this.handleSelectedPlace.bind(this); 21 | } 22 | 23 | handleChange(event) { 24 | this.setState({value: event.target.value}); 25 | // process autocomplete request and update list 26 | makeRequest('POST', BASE_ENDPOINTS.autocomplete, '', { 27 | input: this.state.value, 28 | lat: this.props.position.lat, 29 | lng: this.props.position.lng, 30 | }).then((response) => { 31 | const status = response.data.status; 32 | const predictions = response.data.predictions; 33 | const placeIds = response.data.placeIds; 34 | const descSubfields = response.data.descSubfields; 35 | 36 | this.setState({ 37 | predictions, 38 | placeIds, 39 | descSubfields, 40 | }); 41 | }); 42 | } 43 | 44 | handleSubmit(event) { 45 | event.preventDefault(); 46 | makeRequest('POST', BASE_ENDPOINTS.textsearch, '', { 47 | input: this.state.value, 48 | lat: this.props.position.lat, 49 | lng: this.props.position.lng, 50 | }).then((response) => { 51 | if(response.data.status == OK_STATUS) 52 | { 53 | const [searchResult] = response.data.results; 54 | this.handleSelectedPlace(searchResult); 55 | } 56 | else 57 | alert(response.data.status); 58 | }); 59 | } 60 | 61 | /** 62 | * On search input query completion, add a marker to the map at the search result. 63 | * 64 | * @param {*} placeDetail 65 | */ 66 | handleSelectedPlace(placeDetail) { 67 | if (this.props.routeUrl === '/maps') { 68 | this.props.addMarker(placeDetail.geometry.location, placeDetail.place_id); 69 | 70 | //TO DO: Customize the marker popup 71 | // this.state.marker.bindPopup(`${placeDetail.name || ''} ${placeDetail.formatted_address}`) 72 | } else { 73 | this.props.updateSearchResult(placeDetail); 74 | route('/maps', true); 75 | } 76 | } 77 | 78 | render() { 79 | // pass props to children components 80 | const childWithProps = this.props.children.map((child) => { 81 | return cloneElement(child, { 82 | predictions: this.state.predictions, 83 | descSubfields: this.state.descSubfields, 84 | onClicked: this.handleSelectedPlace 85 | }); 86 | }); 87 | 88 | return ( 89 |
90 | 92 | {childWithProps} 93 |
94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at motosharpley@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /controllers/search-history-controller.js: -------------------------------------------------------------------------------- 1 | const SearchHistory = require('../models/search-history'); 2 | 3 | /** 4 | * @description Handles request for user search history 5 | * 6 | * @api {GET} /search/history 7 | * @apiSuccess 200 {SearchHistory} The collection of saved search queries. 8 | * @apiError 500 {server error} Problem finding all saved search queries. 9 | */ 10 | exports.getSearchHistory = (appReq, appRes) => { 11 | SearchHistory.find({ user: appReq.userId }).then((searchHistory) => { 12 | appRes.status(200).send({ searchHistory }); 13 | }, (e) => { 14 | appRes.status(500).send(e); 15 | }); 16 | }; 17 | 18 | /** 19 | * @description Handle request for getting a specified number of recently 20 | * saved queries 21 | * 22 | * @api {GET} /search/history/recent/:num 23 | * @apiSuccess 200 {searchHistory} The requested recent num search queries. 24 | * @apiError 500 {server error} Server error 25 | * @apiError 404 {request error} Invalid req.params.num 26 | * 27 | * @param {String} appReq.params.num - Number of most recent saved queries to return. 28 | */ 29 | exports.getRecent = (appReq, appRes) => { 30 | const num = parseInt(appReq.params.num); 31 | if (isNaN(num)) { 32 | return appRes.status(404).send({ 33 | error: 'Invalid params passed.', 34 | param: appReq.params.num, 35 | parsedInt: num 36 | }); 37 | } 38 | 39 | SearchHistory.find({ user: appReq.userId }) 40 | .sort({save_date: 'desc'}) 41 | .then((result) => { 42 | const searchHistory = result.slice(0, num); 43 | appRes.send({ searchHistory }); 44 | }, (err) => { 45 | appRes.status(500).send(err); 46 | }); 47 | }; 48 | 49 | /** 50 | * @description Handles request to save a search query if not already in db 51 | * 52 | * @api {POST} /search/history/:query 53 | * @apiSuccess 200 {searchHistory} The document representing the saved query. 54 | * @apiError 500 {server error} Problem saving the requested pin. 55 | * 56 | * @param {string} appReq.params.query - the string query to save 57 | */ 58 | exports.saveQuery = (appReq, appRes) => { 59 | const saveQuery = { 60 | query: appReq.params.query, 61 | user: appReq.userId 62 | } 63 | 64 | const update = { 65 | lastModified: true, 66 | $currentDate : { 67 | save_date: {$type: 'date'}, 68 | }, 69 | } 70 | 71 | const options = { 72 | new: true, // return modified doc instead of original 73 | upsert: true, // create if doesn't exist 74 | 75 | } 76 | 77 | SearchHistory.findOneAndUpdate(saveQuery, update, options, 78 | (err, searchHistory) => { 79 | if (err) { 80 | return appRes.status(500).send(err); 81 | } 82 | 83 | return appRes.status(200).send({searchHistory}); 84 | }); 85 | }; 86 | 87 | /** 88 | * @description Handles request to delete all saved search history 89 | * 90 | * @api {DELETE} /search/history 91 | * @apiSuccess 200 {success} Sucessfully deleted all search history data 92 | * @apiError 500 {server error} 93 | */ 94 | exports.deleteSearchHistory = (appReq, appRes) => { 95 | SearchHistory.remove({ user: appReq.userId }).then(() => { 96 | appRes.status(200).send({ success: true }); 97 | }).catch((err) => { 98 | appRes.status(500).send({ 99 | error: err, 100 | success: false, 101 | }); 102 | }); 103 | }; 104 | -------------------------------------------------------------------------------- /client/src/routes/directions/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component } from "preact"; 2 | import style from "./style"; 3 | import MapPane from '../../components/LeafletOsmMap/MapPane'; 4 | 5 | /** 6 | * Leaflet related imports: leaflet, pouchdb module, and routing machine module 7 | */ 8 | import '../../../node_modules/leaflet/dist/leaflet.css'; 9 | import '../../../node_modules/leaflet-routing-machine/dist/leaflet-routing-machine.css'; 10 | import L from '../../js/leaflet-tileLayer-pouchdb-cached'; 11 | import Routing from '../../../node_modules/leaflet-routing-machine/src/index.js'; 12 | 13 | /** 14 | * TIle layer configuration and attribution constants 15 | */ 16 | const OSM_URL = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; 17 | const OSM_ATTRIB = '© OpenStreetMap contributors'; 18 | const OSM_TILE_LAYER = new L.TileLayer(OSM_URL, { 19 | attribution: OSM_ATTRIB, 20 | useCache: true, 21 | crossOrigin: true, 22 | }); 23 | 24 | // redirect marker icon path to assets directory 25 | L.Icon.Default.imagePath = '../../assets/icons/leaflet/'; 26 | 27 | export default class Directions extends Component { 28 | constructor(props) { 29 | super(props); 30 | this.state = { 31 | // map: null, 32 | // lat: null, 33 | // lng: null, 34 | // watchID: null, 35 | airport: { 36 | lat: 38.8512462, 37 | lng: -77.0424202, 38 | address: 'Ronald Reagan Washington National Airport, Arlington, VA 22202', 39 | }, 40 | whiteHouse: { 41 | address: '1600 Pennsylvania Ave NW, Washington, DC 20500', 42 | lat: 38.8976094, 43 | lng: -77.0389236, 44 | } 45 | } 46 | // this.initMap = this.initMap.bind(this); 47 | } 48 | 49 | componentDidMount() { 50 | const map = L.map('map'); 51 | map.addLayer(OSM_TILE_LAYER); 52 | 53 | const control = Routing.control({ 54 | waypoints: [ 55 | L.latLng(this.state.whiteHouse.lat, 56 | this.state.whiteHouse.lng), 57 | L.latLng(this.state.airport.lat, 58 | this.state.airport.lng), 59 | ], 60 | routeWhileDragging: true, 61 | }).addTo(map); 62 | 63 | map.on('click', function(event) { 64 | const container = L.DomUtil.create('div'); 65 | const startBtn = createButton('Start Here', container); 66 | const destBtn = createButton('End Here', container); 67 | 68 | L.popup() 69 | .setContent(container) 70 | .setLatLng(event.latlng) 71 | .openOn(map); 72 | 73 | L.DomEvent.on(startBtn, 'click', function() { 74 | control.spliceWaypoints(0, 1, event.latlng); 75 | map.closePopup(); 76 | }); 77 | 78 | L.DomEvent.on(destBtn, 'click', function() { 79 | control.spliceWaypoints(control.getWaypoints().length - 1, 1, event.latlng); 80 | map.closePopup(); 81 | }); 82 | }); 83 | } 84 | 85 | render() { 86 | return ( 87 |
88 | 89 |
90 | ); 91 | } 92 | } 93 | 94 | function createButton(label, container) { 95 | const btn = L.DomUtil.create('button', '', container); 96 | btn.setAttribute('type', 'button'); 97 | btn.innerHTML = label; 98 | return btn; 99 | } 100 | -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/SVG/whiteLogo.svg: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /client/src/assets/icons/leaflet/SVG/darkLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 37 | 38 | -------------------------------------------------------------------------------- /docs/learning.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Navi: An open source project built for Grow with Google 4 | 5 | 6 | 7 | 8 | ## Technologies used 9 | ### Preact <img style="width:50px;" src="https://cdn.auth0.com/blog/preact/logo.png"> 10 | Preact is a JavaScript library that offers a fast 3kb alternative to React with the same ES6 API. Currently, Preact has over 10,000 stars on GitHub. Preact gives the developer an edge to build super fast JavaScript web applications without the constant headache of performance improvement because of its lightweight footprint 11 | 12 | - [Preact authentication tutorial](https://auth0.com/blog/preact-authentication-tutorial/) 13 | - [Switching to Preact](https://preactjs.com/guide/switching-to-preact) 14 | - [Preact and progressive web apps](https://preactjs.com/guide/progressive-web-apps) 15 | - [Sample code via repls in Preact](https://preactjs.com/repl) 16 | - [Preact on Github](https://github.com/developit/preact) 17 | 18 | ### Node <img style="width:100px;" src="https://upload.wikimedia.org/wikipedia/commons/d/d9/Node.js_logo.svg"> 19 | 20 | Node.js is an open-source, cross-platform JavaScript run-time environment for executing JavaScript code server-side 21 | 22 | - [Node learning resources](https://github.com/vioan/nodejs-learning-resources) 23 | - [Node resources - Medium article](https://medium.com/@cabot_solutions/15-top-resources-for-node-js-developers-2029ab30cfa4) 24 | 25 | #### Express <img style="width:200px;" src="https://paulund.co.uk/app/uploads/2016/11/express-js.jpg"> 26 | - [Serving static files in Express](https://expressjs.com/en/starter/static-files.html) 27 | - [How to deploy a Node app in express using Cloud9](https://medium.com/the-n00b-code-chronicles/how-to-deploy-a-node-js-web-app-using-express-in-cloud9-91e73910293f) 28 | - [Getting started - Express](https://expressjs.com/en/starter/installing.html) 29 | 30 | ### Leaflet <img style="width:200px;" src="http://leafletjs.com/docs/images/logo.png"> 31 | - [Leaflet](http://leafletjs.com/) is an open-source JavaScript library for mobile-friendly interactive maps. It is light at just 38kb 32 | - Layers Out of the Box 33 | - Interaction Features 34 | - Visual Features 35 | - Zoom and pan animation 36 | - [more](leaflet.html) 37 | 38 | ## Concepts 39 | ### Service workers 40 | - [Web fundamentals code lab](https://developers.google.com/web/fundamentals/codelabs/) Progressive Web Apps, Service Workers, Simple Offline Web App and Others 41 | - [PWA case studies](https://developers.google.com/web/showcase/tags/progressive-web-apps) 42 | - [A Beginner's Guide To Progressive Web Apps](https://www.smashingmagazine.com/2016/08/a-beginners-guide-to-progressive-web-apps/) - with sample code Smashing Magazine 43 | - [Fetch API](https://developers.google.com/web/updates/2015/03/introduction-to-fetch) 44 | - [Fetch API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent) 45 | 46 | 47 | ### ES6 48 | - [Template literals (from Google Web Fundamentals)](https://developers.google.com/web/updates/2015/01/ES6-Template-Strings) 49 | - [Understanding ES6](https://leanpub.com/understandinges6/read) (an online book by Nicholas Zakas. Covers block bindings, destructuring, Symbols, Sets and Maps, Iterators and Generators, JavaScript Classes,Improved Array Capabilities, Promises, Proxies and the Reflection API, and Encapsulating Code with Modules) 50 | - [Three great free books](http://exploringjs.com/) (one is O'Reilly animal Safari book series) on Javascript, and ES6 and ES7 51 | - [https://javascript.info](https://javascript.info) 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/CONTRIBUTORS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Google Offline Navigator: An open source project built for Grow with Google 4 | 5 | 6 | # Contributors 7 | 8 | ### Special thanks for all the people who had helped this [project](index.html) so far : 9 | 10 | <p class="txt-cntr">.</p> | <p class="txt-cntr">N</p> | <p class="txt-cntr">A</p>| <p class="txt-cntr">V</p>| <p class="txt-cntr">I</p> | <p class="txt-cntr">.</p> 11 | ---------------- | ---------------- | ----------------- |---------------- | ----------------- | ---------------- 12 | <p class="txt-cntr">![Bryan](https://avatars0.githubusercontent.com/u/17731807?s=70&v=1)<br> [Bryan](https://github.com/motosharpley)</p> | <p class="txt-cntr">![John Kwening](https://avatars3.githubusercontent.com/u/4764968?s=70&v=1)<br> [John Kwening](https://github.com/jkwening)</p> | <p class="txt-cntr">![Travis](https://avatars1.githubusercontent.com/u/6627101?s=70&v=1)<br> [Travis](https://github.com/mallek)</p> | <p class="txt-cntr">![tanyagupta](https://avatars1.githubusercontent.com/u/8497274?s=70&v=1)<br> [tanyagupta](https://github.com/tanyagupta)</p> | <p class="txt-cntr">![Sameer Khan](https://avatars1.githubusercontent.com/u/21181432?s=70&v=1)<br> [Sameer Khan](https://github.com/sameerkhan116)</p> | <p class="txt-cntr">![Christopher](https://avatars3.githubusercontent.com/u/21190284?s=70&v=1)<br> [Christopher Gates](https://github.com/tophergates)</p> 13 | <p class="txt-cntr">![desdemonhu](https://avatars1.githubusercontent.com/u/26439524?s=70&v=1)<br> [desdemonhu](https://github.com/desdemonhu)</p> | <br><p class="txt-cntr"> [Vignesh](https://github.com/vettyvignesh)</p> |<p class="txt-cntr"> ![Stephen](https://avatars2.githubusercontent.com/u/3073252?s=70&v=1)<br> [Stephen Chavez](https://github.com/redragonx)</p> | <p class="txt-cntr">![Khusbu Chandra](https://avatars0.githubusercontent.com/u/35678241?s=70&v=1)<br> [Khusbu Chandra](https://github.com/khusbuchandra)</p> | <br><p class="txt-cntr">[Eddie](https://github.com/seckboy)</p> | <p class="txt-cntr">![Ricky](https://avatars3.githubusercontent.com/u/35318805?s=70&v=1)<br> [Ricky](https://github.com/ricky-rose)</p> 14 | <p class="txt-cntr">![Alice Palazzolo](https://avatars1.githubusercontent.com/u/35308564?s=70&v=1)<br> [Alice Palazzolo](https://github.com/alicepalazzolo)</p> | <p class="txt-cntr">![Emily Sperry](https://avatars3.githubusercontent.com/u/20411197?s=70&v=1)<br> [Emily Sperry](https://github.com/sperrye)</p> | <p class="txt-cntr">![Peter Matthews](https://avatars0.githubusercontent.com/u/11466707?s=70&v=1)<br> [Peter Matthews](https://github.com/pjdmatts)</p> | <p class="txt-cntr">![Owen](https://avatars0.githubusercontent.com/u/10936591?s=70&v=1)><br> [Owen](https://github.com/othomas1984)</p> | <p class="txt-cntr">![Scott Westover](https://avatars2.githubusercontent.com/u/9931822?s=70&v=1)<br> [Scott Westover](https://github.com/scottwestover)</p> | <p class="txt-cntr">![Ben Reckas](https://avatars0.githubusercontent.com/u/29763017?s=70&v=1)<br> [Ben Reckas](https://github.com/benreckas)<p/> 15 | <p class="txt-cntr">![Evan](https://avatars2.githubusercontent.com/u/16908616?s=70&v=1)<br> [Evan](https://github.com/CodeDraken)</p> | <p class="txt-cntr">![Mebin Robin](https://avatars1.githubusercontent.com/u/35190613?s=70&v=1)<br> [Mebin Robin](https://github.com/mebin)</p> | <p class="txt-cntr">![Minh](https://avatars0.githubusercontent.com/u/18540434?s=70&v=1)<br> [Minh](https://github.com/Minh-B-Dang)</p> | <p class="txt-cntr">![Sonali Shukla](https://avatars0.githubusercontent.com/u/4163671?s=70&v=1)<br> [Sonali Shukla](https://github.com/sonalikatara)</p> | | 16 | 17 | ### If you would like to join this list. 18 | 19 | We're currently looking for contributions for the following: 20 | 21 | - [ ] Bug fixes 22 | - [ ] Translations 23 | - [ ] etc... 24 | 25 | For more information, please refer to our [CONTRIBUTING](CONTRIBUTING.md) guide. 26 | 27 | ![Logo](https://user-images.githubusercontent.com/35308564/36317506-4407c0aa-130b-11e8-88a2-af1cca16eaba.PNG "Logo") 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /controllers/saved-directions-controller.js: -------------------------------------------------------------------------------- 1 | const SavedDirections = require('../models/saved-directions'); 2 | 3 | /** 4 | * @description Handles request for user saved directions 5 | * 6 | * @api {GET} /search/directions 7 | * @apiSuccess 200 {SavedDirections} The collection of all saved directions. 8 | * @apiError 500 {server error} Problem with server. 9 | */ 10 | exports.getSavedDirections = (appReq, appRes) => { 11 | SavedDirections.find({ user: appReq.userId }).then((directions) => { 12 | appRes.status(200).send({ directions }); 13 | }, (err) => { 14 | appRes.status(500).send(err); 15 | }); 16 | }; 17 | 18 | /** 19 | * @description Handle request for getting a specified number of recently 20 | * saved directions 21 | * 22 | * @api {GET} /search/directions/recent/:num 23 | * @apiSuccess 200 {directions} The requested recent num of saved directions. 24 | * @apiError 500 {server error} Server error 25 | * @apiError 404 {request error} Invalid req.params.num 26 | * 27 | * @param {String} appReq.params.num - Number of most recent saved directions to return. 28 | */ 29 | exports.getRecentDirections = (appReq, appRes) => { 30 | const num = parseInt(appReq.params.num); 31 | if (isNaN(num)) { 32 | return appRes.status(404).send({ 33 | error: 'Invalid params passed.', 34 | param: appReq.params.num, 35 | parsedInt: num 36 | }); 37 | } 38 | 39 | SavedDirections.find({ user: appReq.userId }) 40 | .sort({save_date: 'desc'}) 41 | .then((result) => { 42 | const directions = result.slice(0, num); 43 | appRes.send({ directions }); 44 | }, (err) => { 45 | appRes.status(500).send(err); 46 | }); 47 | }; 48 | 49 | /** 50 | * @description Handles request to save a search query if not already in db 51 | * 52 | * @api {POST} /search/directions/ 53 | * @apiSuccess 200 {directions} The document representing the saved query. 54 | * @apiError 500 {server error} Problem saving the requested pin. 55 | * 56 | * @param {lat, lng} appReq.body.origin - starting location 57 | * @param {lat, lng} appReq.body.destination - ending location 58 | * @param {[{lat, lng}]} appReq.body.waypoints - array of geocoded waypoints 59 | * from origin to destination 60 | * @param {[String]} appReq.body.directions - array of string representing 61 | * turn-by-turn directions 62 | * @param {string} appReq.body.label - (optional) user provided label description 63 | */ 64 | exports.saveDirections = (appReq, appRes) => { 65 | const saveQuery = { 66 | origin: appReq.body.origin, 67 | destination: appReq.body.destination, 68 | waypoints: appReq.body.waypoints, 69 | directions: appReq.body.directions, 70 | label: appReq.body.label || `${appReq.body.origin} - ${appReq.body.destination}`, 71 | user: appReq.userId, 72 | } 73 | 74 | const update = { 75 | lastModified: true, 76 | $currentDate : { 77 | save_date: {$type: 'date'}, 78 | }, 79 | $set: { 80 | waypoints: saveQuery.waypoints, 81 | directions: saveQuery.directions, 82 | label: saveQuery.label, 83 | } 84 | } 85 | 86 | const options = { 87 | new: true, // return modified doc instead of original 88 | upsert: true, // create if doesn't exist 89 | } 90 | 91 | SavedDirections.findOneAndUpdate({ 92 | origin: saveQuery.origin, 93 | destination: saveQuery.destination, 94 | user: saveQuery.user, 95 | }, 96 | update, 97 | options, 98 | (err, directions) => { 99 | if (err) { 100 | return appRes.status(500).send(err); 101 | } 102 | 103 | return appRes.status(200).send({directions}); 104 | }); 105 | }; 106 | 107 | /** 108 | * @description Handles request to delete all saved directions 109 | * 110 | * @api {DELETE} /search/directions 111 | * @apiSuccess 200 {success} Sucessfully deleted all directions data 112 | * @apiError 500 {server error} 113 | */ 114 | exports.deleteSavedDirections = (appReq, appRes) => { 115 | SavedDirections.remove({ user: appReq.userId }).then(() => { 116 | appRes.status(200).send({ success: true }); 117 | }).catch((err) => { 118 | appRes.status(500).send({ 119 | error: err, 120 | success: false, 121 | }); 122 | }); 123 | }; 124 | 125 | -------------------------------------------------------------------------------- /controllers/saved-pins-controller.js: -------------------------------------------------------------------------------- 1 | /* eslint no-underscore-dangle: 0 */ 2 | const { ObjectID } = require('mongodb'); 3 | 4 | const SavedPins = require('../models/saved-pins'); 5 | 6 | /** 7 | * @description Handles request for user saved pins 8 | * 9 | * @api {GET} /search/savedpins 10 | * @apiSuccess 200 {SavedPins} The collection of saved pins. 11 | * @apiError 500 {server error} Problem finding all saved pins. 12 | */ 13 | exports.getSavedPins = (appReq, appRes) => { 14 | SavedPins.find({ user: appReq.userId }).then((savedPins) => { 15 | appRes.send({ savedPins }); 16 | }, (e) => { 17 | appRes.status(500).send(e); 18 | }); 19 | }; 20 | 21 | /** 22 | * @description Handle request for getting saved pin with a given id 23 | * 24 | * @api {GET} /search/savedpins/:id 25 | * @apiSuccess 200 {Pin} The requested pin object. 26 | * @apiError 500 {server error} 27 | * @apiError 404 {request error} Invalid pin id. 28 | * 29 | * @param {String} appReq.params.id - Unique id for the requested pin 30 | */ 31 | exports.getSavedPinsById = (appReq, appRes) => { 32 | const params = { id: appReq.params.id }; 33 | if (!ObjectID.isValid(params.id)) { 34 | return appRes.status(404).send(); 35 | } 36 | 37 | SavedPins.findOne({ 38 | _id: params.id, 39 | user: appReq.userId, 40 | }).then((pin) => { 41 | // expect db id to be unique but just in case verifiy user._id 42 | if (!pin || appReq.userId !== pin.user.toString()) { 43 | return appRes.status(404).send(); 44 | } 45 | return appRes.send({ pin }); 46 | }).catch((e) => { 47 | appRes.status(500).send(e); 48 | }); 49 | }; 50 | 51 | /** 52 | * @description Handles request to save a pin 53 | * 54 | * @api {POST} /search/savedpins 55 | * @apiSuccess 200 {SavedPins} The document representing the saved pin. 56 | * @apiError 500 {server error} Problem saving the requested pin. 57 | * 58 | * @param {number} appReq.body.lat - lattitude coordinate for pin location 59 | * @param {number} appReq.body.lng - longitude coordinate for pin location 60 | * @param {string} appReq.body.place_id - name of pin location 61 | */ 62 | exports.postSavedPins = (appReq, appRes) => { 63 | 64 | //ensure not duplicate first 65 | SavedPins.find({ user: appReq.userId }) 66 | .then(savedPins => { 67 | //check to see if the pin already exists 68 | return savedPins.filter(pin => { 69 | return (pin.lat == appReq.body.lat) && (pin.lng == appReq.body.lng) 70 | }); 71 | 72 | }) 73 | .then(duplicatePins => { 74 | 75 | if (duplicatePins.length > 0) { 76 | appRes.status(400).send({ duplicatePin: duplicatePins, message: 'Duplicate found. Pin not saved.' }); 77 | return 78 | } 79 | 80 | const newPin = new SavedPins({ 81 | lat: appReq.body.lat, 82 | lng: appReq.body.lng, 83 | place_id: appReq.body.place_id, 84 | desc: appReq.body.desc, 85 | user: appReq.userId, // authenticated user's id 86 | }); 87 | 88 | newPin.save().then((pin) => { 89 | appRes.send({ pin }); 90 | }, (e) => { 91 | appRes.status(500).send(e); 92 | }); 93 | 94 | 95 | }) 96 | }; 97 | 98 | /** 99 | * @description Handles request to delete all saved pins 100 | * 101 | * @api {DELETE} /search/savedpins 102 | * @apiSuccess 200 103 | * @apiError 500 {server error} 104 | */ 105 | exports.deleteSavedPins = (appReq, appRes) => { 106 | SavedPins.remove({ user: appReq.userId }).then(() => { 107 | appRes.status(200).send(); 108 | }).catch((e) => { 109 | appRes.status(500).send(e); 110 | }); 111 | }; 112 | 113 | /** 114 | * @description Handles request to delete a saved pin with given id 115 | * 116 | * @api {DELETE} /search/savedpins/:id 117 | * @apiSuccess 200 {Pin} The deleted pin object. 118 | * @apiError 500 {server error} 119 | * @apiError 404 {request error} Invalid pin id. 120 | * 121 | * @param {String} appReq.params.id - Unique id for the requested pin 122 | */ 123 | exports.deleteSavedPinsById = (appReq, appRes) => { 124 | const params = { id: appReq.params.id }; 125 | if (!ObjectID.isValid(params.id)) { 126 | return appRes.status(404).send(); 127 | } 128 | 129 | SavedPins.findByIdAndRemove(params.id).then((pin) => { 130 | if (!pin) { 131 | return appRes.status(404).send(); 132 | } 133 | 134 | appRes.send({ pin }); 135 | }).catch((e) => { 136 | appRes.status(500).send(e); 137 | }); 138 | }; 139 | -------------------------------------------------------------------------------- /client/src/js/saved-places.js: -------------------------------------------------------------------------------- 1 | import {h, Component} from "preact"; 2 | import {makeRequest} from "./server-requests-utils"; 3 | import {createButton, createLabel} from "./utilities"; 4 | 5 | /* Set all settings for saved place icon 6 | Waiting for new icon for favorites 7 | Full list of options: http://leafletjs.com/reference-1.3.0.html#icon 8 | */ 9 | 10 | const FAV_MARKER_OPTIONS = { 11 | iconUrl: '../../assets/icons/leaflet/marker-icon-fav-2x.png', 12 | className: 'favorites', 13 | iconSize: [25, 41], //native aspect ratio: [28, 41] 14 | }; 15 | 16 | 17 | /** 18 | * Exported Functions 19 | */ 20 | 21 | //Get and drop pins from any user on any map (all arguments are optional) 22 | const fetchAndDropUserPins = (user_id, mapObj, L) => { 23 | 24 | getSavedPins(user_id) 25 | .then(savedPins => { 26 | 27 | if (!savedPins) return; 28 | 29 | const pinMarkers = makePinMarkers(savedPins, L); 30 | if (mapObj != null) dropPin(pinMarkers, mapObj); 31 | 32 | }) 33 | } 34 | 35 | const getSavedPins = (user_id) => { 36 | //GET 'search/savedpins/:user_id' 37 | 38 | return makeRequest('GET', 'savedPins', '', user_id) //pinsPromised 39 | .then(res => res.data.savedPins) 40 | .catch(err => { 41 | // console.log(`Couldn't get the user pins: ${err}`); 42 | }); 43 | 44 | } 45 | 46 | //This function generates markers and drops them on a map (if specified) 47 | const makePinMarkers = (pinArray = [], L, markerOptions=FAV_MARKER_OPTIONS) => { 48 | 49 | const icon = L ? L.icon(FAV_MARKER_OPTIONS) : undefined; 50 | 51 | let pinMarkers = []; 52 | 53 | for (const pin of pinArray) { 54 | //create marker for the pin and bind it ot the map 55 | 56 | let thisMarker = []; //the marker icon cannot be changed after the marker is created? 57 | 58 | if (icon) { //if (icon) thisMarker.icon = icon; doesn't work 59 | thisMarker = L.marker([pin.lat, pin.lng], { 60 | icon, 61 | draggable: false, 62 | autopan: true, 63 | riseOnHover: true, 64 | title: pin.place_id, 65 | desc: pin.desc 66 | }); 67 | 68 | } else { 69 | thisMarker = L.marker([pin.lat, pin.lng], { 70 | draggable: false, 71 | autopan: true, 72 | riseOnHover: true, 73 | title: pin.place_id, 74 | desc: pin.desc 75 | }); 76 | } 77 | 78 | const container = L.DomUtil.create('div'); 79 | 80 | thisMarker.data = pin; //save the db data to the marker 81 | pinMarkers.push(thisMarker); 82 | } 83 | 84 | // console.log('User Pins added: ',pinMarkers) 85 | return pinMarkers; 86 | 87 | } 88 | 89 | //This funciton drops pins on a map replacing pins if they already exist 90 | const dropPin = (pinMarkers=[], mapObj=undefined) => { 91 | 92 | //if no map is defined, or there are no pins return 93 | if ((mapObj == null) || (pinMarkers.length == 0)) return; 94 | 95 | /*TO DO?: 96 | Do not allow duplication of pins, 97 | Remove those that exist already on the old map 98 | Hint: use sets 99 | */ 100 | 101 | 102 | for (let marker of pinMarkers){ 103 | marker.addTo(mapObj); 104 | setPinPopup(marker); 105 | } 106 | } 107 | 108 | /* TO DO 109 | Create a custom container for each pin on click */ 110 | const setPinPopup = (marker) => { 111 | console.log(marker); 112 | const container = L.DomUtil.create('div'); 113 | console.log(marker); 114 | const markerDescription = createLabel(marker.options.desc, 'center', container); 115 | const deleteBtn = createButton('Delete', 'center',container, marker.data._id); 116 | marker.bindPopup(container); 117 | 118 | 119 | L.DomEvent.on(deleteBtn, 'click', function() { 120 | makeRequest('DELETE', `/search/savedPins/${event.target.id}`, '', { 121 | //place_id: event.target.id //should be from state not dom 122 | }).then((response) => { 123 | 124 | //remove icon if removal from database was success 125 | marker.remove(); 126 | 127 | }).catch((err) => { 128 | 129 | switch (err.response.status){ 130 | case 400: //duplicate pin 131 | const origPin = err.response.data; 132 | alert('We had an issue deleting this pin.'); 133 | /*TO DO: 134 | Convert popup alert to toast message 135 | */ 136 | break; 137 | default: 138 | console.log(err); //error saving pin 139 | 140 | } 141 | 142 | }) 143 | }); 144 | } 145 | 146 | //Export Statements 147 | export {fetchAndDropUserPins, makePinMarkers, dropPin}; 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | See the Guide on how to contribute [here](https://github.com/TheDevPath/Navi/blob/development/CONTRIBUTING.md#how-to-contribute) for instructions on how to fork and set up your repository. 3 | 4 | # Installing Dependencies 5 | In the root directory of your newly cloned project `npm install` 6 | 7 | In the client directory of your project `npm install` 8 | 9 | Skip this next part if you know what you are doing 10 | 11 | --- 12 | 13 | Noob tip 14 | 15 | *If you can, "clone with `SSH` instead of clone with `HTTPS`. This means that, when you type in git remote add origin, you should use a link that looks like this: `git@github.com:*YOUR_USER_NAME/YOUR_REPO_NAME.git.*` Observe how that differs from* `https://github.com/YOUR_USER_NAME/YOUR_REPO_NAME.git`* 16 | While the first creates a remote that uses `ssh` authentication, the latter uses `https`, so it'll always prompt you to enter your username and password to authenticate the connection. For more see this [link](https://gist.github.com/juemura/899241d73cf719de7f540fc68071bd7d)* 17 | 18 | --- 19 | 20 | # Get Google Maps API key 21 | 22 | - In the config subdirectory you will find secrets-*example.json. *Copy it's contents to a new file called secrets.json in the same directory*. 23 | 24 | - Next get a [Google Maps API key](https://developers.google.com/maps/documentation/javascript/get-api-key) 25 | 26 | - Click on the button 27 | 28 | - This will take you through the process 29 | 30 | - Note: If you have an existing API key, you may use that key. 31 | [Detailed instructions](https://developers.google.com/maps/documentation/javascript/get-api-key) 32 | 33 | - Open `secrets.json` and under googlemaps, paste your API key and save 34 | 35 | # Install mongodb 36 | 37 | You also need to install and have running mongoDB - Directions can be found [here](https://docs.mongodb.com/manual/installation/) 38 | 39 | # Update and run 40 | 41 | When update has completed go to where you installed the project and run `npm install` again to install dependencies in the root and client directories. This will update the project with any new packages added to the file package.json in your project. 42 | 43 | When finished, in the project's root directory type `npm run dev`. This will start the dev servers on `localhost:8080` & `localhost:8081` respectively 44 | 45 | 46 | # About Navi 47 | 48 | This is an open source project for Grow with Google Udacity Scholarship Challenge - Navigation app using offline first strategy and Open Street Maps and google api 49 | 50 | The idea for this project is to build a progressive web app utilizing the technologies learned in the Grow with Google Udacity Scholarship challenge. 51 | 52 | The project idea - build a navigation app that will store a local copy of pre selected directions and maps so that navigation continues to work properly in poor to no signal scenarios. 53 | 54 | The stack - this will be a node app utilizing Preact for the front end. 55 | 56 | Pull requests are welcome! 57 | 58 | ## Table of Contents 59 | 60 | - [Main Goal](#main-goal) 61 | - [Features](#features) 62 | - [About the application](#about-the-application) 63 | - [Where to get the files](#where-to-get-the-files) 64 | - [Key files included](#key-files-included) 65 | - [Requirements](#requirements) 66 | - [ToDo](#todo) 67 | 68 | 69 | ## Main Goal 70 | 71 | The main goal of the app is to provide the user with a map interface that they can use on their mobile device and that will continue to be useful in poor to no signal environments. 72 | 73 | ## Features 74 | 75 | * The interface will **display a map** of a designated area 76 | 77 | 78 | * **Users** will be able to: 79 | * Search for a location 80 | * By typing into a search field 81 | * Drop a pin at a location 82 | * By placing a pin on the map 83 | * By clicking 'drop pin' next to search results 84 | * Get directions to a selected location from: 85 | * Their current location 86 | * Another dropped pin 87 | * Save a dropped pin 88 | * Save a set of directions 89 | * View list of dropped pins ('saved places') 90 | * View list of directions ('saved directions') 91 | 92 | 93 | * **The app** will: 94 | * Use the Google Maps API to: 95 | * Display the map 96 | * Provide a search interface 97 | * Provide a current location 98 | * Provide directions 99 | * Interface with a database to save a users: 100 | * Saved places 101 | * Saved directions 102 | * Maintain last state on loss of signal including: 103 | * Current map View 104 | * Saved places 105 | * Saved directions 106 | 107 | 108 | ### *About the application* 109 | * Node backend 110 | * Preact Frontend 111 | * Open Street Maps 112 | * Google api 113 | * Service Workers 114 | * [MIT License](../blob/master/LICENSE) 115 | 116 | ### *Where to get the files* 117 | * [This repository](https://github.com/TheDevPath/Navi) 118 | 119 | ### *Key files included* 120 | * Files 121 | 122 | ## *Requirements* 123 | * Requirements 124 | 125 | ## *ToDo* 126 | * Improvements 127 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | See the Guide on how to contribute [here](https://github.com/TheDevPath/Navi/blob/development/CONTRIBUTING.md#how-to-contribute) for instructions on how to fork and set up your repository. 3 | 4 | # Installing Dependencies 5 | In the root directory of your newly cloned project `npm install` 6 | 7 | In the client directory of your project `npm install` 8 | 9 | Skip this next part if you know what you are doing 10 | 11 | --- 12 | 13 | Noob tip 14 | 15 | *If you can, "clone with `SSH` instead of clone with `HTTPS`. This means that, when you type in git remote add origin, you should use a link that looks like this: `git@github.com:*YOUR_USER_NAME/YOUR_REPO_NAME.git.*` Observe how that differs from* `https://github.com/YOUR_USER_NAME/YOUR_REPO_NAME.git`* 16 | While the first creates a remote that uses `ssh` authentication, the latter uses `https`, so it'll always prompt you to enter your username and password to authenticate the connection. For more see this [link](https://gist.github.com/juemura/899241d73cf719de7f540fc68071bd7d)* 17 | 18 | --- 19 | 20 | # Get Google Maps API key 21 | 22 | - In the config subdirectory you will find secrets-*example.json. *Copy it's contents to a new file called secrets.json in the same directory*. 23 | 24 | - Next get a [Google Maps API key](https://developers.google.com/maps/documentation/javascript/get-api-key) 25 | 26 | - Click on the button 27 | 28 | - This will take you through the process 29 | 30 | - Note: If you have an existing API key, you may use that key. 31 | [Detailed instructions](https://developers.google.com/maps/documentation/javascript/get-api-key) 32 | 33 | - Open `secrets.json` and under googlemaps, paste your API key and save 34 | 35 | # Install mongodb 36 | 37 | You also need to install and have running mongoDB - Directions can be found [here](https://docs.mongodb.com/manual/installation/) 38 | 39 | # Update and run 40 | 41 | When update has completed go to where you installed the project and run `npm install` again to install dependencies in the root and client directories. This will update the project with any new packages added to the file package.json in your project. 42 | 43 | When finished, in the project's root directory type `npm run dev`. This will start the dev servers on `localhost:8080` & `localhost:8081` respectively 44 | 45 | 46 | # About Navi 47 | 48 | This is an open source project for Grow with Google Udacity Scholarship Challenge - Navigation app using offline first strategy and Open Street Maps and google api 49 | 50 | The idea for this project is to build a progressive web app utilizing the technologies learned in the Grow with Google Udacity Scholarship challenge. 51 | 52 | The project idea - build a navigation app that will store a local copy of pre selected directions and maps so that navigation continues to work properly in poor to no signal scenarios. 53 | 54 | The stack - this will be a node app utilizing Preact for the front end. 55 | 56 | Pull requests are welcome! 57 | 58 | ## Table of Contents 59 | 60 | - [Main Goal](#main-goal) 61 | - [Features](#features) 62 | - [About the application](#about-the-application) 63 | - [Where to get the files](#where-to-get-the-files) 64 | - [Key files included](#key-files-included) 65 | - [Requirements](#requirements) 66 | - [ToDo](#todo) 67 | 68 | 69 | ## Main Goal 70 | 71 | The main goal of the app is to provide the user with a map interface that they can use on their mobile device and that will continue to be useful in poor to no signal environments. 72 | 73 | ## Features 74 | 75 | * The interface will **display a map** of a designated area 76 | 77 | 78 | * **Users** will be able to: 79 | * Search for a location 80 | * By typing into a search field 81 | * Drop a pin at a location 82 | * By placing a pin on the map 83 | * By clicking 'drop pin' next to search results 84 | * Get directions to a selected location from: 85 | * Their current location 86 | * Another dropped pin 87 | * Save a dropped pin 88 | * Save a set of directions 89 | * View list of dropped pins ('saved places') 90 | * View list of directions ('saved directions') 91 | 92 | 93 | * **The app** will: 94 | * Use the Google Maps API to: 95 | * Display the map 96 | * Provide a search interface 97 | * Provide a current location 98 | * Provide directions 99 | * Interface with a database to save a users: 100 | * Saved places 101 | * Saved directions 102 | * Maintain last state on loss of signal including: 103 | * Current map View 104 | * Saved places 105 | * Saved directions 106 | 107 | 108 | ### *About the application* 109 | * Node backend 110 | * Preact Frontend 111 | * Open Street Maps 112 | * Google api 113 | * Service Workers 114 | * [MIT License](../blob/master/LICENSE) 115 | 116 | ### *Where to get the files* 117 | * [This repository](https://github.com/TheDevPath/Navi) 118 | 119 | ### *Key files included* 120 | * Files 121 | 122 | ## *Requirements* 123 | * Requirements 124 | 125 | ## *ToDo* 126 | * Improvements 127 | -------------------------------------------------------------------------------- /client/src/js/validate-account-form.js: -------------------------------------------------------------------------------- 1 | import {route} from 'preact-router'; 2 | import { makeRequest, token, BASE_ENDPOINTS } from './server-requests-utils'; 3 | 4 | const ALERT_MESSAGES = { 5 | passwordFormat: "Password should have at least 6 characters, should have at least one letter, number, and special character", 6 | mismatchedPasswords: "Try again, passwords don't match!", 7 | } 8 | 9 | 10 | const validEmail = (email) => { 11 | const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 12 | return re.test(String(email).toLowerCase()); 13 | }; 14 | 15 | const validPassword = (password) => { 16 | const re = /^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{6,}$/; 17 | return re.test(String(password)); 18 | }; 19 | 20 | const passwordsMatch = (password1, password2) => { 21 | return password1 === password2; 22 | }; 23 | 24 | const validateReset = (formData) => { 25 | const {name, email, password, new_password, confirm_password} = formData; 26 | 27 | // passwords form fields are required so if any is null then user info update 28 | if (password && new_password && confirm_password) { 29 | // validate passwords 30 | if (!validPassword(password) || !validPassword(new_password) || 31 | !validPassword(confirm_password)) { 32 | return { 33 | status: false, 34 | message: ALERT_MESSAGES.passwordFormat, 35 | body: null, 36 | }; 37 | } else if (!passwordsMatch(new_password, confirm_password)) { 38 | return { 39 | status: false, 40 | message: ALERT_MESSAGES.mismatchedPasswords, 41 | body: null, 42 | }; 43 | } else { 44 | return { 45 | status: true, 46 | message: 'Success - valid request!', 47 | body: {password, new_password, confirm_password}, 48 | }; 49 | } 50 | } 51 | 52 | // validate user info has at least one field completed 53 | if (!name && !email) { 54 | return { 55 | status: false, 56 | message: 'Please enter a new name and/or email address!', 57 | body: null, 58 | }; 59 | } 60 | 61 | return { 62 | status: true, 63 | message: 'Success - valid request!', 64 | body: {name, email}, 65 | }; 66 | }; 67 | 68 | const validateLogin = (formData) => { 69 | const {email, password} = formData; 70 | 71 | if (!validPassword(password)) { 72 | return { 73 | status: false, 74 | message: ALERT_MESSAGES.passwordFormat, 75 | body: null, 76 | }; 77 | } 78 | 79 | return { 80 | status: true, 81 | message: 'Success - valid request!', 82 | body: {email, password}, 83 | }; 84 | }; 85 | 86 | const validateRegister = (formData) => { 87 | const {name, email, password, confirm_password} = formData; 88 | 89 | if (!validPassword(password) || !validPassword(confirm_password)) { 90 | return { 91 | status: false, 92 | message: ALERT_MESSAGES.passwordFormat, 93 | body: null, 94 | }; 95 | } else if (!passwordsMatch(password, confirm_password)) { 96 | return { 97 | status: false, 98 | message: ALERT_MESSAGES.mismatchedPasswords, 99 | body: null, 100 | }; 101 | } else { 102 | return { 103 | status: true, 104 | message: 'Success - valid request!', 105 | body: {name, email, password, confirm_password}, 106 | }; 107 | } 108 | }; 109 | 110 | // Exports below 111 | export const validateAccountForm = (args) => { 112 | 113 | let {path, formData} = args; 114 | 115 | let result = null; 116 | 117 | if (path === BASE_ENDPOINTS.userReset) { 118 | result = validateReset(formData); 119 | 120 | if (result.body.email || result.body.name) { 121 | path = BASE_ENDPOINTS.userUpdate; 122 | } 123 | } 124 | 125 | if (path === BASE_ENDPOINTS.userRegister) { 126 | result = validateRegister(formData); 127 | } 128 | 129 | if (path == BASE_ENDPOINTS.userLogin) { 130 | result = validateLogin(formData); 131 | } 132 | 133 | const {status, message, body} = result; 134 | 135 | // return if validation fails 136 | if (!status) { 137 | return new Promise((resolve, reject) => { 138 | resolve({status, message}); 139 | }); 140 | } 141 | 142 | // otherwise process server request 143 | return new Promise((resolve, reject) => { 144 | makeRequest('POST', path, '', body) 145 | .then(function (response) { 146 | // TODO - need to better handle token assignment and update 147 | // because depending on the type of response this is accidentally 148 | // removing the token. This temp change should prevent that 149 | // from happening for now. 150 | if (response.data.token) { 151 | token.setCookie(response.data.token); 152 | } 153 | resolve({status, message}); 154 | }) 155 | .catch(function (error) { 156 | reject(error); 157 | }); 158 | }); 159 | }; 160 | 161 | export const clearForms = () => { 162 | for (let form of document.getElementsByTagName("form")) { 163 | form.reset(); 164 | } 165 | }; 166 | 167 | export const logout = () => { 168 | token.deleteCookie(); 169 | }; 170 | 171 | export const setStateUserOrRedirectToSignIn = (component) => { 172 | return makeRequest('GET','user') 173 | .then((response) => { 174 | component.setState({ 175 | user: response.data, 176 | isSignedIn: true, 177 | }); 178 | } 179 | ).catch(() => { 180 | route('/signin', true); 181 | }); 182 | } 183 | -------------------------------------------------------------------------------- /tests/saved-pins-test.js: -------------------------------------------------------------------------------- 1 | /* eslint no-underscore-dangle: 0 */ 2 | // api routes tests 3 | const request = require('supertest'); 4 | const { ObjectID } = require('mongodb'); 5 | 6 | const app = require('../app'); 7 | const SavedPins = require('../models/saved-pins'); 8 | const { 9 | pins, populatePins, users, populateUsers 10 | } = require('./seed/seed'); 11 | 12 | beforeEach(populateUsers); 13 | beforeEach(populatePins); 14 | 15 | describe('POST /search/savedpins', () => { 16 | it('should create new saved pin', (done) => { 17 | const new_pin = { 18 | lat: 10, 19 | lng: 20, 20 | place_id: 'testing pin1', 21 | user: users[0]._id 22 | }; 23 | request(app) 24 | .post('/search/savedpins') 25 | .set('x-access-token', users[0].tokens[0].token) 26 | .send(new_pin) 27 | .expect(200) 28 | .expect((res) => { 29 | expect(res.body.pin.lat).to.equal(new_pin.lat); 30 | expect(res.body.pin.lng).to.equal(new_pin.lng); 31 | }) 32 | .end((err, res) => { 33 | if (err) { 34 | return done(err); 35 | } 36 | 37 | SavedPins.findOne({lat: 10, lng: 20}).then(receivedPin => { 38 | expect(receivedPin).to.deep.include(new_pin); 39 | done(); 40 | }).catch(e => done(e)); 41 | }); 42 | }); 43 | 44 | it('should not save a duplicate pin', done => { 45 | request(app) 46 | .post('/search/savedpins') 47 | .set('x-access-token', users[0].tokens[0].token) 48 | .send(pins[0]) 49 | .expect(400) 50 | .end((err, res) => { 51 | if (err) return done(err); 52 | expect(res.body.message).to.equal("Duplicate found. Pin not saved."); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('should not create savedPin with invalid body data', (done) => { 58 | request(app) 59 | .post('/search/savedpins') 60 | .set('x-access-token', users[0].tokens[0].token) 61 | .send({}) 62 | .expect(500) 63 | .end((err, res) => { 64 | if (err) { 65 | return done(err); 66 | } 67 | 68 | SavedPins.find().then((savedPins) => { 69 | expect(savedPins.length).to.equal(pins.length); 70 | done(); 71 | }).catch(e => done(e)); 72 | }); 73 | }); 74 | }); 75 | 76 | describe('GET /search/savedpins', () => { 77 | it('should return savedpins doc', (done) => { 78 | request(app) 79 | .get('/search/savedpins/') 80 | .set('x-access-token', users[0].tokens[0].token) 81 | .expect(200) 82 | .expect((res) => { 83 | expect(res.body.savedPins[0].lat).to.equal(pins[0].lat); 84 | expect(res.body.savedPins[0].lng).to.equal(pins[0].lng); 85 | expect(res.body.savedPins.length).to.equal(1); 86 | }) 87 | .end(done); 88 | }); 89 | }); 90 | 91 | describe('GET /search/savedpins/:id', () => { 92 | it('should return savedpins doc', (done) => { 93 | request(app) 94 | .get(`/search/savedpins/${pins[0]._id.toHexString()}`) 95 | .set('x-access-token', users[0].tokens[0].token) 96 | .expect(200) 97 | .expect((res) => { 98 | expect(res.body.pin.lat).to.equal(pins[0].lat); 99 | expect(res.body.pin.lng).to.equal(pins[0].lng); 100 | }) 101 | .end(done); 102 | }); 103 | 104 | it('should return 404 if savedpins not found', (done) => { 105 | const hexId = new ObjectID().toHexString(); 106 | 107 | request(app) 108 | .get(`/search/savedpins/${hexId}`) 109 | .set('x-access-token', users[0].tokens[0].token) 110 | .expect(404) 111 | .end(done); 112 | }); 113 | 114 | it('should return 404 for non-object ids', (done) => { 115 | request(app) 116 | .get('/search/savedpins/123abc') 117 | .set('x-access-token', users[0].tokens[0].token) 118 | .expect(404) 119 | .end(done); 120 | }); 121 | }); 122 | 123 | describe('DELETE /search/savedpins', () => { 124 | it('should remove all searchpins', (done) => { 125 | request(app) 126 | .delete('/search/savedpins') 127 | .set('x-access-token', users[0].tokens[0].token) 128 | .expect(200) 129 | .end((err, res) => { 130 | if (err) { 131 | return done(err); 132 | } 133 | 134 | SavedPins.find().then((allPins) => { 135 | expect(allPins.length).to.equal(pins.length - 1); 136 | done(); 137 | }); 138 | }); 139 | }); 140 | }); 141 | 142 | describe('DELETE /search/savedpins/:id', () => { 143 | it('should remove a single searchpins', (done) => { 144 | const hexId = pins[0]._id.toHexString(); 145 | 146 | request(app) 147 | .delete(`/search/savedpins/${hexId}`) 148 | .set('x-access-token', users[0].tokens[0].token) 149 | .expect(200) 150 | .expect((res) => { 151 | expect(res.body.pin._id).to.equal(hexId); 152 | }) 153 | .end((err, res) => { 154 | if (err) { 155 | return done(err); 156 | } 157 | 158 | SavedPins.findById(hexId).then((resPin) => { 159 | expect(resPin).to.equal(null); 160 | done(); 161 | }).catch(e => done(e)); 162 | }); 163 | }); 164 | 165 | it('should return 404 if pin not found', (done) => { 166 | const hexId = new ObjectID().toHexString(); 167 | 168 | request(app) 169 | .delete(`/search/savedpins/${hexId}`) 170 | .set('x-access-token', users[0].tokens[0].token) 171 | .expect(404) 172 | .end(done); 173 | }); 174 | 175 | it('should return 404 if object id is invalid', (done) => { 176 | request(app) 177 | .delete('/search/savedpins/123abc') 178 | .set('x-access-token', users[0].tokens[0].token) 179 | .expect(404) 180 | .end(done); 181 | }); 182 | }); 183 | 184 | -------------------------------------------------------------------------------- /client/tests/AccountForm.test.js: -------------------------------------------------------------------------------- 1 | import {h} from 'preact'; 2 | import {shallow} from 'preact-render-spy'; 3 | import AccountForm from '../src/components/AccountForm'; 4 | import {REGISTER_PATH, LOGIN_PATH, RESET_PATH} from '../config'; 5 | jest.mock('preact-router'); 6 | const myRouter = require('preact-router'); 7 | import * as validate_constants from '../src/js/validate-account-form'; 8 | import {BASE_ENDPOINTS} from '../src/js/server-requests-utils'; 9 | 10 | const VALIDATION_MESSAGE = "VALIDATION MESSAGE"; 11 | 12 | const setup = ({testPropsOverrides, wrapperStateOverrides, wrapperPropsOverrides}) => { 13 | 14 | /** 15 | * If validateAccountForm resolves, it does not necessarily mean the form is validated. 16 | * validateAccountForm will resolve with status: false if the form is not validated. 17 | * validateAccountForm only rejects if there is an error from the server after submitting. 18 | * To test error messages from the server, the test should explicitly set resolve_validation to false. 19 | */ 20 | const testProps = Object.assign({ 21 | resolve_validation: true 22 | }, testPropsOverrides); 23 | 24 | // Mock the alert function 25 | window.alert = jest.fn().mockImplementation( message => testProps.alert_message = message); 26 | 27 | // Mock the route function 28 | myRouter.route = jest.fn().mockImplementation((url, replace) => { 29 | testProps.route_url = url; 30 | testProps.route_replace = replace; 31 | }); 32 | 33 | // Mock the validateAccountForm function 34 | validate_constants.validateAccountForm = jest.fn().mockImplementation( () => { 35 | return new Promise((resolve, reject) => { 36 | if (testProps.resolve_validation) { 37 | resolve({status: testProps.validation_status, message: testProps.validation_message}); 38 | } else { 39 | reject({response: {data: testProps.validation_message}}) 40 | } 41 | }); 42 | }); 43 | 44 | return testProps; 45 | } 46 | 47 | describe('', () => { 48 | 49 | const wrapper = shallow(); 50 | 51 | describe('page loads', () => { 52 | 53 | it('has 4 inputs', async () => { 54 | const testProps = setup({ 55 | testPropsOverrides: { 56 | wrapper, 57 | } 58 | }); 59 | 60 | const inputs = testProps.wrapper.find('input'); 61 | expect(inputs.length).toBe(4); 62 | }); 63 | }); 64 | 65 | describe('validateAccountForm returns true status and message', () => { 66 | 67 | it('routes to /profile', async () => { 68 | const testProps = setup({ 69 | testPropsOverrides: { 70 | validation_status: true, 71 | wrapper, 72 | } 73 | }); 74 | 75 | await Promise.all([testProps.wrapper.find('form').at(0).simulate('submit',{ preventDefault () {} })]); 76 | expect(testProps.route_url).toBe('/profile'); 77 | expect(testProps.route_replace).toBe(true); 78 | }); 79 | 80 | }); 81 | 82 | describe('validateAccountForm returns false status and message', () => { 83 | 84 | it('warns message', async () => { 85 | const testProps = setup({ 86 | testPropsOverrides: { 87 | validation_status: false, 88 | validation_message: VALIDATION_MESSAGE, 89 | wrapper, 90 | } 91 | }); 92 | 93 | await Promise.all([testProps.wrapper.find('form').at(0).simulate('submit',{ preventDefault () {} })]); 94 | expect(testProps.alert_message).toBe(VALIDATION_MESSAGE); 95 | }); 96 | 97 | }); 98 | 99 | describe('validateAccountForm rejects instead of resolves', () => { 100 | 101 | it('warns message', async () => { 102 | const testProps = setup({ 103 | testPropsOverrides: { 104 | resolve_validation: false, 105 | validation_message: VALIDATION_MESSAGE, 106 | wrapper, 107 | } 108 | }); 109 | 110 | await Promise.all([testProps.wrapper.find('form').at(0).simulate('submit',{ preventDefault () {} })]); 111 | expect(testProps.alert_message).toBe(VALIDATION_MESSAGE); 112 | }); 113 | 114 | }); 115 | 116 | }); 117 | 118 | 119 | describe('', () => { 120 | 121 | const wrapper = shallow(); 122 | 123 | describe('page loads', () => { 124 | 125 | it('has 2 inputs', async () => { 126 | const testProps = setup({ 127 | testPropsOverrides: { 128 | wrapper, 129 | } 130 | }); 131 | 132 | const inputs = testProps.wrapper.find('input'); 133 | expect(inputs.length).toBe(2); 134 | }); 135 | }); 136 | 137 | }); 138 | 139 | describe('', () => { 140 | 141 | const wrapper = shallow(); 142 | 143 | describe('page loads', () => { 144 | 145 | it('has 5 inputs', async () => { 146 | const testProps = setup({ 147 | testPropsOverrides: { 148 | wrapper, 149 | } 150 | }); 151 | 152 | const inputs = testProps.wrapper.find('input'); 153 | expect(inputs.length).toBe(5); 154 | }); 155 | }); 156 | 157 | describe('validateAccountForm returns true status and message', () => { 158 | 159 | it('warns message', async () => { 160 | const testProps = setup({ 161 | testPropsOverrides: { 162 | validation_status: true, 163 | validation_message: VALIDATION_MESSAGE, 164 | wrapper, 165 | }, 166 | }); 167 | 168 | await Promise.all([testProps.wrapper.find('form').at(0).simulate('submit',{ preventDefault () {} })]); 169 | expect(testProps.alert_message).toBe(VALIDATION_MESSAGE); 170 | }); 171 | 172 | }); 173 | 174 | }); 175 | -------------------------------------------------------------------------------- /client/src/components/AccountForm/index.js: -------------------------------------------------------------------------------- 1 | import { h, Component, render } from 'preact'; 2 | import style from './style.css'; 3 | import { validateAccountForm, clearForms } from "../../js/validate-account-form"; 4 | import { BASE_ENDPOINTS } from "../../js/server-requests-utils"; 5 | import linkState from "linkstate"; 6 | import { route } from 'preact-router'; 7 | 8 | export default class AccountForm extends Component { 9 | constructor() { 10 | super(); 11 | 12 | this.routeToRegister = this.routeToRegister.bind(this); 13 | this.handleSubmit = this.handleSubmit.bind(this); 14 | } 15 | 16 | handleSubmit = (event) => { 17 | event.preventDefault(); 18 | 19 | const formData = { 20 | name: this.state.name, 21 | email: this.state.email, 22 | password: this.state.password, 23 | new_password: this.state.new_password, 24 | confirm_password: this.state.confirm_password, 25 | } 26 | 27 | const args = { 28 | path: this.props.path, 29 | formData, 30 | } 31 | 32 | validateAccountForm(args).then((response) => { 33 | console.log('validateAccountForm(): ', response); 34 | if (response.status) { 35 | if (this.props.path === BASE_ENDPOINTS.userReset) { 36 | alert(response.message); 37 | } else { // redirect to /profile with success for login/registration 38 | route(`/profile`, true); 39 | } 40 | } else { 41 | // TODO - alert failure to process 42 | alert(response.message); 43 | } 44 | }).catch(function (error) { 45 | // TODO - standardize API error output so we can handle cleanly 46 | // on frontend 47 | if (error.response) { 48 | alert(error.response.data); 49 | } else { 50 | alert(error); 51 | } 52 | }); 53 | }; 54 | 55 | componentWillUnmount = () => { 56 | clearForms(); 57 | } 58 | 59 | routeToRegister() { 60 | route("/register", true); 61 | } 62 | 63 | render({ path },{ name, email, password, new_password, confirm_password }) { 64 | 65 | //DEFAULT TO LOGIN PATH 66 | let display = 67 |
68 |
69 | 71 | 73 |
74 | 75 |
76 |
77 |
78 |
79 | OR 80 |
81 | 83 |
84 |

forgot password?

85 |
; 86 | 87 | if(path === BASE_ENDPOINTS.userRegister){ 88 | display = 89 |
90 |
91 | 93 | 95 | 97 | 99 | 100 |
101 |
; 102 | } 103 | 104 | if(path === BASE_ENDPOINTS.userReset){ 105 | display = 106 |
107 |
108 |

To change user info:

109 | 111 | 113 | 114 |
115 |
116 |

To change password:

117 | 119 | 121 | 123 | 124 |
125 |
; 126 | } 127 | return ( 128 |
129 | Navi logo 130 | {display} 131 |
132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributions Welcome 2 | 3 | Thanks for your interest in contributing to **Navi**! Contributing to open source projects like this one can be a rewarding way to learn, teach, and build experience. Not only that, contributing is a great way to get involved with _social coding_. We are excited to see what amazing contributions you will make, as well as how your contributions will benefit others. 4 | 5 | If you are new to contributing to open source projects, the process can be intimidating. Not to worry! To help ensure both you and the community get the most out of your contributions, we've put together the following guidelines. 6 | 7 | ## Table of Contents 8 | 9 | 1. [Types of Contributions](#types-of-contributions) 10 | 1. [Ground Rules & Expectations](#ground-rules--expectations) 11 | 1. [How to Contribute](#how-to-contribute) 12 | 13 | --- 14 | 15 | ## Types of Contributions 16 | 17 | The common misconception about contributing to an open source project is that you need to contribute code. In fact, there are numerous ways you can directly contribute. To give you some ideas of how you can contribute, here are some examples of the types of contributions we are looking for: 18 | 19 | ### Developers can: 20 | 21 | * Take a look at the [open issues][issues] and find one you can tackle. 22 | 23 | * Locate and fix bugs. 24 | 25 | * Implement innovative and awesome new features. 26 | 27 | * Help to improve tooling and testing. 28 | 29 | ### Organizers and Planners can: 30 | 31 | * Link to duplicate issues, and suggest new issue labels, to help keep things organized. 32 | 33 | * Go through the [open issues][issues] and suggest closing old ones. 34 | 35 | * Ask clarifying questions on recently opened issues to move the discussion forward. 36 | 37 | * Help to organize meetups about the project. 38 | 39 | ### Writers can: 40 | 41 | * Help to fix or improve the project's documentation. 42 | 43 | * Contribute to the project's [Wiki][wiki]. 44 | 45 | ### Designers can: 46 | 47 | * Design wire frames, mock-ups, graphical assets, and logos. 48 | 49 | * Put together a style guide to help the project have a consistent visual design. 50 | 51 | ### Supporters can: 52 | 53 | * Answer questions for people on open issues, or about the project in general. 54 | 55 | * Help to moderate discussion boards or conversation channels. 56 | 57 | ## Ground Rules & Expectations 58 | 59 | Since the project is constantly being updated with contributions of all sorts, it is important to establish ground rules and as well as expectations. This helps to ensure the best possible experience for users of the Offline Google Maps Navigator application, as well as encourage a positive, helpful, and lively community of active contributors just like you! 60 | 61 | Please make sure you read our [code of conduct][code-of-conduct] prior to contributing. 62 | 63 | ## How to Contribute 64 | 65 | If you'd like to contribute, a good place to start is by searching through the [issues][issues] and [pull requests][pull-requests] to see if someone else had a similar idea or question. 66 | 67 | If you don't see your idea listed, and you think it fits into the goals of the project, you should: 68 | 69 | * **Minor Contribution _(e.g., typo fix)_:** Open a pull request 70 | * **Major Contribution _(e.g., new feature)_:** Start by opening an issue first. That way, other people can weigh in on the discussion and planning before you do any work. 71 | 72 | To start making a contribution: 73 | 74 | 1. `fork` the project repository by clicking the **fork** button on GitHub.![fork](https://help.github.com/assets/images/help/repository/fork_button.jpg) 75 | 76 | 1. `clone` your forked repository (_noob tip: the actual command you type in is everything after the $_): 77 | 78 | ```shell 79 | $ git clone https://github.com//Navi 80 | ``` 81 | 82 | 1. Add a new remote that points to the original project so you can sync project changes with your local copy: 83 | 84 | ```shell 85 | $ git remote add upstream https://github.com/TheDevPath/Navi 86 | ``` 87 | 88 | 1. Pull upstream changes into your local repositories `development` branch: 89 | 90 | ```shell 91 | $ git checkout development 92 | $ git pull upstream development && git push origin development 93 | ``` 94 | 95 | 1. Create a new branch from the `development` branch: 96 | ![branch](https://help.github.com/assets/images/help/branch/branch-selection-dropdown.png) 97 | 98 | **IMPORTANT:** Make sure you are on the `development` branch first. 99 | 100 | ```shell 101 | $ git checkout -b 102 | ``` 103 | 104 | 1. Make your contribution to the project code. 105 | 106 | 1. Write or adapt tests as needed. 107 | 108 | 1. Add or change documentation as needed. 109 | 110 | 1. After commiting changes, push your branch to your fork on Github, the remote `origin`: 111 | 112 | **IMPORTANT:** Your commit message should be in present tense and should describe what the commit, when applied, does to the code - not what you did to the code. 113 | 114 | ```shell 115 | $ git push -u origin 116 | ``` 117 | 118 | 1. From your forked GitHub repository, open a pull request in the branch containing your contributions. Target the project's `development` branch for the pull request. 119 | 120 | 1. At this point, your contribution has been submitted for review. Please be patient while your contribution is being reviewed as this can take some time. Meanwhile, if there are questions or comments on your contribution, please respond and/or update with future commits. 121 | 122 | 1. Once the pull request is approved and merged, you can pull the changes from `upstream` to your local repository and delete your extra branch(es). 123 | 124 | 1. Don't forget to check out more [about] this project 125 | 126 | Happy contributing! 127 | 128 | [issues]: https://github.com/TheDevPath/Navi/issues 129 | [pull-requests]: https://github.com/TheDevPath/Navi/pulls 130 | [wiki]: https://thedevpath.github.io/Navi/ 131 | [code-of-conduct]: ./CODE_OF_CONDUCT.md 132 | [about]: https://github.com/TheDevPath/Navi/blob/static-docs/README.md 133 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributions Welcome 2 | 3 | Thanks for your interest in contributing to **Navi**! Contributing to open source projects like this one can be a rewarding way to learn, teach, and build experience. Not only that, contributing is a great way to get involved with _social coding_. We are excited to see what amazing contributions you will make, as well as how your contributions will benefit others. 4 | 5 | If you are new to contributing to open source projects, the process can be intimidating. Not to worry! To help ensure both you and the community get the most out of your contributions, we've put together the following guidelines. 6 | 7 | ## Table of Contents 8 | 9 | 1. [Types of Contributions](#types-of-contributions) 10 | 1. [Ground Rules & Expectations](#ground-rules--expectations) 11 | 1. [How to Contribute](#how-to-contribute) 12 | 13 | --- 14 | 15 | ## Types of Contributions 16 | 17 | The common misconception about contributing to an open source project is that you need to contribute code. In fact, there are numerous ways you can directly contribute. To give you some ideas of how you can contribute, here are some examples of the types of contributions we are looking for: 18 | 19 | ### Developers can: 20 | 21 | * Take a look at the [open issues][issues] and find one you can tackle. 22 | 23 | * Locate and fix bugs. 24 | 25 | * Implement innovative and awesome new features. 26 | 27 | * Help to improve tooling and testing. 28 | 29 | ### Organizers and Planners can: 30 | 31 | * Link to duplicate issues, and suggest new issue labels, to help keep things organized. 32 | 33 | * Go through the [open issues][issues] and suggest closing old ones. 34 | 35 | * Ask clarifying questions on recently opened issues to move the discussion forward. 36 | 37 | * Help to organize meetups about the project. 38 | 39 | ### Writers can: 40 | 41 | * Help to fix or improve the project's documentation. 42 | 43 | * Contribute to the project's [Wiki][wiki]. 44 | 45 | ### Designers can: 46 | 47 | * Design wire frames, mock-ups, graphical assets, and logos. 48 | 49 | * Put together a style guide to help the project have a consistent visual design. 50 | 51 | ### Supporters can: 52 | 53 | * Answer questions for people on open issues, or about the project in general. 54 | 55 | * Help to moderate discussion boards or conversation channels. 56 | 57 | ## Ground Rules & Expectations 58 | 59 | Since the project is constantly being updated with contributions of all sorts, it is important to establish ground rules and as well as expectations. This helps to ensure the best possible experience for users of the Offline Google Maps Navigator application, as well as encourage a positive, helpful, and lively community of active contributors just like you! 60 | 61 | Please make sure you read our [code of conduct][code-of-conduct] prior to contributing. 62 | 63 | ## How to Contribute 64 | 65 | If you'd like to contribute, a good place to start is by searching through the [issues][issues] and [pull requests][pull-requests] to see if someone else had a similar idea or question. 66 | 67 | If you don't see your idea listed, and you think it fits into the goals of the project, you should: 68 | 69 | * **Minor Contribution _(e.g., typo fix)_:** Open a pull request 70 | * **Major Contribution _(e.g., new feature)_:** Start by opening an issue first. That way, other people can weigh in on the discussion and planning before you do any work. 71 | 72 | To start making a contribution: 73 | 74 | 1. `fork` the project repository by clicking the **fork** button on GitHub.![fork](https://help.github.com/assets/images/help/repository/fork_button.jpg) 75 | 76 | 1. `clone` your forked repository (_noob tip: the actual command you type in is everything after the $_): 77 | 78 | ```shell 79 | $ git clone https://github.com//Navi 80 | ``` 81 | 82 | 1. Add a new remote that points to the original project so you can sync project changes with your local copy: 83 | 84 | ```shell 85 | $ git remote add upstream https://github.com/TheDevPath/Navi 86 | ``` 87 | 88 | 1. Pull upstream changes into your local repositories `development` branch: 89 | 90 | ```shell 91 | $ git checkout development 92 | $ git pull upstream development && git push origin development 93 | ``` 94 | 95 | 1. Create a new branch from the `development` branch: 96 | ![branch](https://help.github.com/assets/images/help/branch/branch-selection-dropdown.png) 97 | 98 | **IMPORTANT:** Make sure you are on the `development` branch first. 99 | 100 | ```shell 101 | $ git checkout -b 102 | ``` 103 | 104 | 1. Make your contribution to the project code. 105 | 106 | 1. Write or adapt tests as needed. 107 | 108 | 1. Add or change documentation as needed. 109 | 110 | 1. After commiting changes, push your branch to your fork on Github, the remote `origin`: 111 | 112 | **IMPORTANT:** Your commit message should be in present tense and should describe what the commit, when applied, does to the code - not what you did to the code. 113 | 114 | ```shell 115 | $ git push -u origin 116 | ``` 117 | 118 | 1. From your forked GitHub repository, open a pull request in the branch containing your contributions. Target the project's `development` branch for the pull request. 119 | 120 | 1. At this point, your contribution has been submitted for review. Please be patient while your contribution is being reviewed as this can take some time. Meanwhile, if there are questions or comments on your contribution, please respond and/or update with future commits. 121 | 122 | 1. Once the pull request is approved and merged, you can pull the changes from `upstream` to your local repository and delete your extra branch(es). 123 | 124 | 1. Don't forget to check out more [about] this project 125 | 126 | Happy contributing! 127 | 128 | [issues]: https://github.com/TheDevPath/Navi/issues 129 | [pull-requests]: https://github.com/TheDevPath/Navi/pulls 130 | [wiki]: https://thedevpath.github.io/Navi/ 131 | [code-of-conduct]: ./CODE_OF_CONDUCT.md 132 | [about]: https://github.com/TheDevPath/Navi/blob/static-docs/README.md 133 | -------------------------------------------------------------------------------- /tests/users-test.js: -------------------------------------------------------------------------------- 1 | // api routes tests 2 | const request = require('supertest'); 3 | const app = require('../app'); 4 | 5 | const { 6 | users, populateUsers, deleteTestUser, stopServer, 7 | } = require('./seed/seed'); 8 | 9 | 10 | beforeEach(populateUsers); 11 | afterEach(deleteTestUser); 12 | afterEach(stopServer); 13 | 14 | describe('/Users API Routes', () => { 15 | describe('GET /users/user', () => { 16 | it('does not send a user if not logged in', (done) => { 17 | request(app) 18 | .get('/users/user') 19 | .expect(401) 20 | .then(() => { 21 | done(); 22 | }) 23 | .catch((err) => { 24 | done(err); 25 | }, 26 | ); 27 | }); 28 | it('Sends the requested user when they exist', (done) => { 29 | request(app) 30 | .get('/users/user') 31 | .set('x-access-token', users[0].tokens[0].token) 32 | .expect(200) 33 | .then((res) => { 34 | expect(res.body._id).to.equal(`${users[0]._id}`); 35 | done(); 36 | }) 37 | .catch((err) => { 38 | done(err); 39 | }); 40 | }); 41 | }); 42 | // /register- POST 43 | // valid email, password, and doesn't exist already 44 | 45 | describe('POST users/register', () => { 46 | it('succesfully creates a new user', (done) => { 47 | request(app) 48 | .post('/users/register') 49 | .send({ 50 | name: users[2].name, 51 | email: users[2].email, 52 | password: users[2].password, 53 | }) 54 | .expect(200) 55 | .then((res) => { 56 | expect(res.body.auth).to.equal(true); 57 | done(); 58 | }) 59 | .catch((err) => { 60 | done(err); 61 | }); 62 | }); 63 | 64 | // valid email and password, but exists already 65 | it('Wont create a new user if email address already exists', (done) => { 66 | request(app) 67 | .post('/users/register') 68 | .send({ 69 | name: users[0].name, 70 | email: users[0].email, 71 | password: users[0].password, 72 | }) 73 | .expect(409) 74 | .then((res) => { 75 | expect(res.text).to.equal('Email already in use.'); 76 | done(); 77 | }) 78 | .catch((err) => { 79 | done(err); 80 | }); 81 | }); 82 | // invalid email and try to create 83 | it('Wont create user with invalid email', (done) => { 84 | request(app) 85 | .post('/users/register') 86 | .send({ 87 | name: users[2].name, 88 | email: 'badtestemail', 89 | password: 'passcode', 90 | }) 91 | .expect(400) 92 | .then((res) => { 93 | expect(res.text).to.equal('Email is not of the valid format'); 94 | done(); 95 | }) 96 | .catch((err) => { 97 | done(err); 98 | }); 99 | }); 100 | // invalid password and try to create 101 | it('Wont create user with invalid password', (done) => { 102 | request(app) 103 | .post('/users/register') 104 | .send({ 105 | name: users[2].name, 106 | email: 'testing@test.com', 107 | password: '0', 108 | }) 109 | .expect(400) 110 | .then((res) => { 111 | expect(res.text).to.equal('Password should have minimum length of 6 & it should have atleast one letter, one number, and one special character'); 112 | done(); 113 | }) 114 | .catch((err) => { 115 | done(err); 116 | }); 117 | }); 118 | }); 119 | 120 | 121 | // /login - POST 122 | // apiSuccess 200 { auth: true, token: token } jsonwebtoken. 123 | describe('POST /users/login', () => { 124 | it('Successfully logs the user in', (done) => { 125 | request(app) 126 | .post('/users/register') 127 | .send({ 128 | name: users[2].name, 129 | email: users[2].email, 130 | password: users[2].password, 131 | }).then((res) => { 132 | request(app) 133 | .post('/users/login') 134 | .set('x-access-token', res.body.token) 135 | .send({ 136 | name: users[2].name, 137 | email: users[2].email, 138 | password: users[2].password, 139 | }) 140 | .expect(200) 141 | .then((finalRes) => { 142 | expect(finalRes.body.auth).to.equal(true); 143 | expect(finalRes.body.token).to.equal(res.body.token); 144 | done(); 145 | }) 146 | .catch((err) => { 147 | done(err); 148 | }); 149 | }); 150 | }); 151 | // apiError 400 { request error } User not found. 152 | it('Should send 404 if user is not found', (done) => { 153 | request(app) 154 | .post('/users/login') 155 | .set('x-access-token', users[0].tokens[0].token) 156 | .send({ 157 | email: 'gibberish@gmail.com', 158 | password: users[0].password, 159 | }) 160 | .expect(404) 161 | .then((res) => { 162 | expect(res.text).to.equal('No user found.'); 163 | done(); 164 | }) 165 | .catch((err) => { 166 | done(err); 167 | }); 168 | }); 169 | // apiError 401 { auth: false, token: null } Invalid password. 170 | it('Should send 401 if user has invalid password', (done) => { 171 | request(app) 172 | .post('/users/login') 173 | .set('x-access-token', users[0].tokens[0].token) 174 | .send({ 175 | email: users[0].email, 176 | password: 'badpassword', 177 | }) 178 | .expect(401) 179 | .then((res) => { 180 | expect(res.body.auth).to.equal(false); 181 | expect(res.body.token).to.equal(null); 182 | done(); 183 | }) 184 | .catch((err) => { 185 | done(err); 186 | }); 187 | }); 188 | }); 189 | 190 | // /logout - GET 191 | describe('GET /users/logout', () => { 192 | // apiSuccess 200 { auth: false, token: null } 193 | it('Should send 200 if logout is successful', (done) => { 194 | request(app) 195 | .get('/users/logout') 196 | .set('x-access-token', users[0].tokens[0].token) 197 | .expect(200) 198 | .then((res) => { 199 | expect(res.body.auth).to.equal(false); 200 | expect(res.body.token).to.equal(null); 201 | done(); 202 | }) 203 | .catch((err) => { 204 | done(err); 205 | }); 206 | }); 207 | }); 208 | }); 209 | --------------------------------------------------------------------------------