diff --git a/client/config/dev.config.js b/client/config/dev.config.js index a6c725c..11b5769 100644 --- a/client/config/dev.config.js +++ b/client/config/dev.config.js @@ -4,4 +4,6 @@ module.exports = { REGISTER_PATH: '/users/register', LOGIN_PATH: '/users/login', RESET_PATH: '/users/reset-password', + SEARCH_HISTORY_PATH: '/search/history', + SAVED_PINS_PATH: '/search/savedpins', }; diff --git a/client/package-lock.json b/client/package-lock.json index 3a68026..831e1c9 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -765,7 +765,7 @@ }, "ast-types-flow": { "version": "0.0.7", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", "dev": true }, @@ -821,7 +821,7 @@ }, "axobject-query": { "version": "0.1.0", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/axobject-query/-/axobject-query-0.1.0.tgz", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-0.1.0.tgz", "integrity": "sha1-YvWdvFnJ+SQnWco0mWDnov48NsA=", "dev": true, "requires": { @@ -2825,7 +2825,7 @@ }, "contains-path": { "version": "0.1.0", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/contains-path/-/contains-path-0.1.0.tgz", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true }, @@ -3379,7 +3379,7 @@ }, "damerau-levenshtein": { "version": "1.0.4", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", "integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=", "dev": true }, @@ -4090,7 +4090,7 @@ }, "emoji-regex": { "version": "6.5.1", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/emoji-regex/-/emoji-regex-6.5.1.tgz", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz", "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==", "dev": true }, @@ -4345,7 +4345,7 @@ }, "eslint-config-airbnb-base": { "version": "12.1.0", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz", "integrity": "sha512-/vjm0Px5ZCpmJqnjIzcFb9TKZrKWz0gnuG/7Gfkt0Db1ELJR51xkZth+t14rYdqWgX836XbuxtArbIHlVhbLBA==", "dev": true, "requires": { @@ -4354,7 +4354,7 @@ }, "eslint-import-resolver-node": { "version": "0.3.2", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", "dev": true, "requires": { @@ -4364,7 +4364,7 @@ "dependencies": { "debug": { "version": "2.6.9", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/debug/-/debug-2.6.9.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { @@ -4375,7 +4375,7 @@ }, "eslint-module-utils": { "version": "2.1.1", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==", "dev": true, "requires": { @@ -4385,7 +4385,7 @@ "dependencies": { "debug": { "version": "2.6.9", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/debug/-/debug-2.6.9.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { @@ -4394,7 +4394,7 @@ }, "find-up": { "version": "1.1.2", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/find-up/-/find-up-1.1.2.tgz", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { @@ -4404,7 +4404,7 @@ }, "path-exists": { "version": "2.1.0", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/path-exists/-/path-exists-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { @@ -4413,7 +4413,7 @@ }, "pkg-dir": { "version": "1.0.0", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/pkg-dir/-/pkg-dir-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", "dev": true, "requires": { @@ -4442,7 +4442,7 @@ "dependencies": { "debug": { "version": "2.6.9", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/debug/-/debug-2.6.9.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { @@ -4451,7 +4451,7 @@ }, "doctrine": { "version": "1.5.0", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/doctrine/-/doctrine-1.5.0.tgz", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { @@ -4461,7 +4461,7 @@ }, "load-json-file": { "version": "2.0.0", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/load-json-file/-/load-json-file-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { @@ -4473,7 +4473,7 @@ }, "path-type": { "version": "2.0.0", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/path-type/-/path-type-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { @@ -4482,7 +4482,7 @@ }, "read-pkg": { "version": "2.0.0", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/read-pkg/-/read-pkg-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { @@ -4493,7 +4493,7 @@ }, "read-pkg-up": { "version": "2.0.0", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { @@ -4503,7 +4503,7 @@ }, "strip-bom": { "version": "3.0.0", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/strip-bom/-/strip-bom-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true } @@ -4555,7 +4555,7 @@ }, "eslint-restricted-globals": { "version": "0.1.1", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", "dev": true }, @@ -7771,7 +7771,7 @@ }, "lodash.cond": { "version": "4.5.2", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/lodash.cond/-/lodash.cond-4.5.2.tgz", + "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", "dev": true }, @@ -9454,7 +9454,7 @@ }, "path-parse": { "version": "1.0.5", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/path-parse/-/path-parse-1.0.5.tgz", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", "dev": true }, @@ -12402,7 +12402,7 @@ }, "resolve": { "version": "1.5.0", - "resolved": "https://bcitllc.pkgs.visualstudio.com/_packaging/BcitPackages/npm/registry/resolve/-/resolve-1.5.0.tgz", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", "dev": true, "requires": { diff --git a/client/src/assets/icons/leaflet/marker-icon-fav-2x.png b/client/src/assets/icons/leaflet/marker-icon-fav-2x.png new file mode 100644 index 0000000..9e19d7d Binary files /dev/null and b/client/src/assets/icons/leaflet/marker-icon-fav-2x.png differ diff --git a/client/src/assets/icons/leaflet/marker-icon-fav.png b/client/src/assets/icons/leaflet/marker-icon-fav.png new file mode 100644 index 0000000..41a7a97 Binary files /dev/null and b/client/src/assets/icons/leaflet/marker-icon-fav.png differ diff --git a/client/src/components/AccountForm/index.js b/client/src/components/AccountForm/index.js index 3707b4b..a2ea864 100644 --- a/client/src/components/AccountForm/index.js +++ b/client/src/components/AccountForm/index.js @@ -3,6 +3,7 @@ import style from './style.css'; import { handleSubmit, clearForms, setStateUserOrRedirectToSignIn } from "../../js/utilities"; import { LOGIN_PATH, REGISTER_PATH, RESET_PATH } from '../../../config'; import linkState from "linkstate"; +import { route } from 'preact-router'; export default class AccountForm extends Component { constructor() { @@ -14,6 +15,7 @@ export default class AccountForm extends Component { validatePasswordMap: this.createValidatePasswordMap(), }; this.handleSubmit = handleSubmit.bind(this); + this.routeToRegister = this.routeToRegister.bind(this); } createMessageMap = () => { @@ -66,54 +68,86 @@ export default class AccountForm extends Component { } } + routeToRegister() { + route("/register", true); + } + render({path},{ form_message, user, name, email, password, new_password, confirm_password }) { //DEFAULT TO LOGIN_PATH let form_header = "Sign In"; let name_input = ""; - let email_input =
Email:
; - let password_input =
Password:
; + let email_input = + ; + let password_input = + ; let new_password_input = ""; let confirm_password_input = ""; - let submit_button =
; - let link1 =

register

; - let link2 = ""; - // let link2 =

forgot password?

; + let submit_button = +
+ +
+ OR +
+ +
; + let link2 =

forgot password?

; if(path === REGISTER_PATH){ form_header = "Register"; - name_input =
Name:
; - confirm_password_input =
Confirm password:
; - submit_button =
; - link1 =

Sign in

; + name_input = + ; + confirm_password_input = + ; + submit_button = ; + link2 = ""; } + if(path === RESET_PATH){ form_header = "Reset Password"; - email_input =
Email: {user ? user.email : ''}
; - password_input =
Current password:
; - new_password_input =
New password:
; - confirm_password_input =
Confirm new password:
; - submit_button =
;; - link1 = ""; + name_input = +
+

To change user info:

+ + +
; + email_input = ""; + password_input = +
+

To change password:

+ + + +
; + submit_button = ; + link2 = ""; } return ( -
-

{form_header}

+
+ Navi logo
{form_message}
-
-
+
+ {name_input} {email_input} {password_input} {new_password_input} {confirm_password_input} {submit_button} - {link1} - {link2}
+ {link2}
); } - // } } diff --git a/client/src/components/AccountForm/style.css b/client/src/components/AccountForm/style.css index caa7917..c16a15b 100644 --- a/client/src/components/AccountForm/style.css +++ b/client/src/components/AccountForm/style.css @@ -1,6 +1,79 @@ +.inherit { + display: inherit; + height: inherit; + width: inherit; +} -.signOut a{ - padding: 5px; - font-weight: bold; - color: green; -} \ No newline at end of file +.logo { + display: inherit; + margin: auto; + padding: 2vh; + width: 50%; +} + +.form { + display: grid; + padding: 0 5vh; + height: 70vh; + align-content: flex-start; +} + +.formChild { + display: block; + margin: 2vh auto; + width: 100%; + padding: 3vw; + border-color: lightslategray; + border-radius: 8px; + font-size: 1.2em; +} + +.strike { + display: block; + text-align: center; + overflow: hidden; + white-space: nowrap; +} + +.strike > span { + position: relative; + display: inline-block; + color: blue; +} + +.strike > span:before, +.strike > span:after { + content: ""; + position: absolute; + top: 50%; + width: 9999px; + height: 1px; + background: black; +} + +.strike > span:before { + right: 100%; + margin-right: 15px; +} + +.strike > span:after { + left: 100%; + margin-left: 15px; +} + +.link2 { + display: block; + position: fixed; + right: 5vw; + bottom: 1vh; + font-size: 1.1em; + color: blue; +} + +button { + background-color: #5A98A1; +} + +.regBtn { + background-color: #2c5b61; +} diff --git a/client/src/components/ForgotPasswordForm/index.js b/client/src/components/ForgotPasswordForm/index.js index 1a83853..80b1841 100644 --- a/client/src/components/ForgotPasswordForm/index.js +++ b/client/src/components/ForgotPasswordForm/index.js @@ -5,17 +5,16 @@ import style from './style'; export default class ForgotPasswordForm extends Component { render() { - return (
- -

We will send you a temporary password, enter your email address

-
- Email:
-
- + return ( +
+

Forgot Your Password?

+

Enter your email address below to reset your password.

+ + + - register -
- sign in +

Not a member? Sign up!

); } } diff --git a/client/src/components/ForgotPasswordForm/style.css b/client/src/components/ForgotPasswordForm/style.css index 7a68d82..4dd1649 100644 --- a/client/src/components/ForgotPasswordForm/style.css +++ b/client/src/components/ForgotPasswordForm/style.css @@ -1,5 +1,43 @@ .forgot-password-form { - padding: 56px 20px; - min-height: 100%; + width: inherit; + height: inherit; + margin-top: 15vh; +} + +.forgot-password-form > h2 { + margin: auto; + text-align: center; +} + +.forgot-password-form > p { + padding: 3vw; + text-align: center; + font-size: 1.1em; +} + +.form { + display: grid; + padding: 0 5vh; + align-content: flex-start; +} + +.formChild { + display: block; + margin: 2vh auto; width: 100%; + padding: 3vw; + border-color: lightslategray; + border-radius: 8px; + font-size: 1.2em; +} + +.register { + display: block; + position: fixed; + bottom: 5vh; +} + +.register > p { + text-align: center; } + diff --git a/client/src/components/LeafletOsmMap/MapPane.js b/client/src/components/LeafletOsmMap/MapPane.js index d259697..3a0412b 100644 --- a/client/src/components/LeafletOsmMap/MapPane.js +++ b/client/src/components/LeafletOsmMap/MapPane.js @@ -3,7 +3,7 @@ import { h, Component } from 'preact'; export default class MapPane extends Component { render() { const styles = { - height: this.props.height, // HAVE TO SET HEIGHT TO RENDER MAP + height: this.props.paneHeight, // HAVE TO SET HEIGHT TO RENDER MAP } return (
diff --git a/client/src/components/LeafletOsmMap/index.js b/client/src/components/LeafletOsmMap/index.js index 561aa19..29def97 100644 --- a/client/src/components/LeafletOsmMap/index.js +++ b/client/src/components/LeafletOsmMap/index.js @@ -1,9 +1,11 @@ import { h, Component } from "preact"; +import { route } from "preact-router"; import style from "./style"; import MapPane from './MapPane'; import Search from '../../components/Search'; import SearchResults from '../../components/SearchResults'; import { makeRequest } from '../../js/server-requests-utils'; +import {fetchAndDropUserPins, makePinMarkers, dropPin} from '../../js/saved-places'; /** * Leaflet related imports: leaflet, pouchdb module, and routing machine module @@ -17,11 +19,12 @@ import Routing from '../../../node_modules/leaflet-routing-machine/src/index.js' * TIle layer configuration and attribution constants */ const OSM_URL = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; -const OSM_ATTRIB = '© OpenStreetMap contributors'; +const OSM_ATTRIB = + '© OpenStreetMap contributors'; const OSM_TILE_LAYER = new L.TileLayer(OSM_URL, { - attribution: OSM_ATTRIB, - useCache: true, - crossOrigin: true, + attribution: OSM_ATTRIB, + useCache: true, + crossOrigin: true }); // redirect marker icon path to assets directory @@ -33,8 +36,8 @@ export default class LeafletOSMMap extends Component { this.state = { map: null, mapCenter: null, - userMarker: null, - } + userMarker: null + }; this.onLocationFound = this.onLocationFound.bind(this); this.onLocationError = this.onLocationError.bind(this); this.onMapClick = this.onMapClick.bind(this); @@ -44,51 +47,112 @@ export default class LeafletOSMMap extends Component { // initialize map container if null if (!this.state.map) { this.setState({ - map: L.map('map', { - zoomControl: false, - zoom: 16, - }), + map: L.map('map', { + zoomControl: false, + zoom: 16 + }) }); } // add zoom to bottom left - const zoomControl = L.control.zoom().setPosition('bottomleft').addTo(this.state.map); + const zoomControl = L.control + .zoom() + .setPosition('bottomleft') + .addTo(this.state.map); this.state.map.addLayer(OSM_TILE_LAYER); // attempt to get user's current location via device this.state.map.locate({ setView: true, - enableHighAccuracy: true, + enableHighAccuracy: true }); // configure map events this.state.map.on('locationfound', this.onLocationFound); this.state.map.on('locationerror', this.onLocationError); this.state.map.on('click', this.onMapClick); + + //once map is ready, drop pins (user=undefined --> default) + fetchAndDropUserPins(undefined, this.state.map, L); } /** * Handle leaflet map get device location event - * @param {*} event + * @param {*} event */ onLocationFound(event) { this.state.map.setZoom(16); const userMarker = L.circleMarker(event.latlng, { radius: 8, weight: 3, - fillColor: 'red', - }).addTo(this.state.map) + fillColor: 'red' + }) + .addTo(this.state.map) .bindPopup('You Are Here'); this.setState({ mapCenter: event.latlng, - userMarker: userMarker, + userMarker: userMarker + }); + + L.Control.Center = L.Control.extend({ + onAdd: map => { + const center = this.state.mapCenter; + const zoom = map.options.zoom; + const container = L.DomUtil.create( + 'div', + 'leaflet-bar leaflet-control leaflet-control-custom' + ); + + const controlText = L.DomUtil.create('div'); + controlText.style.color = 'rgb(25,25,25)'; + controlText.style.fontFamily = 'Roboto,Arial,sans-serif'; + controlText.style.fontSize = '16px'; + controlText.style.lineHeight = '38px'; + controlText.style.paddingLeft = '5px'; + controlText.style.paddingRight = '5px'; + controlText.innerHTML = 'Center Map'; + container.appendChild(controlText); + + L.DomEvent.disableClickPropagation(container); + + container.style.backgroundColor = '#fff'; + container.style.border = '2px solid #e7e7e7'; + container.style.borderRadius = '3px'; + container.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)'; + container.style.cursor = 'pointer'; + container.style.marginBottom = '22px'; + container.style.textAlign = 'center'; + container.title = 'Click to recenter the map'; + + L.DomEvent.on(container, 'mouseenter', function(e) { + e.target.style.background = '#e7e7e7'; + }); + + L.DomEvent.on(container, 'mouseleave', function(e) { + // e.target.style.color = '#ccc'; + e.target.style.background = '#fff'; + }); + L.DomEvent.on(container, 'click', function() { + map.setView(center, zoom); + }); + return container; + } }); + + L.control.center = function(opts) { + return new L.Control.Center(opts); + }; + + L.control + .center() + .setPosition('bottomright') + .addTo(this.state.map); } /** * Handle leaflet map get device location failure - * @param {*} event + * @param {*} event */ onLocationError(event) { // TODO - dummy message for now if don't have permission to get user @@ -99,31 +163,60 @@ export default class LeafletOSMMap extends Component { /** * On map click event, add a marker to the map at the clicked location. * - * @param {*} event + * @param {*} event */ onMapClick(event) { const droppedPin = L.marker(event.latlng, { draggable: true, - autoPan: true, + autoPan: true }).addTo(this.state.map); const container = L.DomUtil.create('div'); const saveBtn = createButton('Save', container); const deleteBtn = createButton('Remove', container); - - L.DomEvent.on(saveBtn, 'click', function() { + + L.DomEvent.on(saveBtn, 'click', function () { makeRequest('POST', 'savedPins', '', { lat: event.latlng.lat, lng: event.latlng.lng, }).then((response) => { - alert(`Succes: Saved pin at ${event.latlng} to db`); - console.log('Success - saved: ', response); + + //remove old icon + droppedPin.remove() + + //add a new one + const savedMarker = makePinMarkers([response.data.pin], L); + dropPin(savedMarker, event.target); + + // alert(`Succes: Saved pin at ${event.latlng} to db`); + }).catch((err) => { - alert('Error saving pin: ', err); - console.log('Error saving pin: ', err); + + switch (err.response.status){ + case 400: //duplicate pin + const origPin = err.response.data; + alert('This pin is already on your map.'); + /*TO DO: + Convert popup alert to toast message + */ + break; + case 403: //user not signed in + /*TO DO: + Convert to overlay/lightbox window to sign up form + */ + if (confirm("Would you like to sign in to save places?")) { + route('/signin', true); + } + + break; + default: + console.log(err); //error saving pin + + } + }) }); - + L.DomEvent.on(deleteBtn, 'click', function() { droppedPin.remove(); }); @@ -131,14 +224,13 @@ export default class LeafletOSMMap extends Component { droppedPin.bindPopup(container); } - render() { return (
- +
); } @@ -148,7 +240,7 @@ export default class LeafletOSMMap extends Component { // only needed for directions mode. // map.stopWatch(); this.state.map.remove(); - this.setState({map: null}); + this.setState({ map: null }); } } diff --git a/client/src/components/Logo/index.js b/client/src/components/Logo/index.js index b35b49a..0a2e047 100644 --- a/client/src/components/Logo/index.js +++ b/client/src/components/Logo/index.js @@ -8,7 +8,7 @@ export default class Logo extends Component { return (
-

Welcome to {APP_NAME}

+

Welcome!

); } diff --git a/client/src/components/Nav/index.js b/client/src/components/Nav/index.js index d283520..9ba4f62 100644 --- a/client/src/components/Nav/index.js +++ b/client/src/components/Nav/index.js @@ -1,41 +1,33 @@ import {h, Component} from 'preact'; +import { route } from 'preact-router'; import style from './style.css'; export default class Nav extends Component { + constructor(props) { + super(props); - componentDidMount() { - let navIcon = document.getElementsByClassName(style.navIcon)[0]; - let linkContainer = document.getElementsByClassName(style.linkContainer)[0]; - linkContainer.style.display = "none"; - navIcon.onclick = function (e) { - e.preventDefault(); - var x = linkContainer; - if (x.style.display === "block") { - x.style.display = "none"; - } else { - x.style.display = "block"; - } - return false; - } + this.routeToHome = this.routeToHome.bind(this); + this.routeToProfile = this.routeToProfile.bind(this); + } - let links = document.getElementsByClassName(style.link); - for (let link of links) { - link.onclick = function () { - linkContainer.style.display = "none"; - } - } - } + routeToHome() { + route('/', true); + }; + + routeToProfile() { + route('/profile', true); + }; render() { + const styles = { + height: this.props.navHeight, + } return ( -
- - +
+ Home screen icon + Profile page icon
); } diff --git a/client/src/components/Nav/style.css b/client/src/components/Nav/style.css index 0477ac8..50254af 100644 --- a/client/src/components/Nav/style.css +++ b/client/src/components/Nav/style.css @@ -1,26 +1,17 @@ .nav { width: 100%; - height: 5vh; + background: linear-gradient(to right, #5A98A1,#2c5b61); + display: block; } -.navIcon{ - height: 40px; - background-color:white; - position: relative; - top: 50%; - transform: translateY(-50%); - padding: 10px; +.profileIcon { + padding: 2vw; + height: inherit; + position: fixed; + right: 0px; } -.linkContainer{ - display: none; - background-color: white; - position: absolute; - top: 40px; - padding: 10px; - z-index:1; +.homeIcon { + padding: 2vw; + height: inherit; } - -.link{ - -} \ No newline at end of file diff --git a/client/src/components/SavedPinsCard/index.js b/client/src/components/SavedPinsCard/index.js new file mode 100644 index 0000000..1e7f808 --- /dev/null +++ b/client/src/components/SavedPinsCard/index.js @@ -0,0 +1,59 @@ +import {h, Component} from 'preact'; +import style from './style'; +import {makeRequest, token} from "../../js/server-requests-utils"; +import {SAVED_PINS_PATH} from '../../../config'; + +export default class SavedPinsCard extends Component { + constructor() { + super(); + this.state = { + user: {}, + savedPins: [], + }; + this.getSavedPins = this.getSavedPins.bind(this); + this.displayPins = this.displayPins.bind(this); + } + + componentWillMount() { + this.setState({user: this.props.user}); + this.getSavedPins(); + } + + displayPins(savedPins) { + let pins = []; + savedPins.map( pin => { + pins.push(
{pin.place_id}
); + }); + if(pins.length == 0) pins.push(
No saved pins
); + this.setState({pins: pins}); + } + + getSavedPins() { + makeRequest('GET', SAVED_PINS_PATH) + .then(function (response) { + this.displayPins(response.data.savedPins); + }.bind(this)) + .catch(function (error) { + if (error.response === undefined) { + this.setState({savedPins: error}); + } else { + this.setState({savedPins: error.response.data}); + } + }.bind(this)); + } + + render({}, {user, pins}) { + + + return ( +
+ + Saved Pins
+ + +
{pins}
+ +
+ ); + } +} diff --git a/client/src/components/SavedPinsCard/style.css b/client/src/components/SavedPinsCard/style.css new file mode 100644 index 0000000..1e4ce8b --- /dev/null +++ b/client/src/components/SavedPinsCard/style.css @@ -0,0 +1,21 @@ +.savedPinsCard { + border-radius: 3px; + border-width: 2px; + border-style: groove; + margin-bottom: 10px; + padding-left: 5px; +} + +#savedPinsCardTitle { + display: block; + font-size: 1em; + font-weight: bold; + text-align: center; + color: purple; + margin: 0 auto; +} + +.savedPinsContainer{ + text-align: center; +} + diff --git a/client/src/components/SearchHistoryCard/index.js b/client/src/components/SearchHistoryCard/index.js new file mode 100644 index 0000000..0434529 --- /dev/null +++ b/client/src/components/SearchHistoryCard/index.js @@ -0,0 +1,59 @@ +import {h, Component} from 'preact'; +import style from './style'; +import {makeRequest, token} from "../../js/server-requests-utils"; +import {SEARCH_HISTORY_PATH} from '../../../config'; + +export default class SearchHistoryCard extends Component { + constructor() { + super(); + this.state = { + user: {}, + searchHistory: [], + }; + this.getSearchHistory = this.getSearchHistory.bind(this); + this.displayHistory = this.displayHistory.bind(this); + } + + componentWillMount() { + this.setState({user: this.props.user}); + this.getSearchHistory(); + } + + displayHistory(searchHistory) { + let histories = []; + searchHistory.map( search => { + histories.push(
{search.query}
); + }); + if(histories.length == 0) histories.push(
No saved searches
); + this.setState({histories: histories}); + } + + getSearchHistory() { + makeRequest('GET', SEARCH_HISTORY_PATH) + .then(function (response) { + this.displayHistory(response.data.searchHistory); + }.bind(this)) + .catch(function (error) { + if (error.response === undefined) { + this.setState({searchHistory: error}); + } else { + this.setState({searchHistory: error.response.data}); + } + }.bind(this)); + } + + render({}, {user, histories}) { + + + return ( +
+ + Search History
+ + +
{histories}
+ +
+ ); + } +} diff --git a/client/src/components/SearchHistoryCard/style.css b/client/src/components/SearchHistoryCard/style.css new file mode 100644 index 0000000..006d3df --- /dev/null +++ b/client/src/components/SearchHistoryCard/style.css @@ -0,0 +1,21 @@ +.searchHistoryCard { + border-radius: 3px; + border-width: 2px; + border-style: groove; + margin-bottom: 10px; + padding-left: 5px; +} + +#searchHistoryCardTitle { + display: block; + font-size: 1em; + font-weight: bold; + text-align: center; + color: purple; + margin: 0 auto; +} + +.searchHistoriesContainer{ + text-align: center; +} + diff --git a/client/src/components/app.js b/client/src/components/app.js index 5d661f4..94a4e5a 100644 --- a/client/src/components/app.js +++ b/client/src/components/app.js @@ -19,10 +19,16 @@ import SignOut from '../routes/signout'; // import Register from '../routes/register'; import Settings from '../routes/settings'; -// import Home from 'async!../routes/home'; -// import Profile from 'async!../routes/profile'; +// Available screen real state after factoring space for navbar +const AVAIL_PANE_HEIGHT = screen.availHeight * 0.93; export default class App extends Component { + constructor() { + super(); + this.state = { + navbarHeight: screen.availHeight - AVAIL_PANE_HEIGHT + }; + } /** Gets fired when the route changes. * @param {Object} event "change" event from [preact-router](http://git.io/preact-router) * @param {string} event.url The newly routed URL @@ -34,24 +40,20 @@ export default class App extends Component { render() { return (
- - { ({ matches, path, url }) => matches && ( - - ) } - +
); diff --git a/client/src/js/saved-places.js b/client/src/js/saved-places.js new file mode 100644 index 0000000..4e9f1e5 --- /dev/null +++ b/client/src/js/saved-places.js @@ -0,0 +1,113 @@ +import {h, Component} from "preact"; +import {makeRequest} from "./server-requests-utils"; + + +/* Set all settings for saved place icon +Waiting for new icon for favorites +Full list of options: http://leafletjs.com/reference-1.3.0.html#icon + */ + +const FAV_MARKER_OPTIONS = { + iconUrl: '../../assets/icons/leaflet/marker-icon-fav-2x.png', + className: 'favorites', + iconSize: [25, 41], //native aspect ratio: [28, 41] +}; + + +/** + * Exported Functions + */ + +//Get and drop pins from any user on any map (all arguments are optional) +const fetchAndDropUserPins = (user_id, mapObj, L) => { + + getSavedPins(user_id) + .then(savedPins => { + + if (!savedPins) return; + + const pinMarkers = makePinMarkers(savedPins, L); + if (mapObj != null) dropPin(pinMarkers, mapObj); + + }) +} + +const getSavedPins = (user_id) => { + //GET 'search/savedpins/:user_id' + + return makeRequest('GET', 'savedPins', '', user_id) //pinsPromised + .then(res => res.data.savedPins) + .catch(err => { + // console.log(`Couldn't get the user pins: ${err}`); + }); + +} + +//This function generates markers and drops them on a map (if specified) +const makePinMarkers = (pinArray = [], L, markerOptions=FAV_MARKER_OPTIONS) => { + + const icon = L ? L.icon(FAV_MARKER_OPTIONS) : undefined; + + let pinMarkers = []; + + for (const pin of pinArray) { + //create marker for the pin and bind it ot the map + + let thisMarker = []; //the marker icon cannot be changed after the marker is created? + + if (icon) { //if (icon) thisMarker.icon = icon; doesn't work + thisMarker = L.marker([pin.lat, pin.lng], { + icon, + draggable: false, + autopan: true, + riseOnHover: true, + title: pin.place_id + }); + + } else { + thisMarker = L.marker([pin.lat, pin.lng], { + draggable: false, + autopan: true, + riseOnHover: true, + title: pin.place_id + }); + } + + thisMarker.data = pin; //save the db data to the marker + + pinMarkers.push(thisMarker); + } + + // console.log('User Pins added: ',pinMarkers) + return pinMarkers; + +} + +//This funciton drops pins on a map replacing pins if they already exist +const dropPin = (pinMarkers=[], mapObj=undefined) => { + + //if no map is defined, or there are no pins return + if ((mapObj == null) || (pinMarkers.length == 0)) return; + + /*TO DO?: + Do not allow duplication of pins, + Remove those that exist already on the old map + Hint: use sets + */ + + + for (let marker of pinMarkers){ + marker.addTo(mapObj); + setPinPopup(marker); + } +} + +/* TO DO + Create a custom container for each pin on click */ +const setPinPopup = (marker) => { + marker.bindPopup(marker.options.title); +} + + +//Export Statements +export {fetchAndDropUserPins, makePinMarkers, dropPin}; diff --git a/client/src/routes/account/index.js b/client/src/routes/account/index.js index 961a1ff..5ff98ff 100644 --- a/client/src/routes/account/index.js +++ b/client/src/routes/account/index.js @@ -27,11 +27,14 @@ export default class Account extends Component { ) } + + const styles = { + height: this.props.paneHeight + } + return ( -
-
- {renderedForm} -
+
+ {renderedForm}
); } diff --git a/client/src/routes/account/style.css b/client/src/routes/account/style.css index 9723eb7..fdcfad6 100644 --- a/client/src/routes/account/style.css +++ b/client/src/routes/account/style.css @@ -1,8 +1,12 @@ -.signin { - padding: 56px 20px; - min-height: 100%; - width: 100%; +.inherit { + height: inherit; + width: inherit; + display: inherit; } .main { + display: block; + width: 100vw; + position: fixed; + top: 7vh; } diff --git a/client/src/routes/home/index.js b/client/src/routes/home/index.js index 4f987e3..489df8d 100644 --- a/client/src/routes/home/index.js +++ b/client/src/routes/home/index.js @@ -1,13 +1,36 @@ import { h, Component } from 'preact'; import style from './style'; +import { route } from 'preact-router'; +import Search from '../../components/Search'; +import SearchResults from '../../components/SearchResults'; export default class Home extends Component { + constructor(props) { + super(props); + + this.routeToMap = this.routeToMap.bind(this); + } + + routeToMap() { + route('/maps', true); + } + render() { return (
-

What Can navi find for you today!

- - +
+

WELCOME TO

+ +
+
+

Where can we take you today?

+ + + +
+ where am I? +
); } diff --git a/client/src/routes/home/style.css b/client/src/routes/home/style.css index 727ea80..06dfee9 100644 --- a/client/src/routes/home/style.css +++ b/client/src/routes/home/style.css @@ -1,9 +1,48 @@ .home { - padding: 56px 20px; width: 100%; + display: block; } -button { - display: block; - width: 80%; +.welcome { + padding-top: 3vh; + display: inherit; +} + +.welcome > h2 { + text-align: center; + margin: 3vh auto auto; +} + +.welcome > img { + display: inherit; + margin: auto; + width: 50vw; +} + +.search { + padding-top: 5vh; + display: inherit; +} + +.search > p { + text-align: center; + font-size: 1.2em; +} + +.search > form { + position: static; +} + +.mapLink { + position: fixed; + right: 15vw; + bottom: 5vh; +} + +.pin { + width: 9vw; + height: 9vh; + position: fixed; + right: 5vw; + bottom: 3vh; } diff --git a/client/src/routes/maps/index.js b/client/src/routes/maps/index.js index 16b5a50..2c71388 100644 --- a/client/src/routes/maps/index.js +++ b/client/src/routes/maps/index.js @@ -6,7 +6,7 @@ export default class MapExplorer extends Component { render() { return (
- +
); } diff --git a/client/src/routes/maps/style.css b/client/src/routes/maps/style.css index 9070e0b..5008164 100644 --- a/client/src/routes/maps/style.css +++ b/client/src/routes/maps/style.css @@ -1,5 +1,4 @@ .maps { width: 100vw; - height: 95vh; display: block; -} \ No newline at end of file +} diff --git a/client/src/routes/profile/index.js b/client/src/routes/profile/index.js index 61cdc78..3f302af 100644 --- a/client/src/routes/profile/index.js +++ b/client/src/routes/profile/index.js @@ -5,6 +5,8 @@ import Logo from '../../components/Logo'; import ProfileCard from '../../components/ProfileCard'; import ProfileEditForm from '../../components/ProfileEditForm'; import ProfileSettingsForm from '../../components/ProfileSettingsForm'; +import SavedPinsCard from '../../components/SavedPinsCard'; +import SearchHistoryCard from '../../components/SearchHistoryCard'; import {setStateUserOrRedirectToSignIn} from "../../js/utilities"; export default class Profile extends Component { @@ -23,11 +25,21 @@ render({success}, {user, time}) { return (
- {success} - - - - +
{success}
+
+
+ User Info + Name: {user.name}
+ Email: {user.email}
+
+
+ + +
); } diff --git a/client/src/routes/profile/style.css b/client/src/routes/profile/style.css index cc0c206..a7c1845 100644 --- a/client/src/routes/profile/style.css +++ b/client/src/routes/profile/style.css @@ -1,13 +1,31 @@ .profile { - padding: 56px 20px; - min-height: 100%; + padding: 5vh 5vw; width: 100%; } +.successMessage{ + text-align: center; + font-weight: bold; + padding: 5px; +} + .link { padding: 5px; } .link a { text-decoration: none; -} \ No newline at end of file +} + +.links_container{ + text-align: center; +} + +form { + text-align: center; + margin-bottom: 2vh; +} + +fieldset { + border-radius: 3px; +} diff --git a/controllers/saved-pins-controller.js b/controllers/saved-pins-controller.js index b346151..f53e0a1 100644 --- a/controllers/saved-pins-controller.js +++ b/controllers/saved-pins-controller.js @@ -60,18 +60,38 @@ exports.getSavedPinsById = (appReq, appRes) => { * @param {string} appReq.body.place_id - name of pin location */ exports.postSavedPins = (appReq, appRes) => { - const savedPin = new SavedPins({ - lat: appReq.body.lat, - lng: appReq.body.lng, - place_id: appReq.body.place_id, - user: appReq.userId, // authenticated user's id - }); - savedPin.save().then((pin) => { - appRes.send({ pin }); - }, (e) => { - appRes.status(500).send(e); - }); + //ensure not duplicate first + SavedPins.find({ user: appReq.userId }) + .then(savedPins => { + //check to see if the pin already exists + return savedPins.filter(pin => { + return (pin.lat == appReq.body.lat) && (pin.lng == appReq.body.lng) + }); + + }) + .then(duplicatePins => { + + if (duplicatePins.length > 0) { + appRes.status(400).send({ duplicatePin: duplicatePins, message: 'Duplicate found. Pin not saved.' }); + return + } + + const newPin = new SavedPins({ + lat: appReq.body.lat, + lng: appReq.body.lng, + place_id: appReq.body.place_id, + user: appReq.userId, // authenticated user's id + }); + + newPin.save().then((pin) => { + appRes.send({ pin }); + }, (e) => { + appRes.status(500).send(e); + }); + + + }) }; /**