diff --git a/.github/workflows/mainToHeroku.yml b/.github/workflows/mainToHeroku.yml index e45328732..1d1a588db 100644 --- a/.github/workflows/mainToHeroku.yml +++ b/.github/workflows/mainToHeroku.yml @@ -9,8 +9,8 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: akhileshns/heroku-deploy@v3.8.9 # This is the action + - uses: actions/checkout@v3 + - uses: akhileshns/heroku-deploy@v3.12.14 # This is the action with: heroku_api_key: ${{secrets.HEROKU_API_KEY}} heroku_app_name: "foodoasis" #Must be unique in Heroku diff --git a/client/package-lock.json b/client/package-lock.json index f2ccb908b..2f894f2bf 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "foodoasis-client", - "version": "1.0.77", + "version": "1.0.79", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "foodoasis-client", - "version": "1.0.77", + "version": "1.0.79", "license": "GPL-2.0", "dependencies": { "@craco/craco": "^7.0.0", @@ -18,17 +18,19 @@ "@mui/material": "^5.11.1", "@mui/x-data-grid": "^5.17.20", "@mui/x-date-pickers": "^6.8.0", + "@types/mapbox-gl": "^3.1.0", "axios": "^1.6.7", "dayjs": "^1.11.7", "debounce-fn": "^5.0.0", "formik": "^2.2.9", "js-file-download": "^0.4.12", + "mapbox-gl": "^3.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-draggable": "^4.4.6", "react-gtm-module": "^2.0.11", "react-helmet-async": "^1.3.0", - "react-map-gl": "^6.1.17", + "react-map-gl": "^7.1.7", "react-router-dom": "^6.10.0", "react-scripts": "^5.0.1", "react-virtuoso": "^4.1.0", @@ -3177,9 +3179,9 @@ } }, "node_modules/@mapbox/mapbox-gl-supported": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz", - "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-3.0.0.tgz", + "integrity": "sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg==" }, "node_modules/@mapbox/point-geometry": { "version": "0.1.0", @@ -3223,13 +3225,22 @@ "node": ">=6.0.0" } }, - "node_modules/@math.gl/web-mercator": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@math.gl/web-mercator/-/web-mercator-3.6.3.tgz", - "integrity": "sha512-UVrkSOs02YLehKaehrxhAejYMurehIHPfFQvPFZmdJHglHOU4V2cCUApTVEwOksvCp161ypEqVp+9H6mGhTTcw==", + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "19.3.3", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz", + "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==", "dependencies": { - "@babel/runtime": "^7.12.0", - "gl-matrix": "^3.4.0" + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^3.0.0", + "minimist": "^1.2.8", + "rw": "^1.3.3", + "sort-object": "^3.0.3" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" } }, "node_modules/@mui/base": { @@ -4004,11 +4015,6 @@ "@types/node": "*" } }, - "node_modules/@types/hammerjs": { - "version": "2.0.41", - "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz", - "integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==" - }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -4059,9 +4065,9 @@ "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==" }, "node_modules/@types/mapbox-gl": { - "version": "2.7.10", - "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-2.7.10.tgz", - "integrity": "sha512-nMVEcu9bAcenvx6oPWubQSPevsekByjOfKjlkr+8P91vawtkxTnopDoXXq1Qn/f4cg3zt0Z2W9DVsVsKRNXJTw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.1.0.tgz", + "integrity": "sha512-hI6cQDjw1bkJw7MC/eHMqq5TWUamLwsujnUUeiIX2KDRjxRNSYMjnHz07+LATz9I9XIsKumOtUz4gRYnZOJ/FA==", "dependencies": { "@types/geojson": "*" } @@ -4839,6 +4845,14 @@ "deep-equal": "^2.0.5" } }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", @@ -4974,6 +4988,14 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -5546,6 +5568,23 @@ "node": ">= 0.8" } }, + "node_modules/bytewise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", + "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==", + "dependencies": { + "bytewise-core": "^1.2.2", + "typewise": "^1.0.3" + } + }, + "node_modules/bytewise-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", + "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==", + "dependencies": { + "typewise-core": "^1.2" + } + }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -5667,6 +5706,11 @@ "node": ">=10" } }, + "node_modules/cheap-ruler": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/cheap-ruler/-/cheap-ruler-3.0.2.tgz", + "integrity": "sha512-02T332h1/HTN6cDSufLP8x4JzDs2+VC+8qZ/N0kWIVPyc2xUkWwWh3B2fJxR7raXkL4Mq7k554mfuM9ofv/vGg==" + }, "node_modules/check-types": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz", @@ -8047,6 +8091,17 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8690,6 +8745,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/gl-matrix": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", @@ -8846,14 +8909,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hammerjs": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", - "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -9481,6 +9536,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -11937,6 +12000,11 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, + "node_modules/json-stringify-pretty-compact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", + "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -11980,9 +12048,9 @@ } }, "node_modules/kdbush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", - "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==" }, "node_modules/kind-of": { "version": "6.0.3", @@ -12096,6 +12164,11 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -12197,30 +12270,35 @@ } }, "node_modules/mapbox-gl": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-2.13.0.tgz", - "integrity": "sha512-G8pU1/I9HC7xNbhKPKFtRkdUDkWJBNbYPMeRjBig3lPaYtvHPIaFmXMR6BDyZ/gnwodElrwMZGdGsoH8kecX8w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.2.0.tgz", + "integrity": "sha512-v8S7x+wTr35kJ9nqzgn/VPiSFZxBkyQhwCk9bdyiFHVwCukNGG3LXt03FoaHHTsOuB9JWenWE96k0Uw+HGMZ8w==", "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", - "@mapbox/mapbox-gl-supported": "^2.0.1", + "@mapbox/mapbox-gl-supported": "^3.0.0", "@mapbox/point-geometry": "^0.1.0", "@mapbox/tiny-sdf": "^2.0.6", "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^1.3.1", "@mapbox/whoots-js": "^3.1.0", + "cheap-ruler": "^3.0.1", "csscolorparser": "~1.0.3", "earcut": "^2.2.4", "geojson-vt": "^3.2.1", "gl-matrix": "^3.4.3", "grid-index": "^1.1.0", + "kdbush": "^4.0.1", + "lodash.clonedeep": "^4.5.0", "murmurhash-js": "^1.0.0", "pbf": "^3.2.1", "potpack": "^2.0.0", "quickselect": "^2.0.0", "rw": "^1.3.3", - "supercluster": "^7.1.5", + "serialize-to-js": "^3.1.2", + "supercluster": "^8.0.0", "tinyqueue": "^2.0.3", + "tweakpane": "^4.0.3", "vt-pbf": "^3.1.3" } }, @@ -12413,19 +12491,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mjolnir.js": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/mjolnir.js/-/mjolnir.js-2.7.1.tgz", - "integrity": "sha512-72BeUWgTv2cj5aZQKpwL8caNUFhXZ9bDm1hxpNj70XJQ62IBnTZmtv/WPxJvtaVNhzNo+D2U8O6ryNI0zImYcw==", - "dependencies": { - "@types/hammerjs": "^2.0.41", - "hammerjs": "^2.0.8" - }, - "engines": { - "node": ">= 4", - "npm": ">= 3" - } - }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -14792,25 +14857,26 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-map-gl": { - "version": "6.1.21", - "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-6.1.21.tgz", - "integrity": "sha512-7ENXxAeYaI4dhol5bir3iK6TeR9teA3MF0WH6VIhmkMRdYY3lgA4t1GzDh2BwpSO340Ngw+5mC0nTsc6gd1O4w==", - "dependencies": { - "@babel/runtime": "^7.0.0", - "@types/geojson": "^7946.0.7", - "@types/mapbox-gl": "^2.0.3", - "mapbox-gl": "^2.3.0", - "mjolnir.js": "^2.5.0", - "prop-types": "^15.7.2", - "resize-observer-polyfill": "^1.5.1", - "viewport-mercator-project": "^7.0.4" - }, - "engines": { - "node": ">= 4", - "npm": ">= 3" + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-7.1.7.tgz", + "integrity": "sha512-mwjc0obkBJOXCcoXQr3VoLqmqwo9vS4bXfbGsdxXzEgVCv/PM0v+1QggL7W0d/ccIy+VCjbXNlGij+PENz6VNg==", + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^19.2.1", + "@types/mapbox-gl": ">=1.0.0" }, "peerDependencies": { - "react": ">=16.3.0" + "mapbox-gl": ">=1.13.0", + "maplibre-gl": ">=1.13.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + }, + "maplibre-gl": { + "optional": true + } } }, "node_modules/react-router": { @@ -15578,11 +15644,6 @@ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" }, - "node_modules/resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" - }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -16036,6 +16097,14 @@ "randombytes": "^2.1.0" } }, + "node_modules/serialize-to-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/serialize-to-js/-/serialize-to-js-3.1.2.tgz", + "integrity": "sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", @@ -16148,6 +16217,20 @@ "node": ">= 0.4" } }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -16237,6 +16320,38 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sort-asc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz", + "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-desc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz", + "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-object": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz", + "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==", + "dependencies": { + "bytewise": "^1.1.0", + "get-value": "^2.0.2", + "is-extendable": "^0.1.1", + "sort-asc": "^0.2.0", + "sort-desc": "^0.2.0", + "union-value": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -16329,6 +16444,40 @@ "wbuf": "^1.7.3" } }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -16581,11 +16730,11 @@ "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" }, "node_modules/supercluster": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", - "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", "dependencies": { - "kdbush": "^3.0.0" + "kdbush": "^4.0.2" } }, "node_modules/supports-color": { @@ -17044,6 +17193,14 @@ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, + "node_modules/tweakpane": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-4.0.3.tgz", + "integrity": "sha512-BlcWOAe8oe4c+k9pmLBARGdWB6MVZMszayekkixQXTgkxTaYoTUpHpwVEp+3HkoamZkomodpbBf0CkguIHTgLg==", + "funding": { + "url": "https://github.com/sponsors/cocopon" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -17167,6 +17324,19 @@ "node": ">=4.2.0" } }, + "node_modules/typewise": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", + "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==", + "dependencies": { + "typewise-core": "^1.2.0" + } + }, + "node_modules/typewise-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", + "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==" + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -17217,6 +17387,20 @@ "node": ">=4" } }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -17378,14 +17562,6 @@ "node": ">= 0.8" } }, - "node_modules/viewport-mercator-project": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/viewport-mercator-project/-/viewport-mercator-project-7.0.4.tgz", - "integrity": "sha512-0jzpL6pIMocCKWg1C3mqi/N4UPgZC3FzwghEm1H+XsUo8hNZAyJc3QR7YqC816ibOR8aWT5pCsV+gCu8/BMJgg==", - "dependencies": { - "@math.gl/web-mercator": "^3.5.5" - } - }, "node_modules/vt-pbf": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", diff --git a/client/package.json b/client/package.json index 1f7c0fd00..3d939c5b9 100644 --- a/client/package.json +++ b/client/package.json @@ -1,7 +1,7 @@ { "name": "foodoasis-client", "description": "React Client for Food Oasis", - "version": "1.0.79", + "version": "1.0.80", "author": "Hack for LA", "license": "GPL-2.0", "private": true, @@ -25,17 +25,19 @@ "@mui/material": "^5.11.1", "@mui/x-data-grid": "^5.17.20", "@mui/x-date-pickers": "^6.8.0", + "@types/mapbox-gl": "^3.1.0", "axios": "^1.6.7", "dayjs": "^1.11.7", "debounce-fn": "^5.0.0", "formik": "^2.2.9", "js-file-download": "^0.4.12", + "mapbox-gl": "^3.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-draggable": "^4.4.6", "react-gtm-module": "^2.0.11", "react-helmet-async": "^1.3.0", - "react-map-gl": "^6.1.17", + "react-map-gl": "^7.1.7", "react-router-dom": "^6.10.0", "react-scripts": "^5.0.1", "react-virtuoso": "^4.1.0", diff --git a/client/src/App.js b/client/src/App.js index 84b3c4bb8..f4b315468 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -61,6 +61,7 @@ const About = lazy(() => import("./components/About")); const Faq = lazy(() => import("./components/Faq")); const Contact = lazy(() => import("./components/StaticPages/Contact")); const MuiDemo = lazy(() => import("./components/MuiDemo/MuiDemo")); +const Features = lazy(() => import("./components/Admin/Features")); function App() { useEffect(() => { @@ -170,7 +171,6 @@ function App() { } /> - } /> + +
+ +
+ + } + /> ({ + loginId: user.login_id, + firstName: user.first_name, + lastName: user.last_name, + email: user.email, + featureToLoginId: user.ftl_id, + })), + }; +} + +const featureFormValidationSchema = Yup.object({ + name: Yup.string().trim().required("Name is required"), +}); +const userFormValidationSchema = Yup.object({ + email: Yup.string() + .email("Invalid email address") + .required("User email is required"), +}); +const Features = () => { + const [selectedRowId, setSelectedRowId] = useState(null); + const [rows, setRows] = useState([]); + const [selectedFeatureName, setSelectedFeatureName] = useState(""); + const [selectedFeatureId, setSelectedFeatureId] = useState(null); + const [featureModalOpen, setFeatureModalOpen] = useState(false); + const [userModalOpen, setUserModalOpen] = useState(false); + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(10); + + const { + data: featureToLoginData, + loading: featureToLoginLoading, + refetch: featureToLoginRefetch, + } = useFeatureToLogin(); + + useEffect(() => { + if (featureToLoginData) { + const groupedByFeatureName = featureToLoginData.reduce((acc, curr) => { + acc[curr.feature_name] = [...(acc[curr.feature_name] || []), curr]; + return acc; + }, {}); + const newRows = Object.entries(groupedByFeatureName).map( + ([featureName, users]) => { + const featureId = users[0]?.feature_id; + return createData(featureName, users, featureId); + } + ); + setRows(newRows); + } + }, [featureToLoginData]); + + const handleFeatureModalOpen = () => setFeatureModalOpen(true); + const handleModalClose = () => { + setFeatureModalOpen(false); + featureFormik.resetForm(); + }; + const handleUserModalOpen = (featureName, featureId) => { + setSelectedFeatureName(featureName); + setSelectedFeatureId(featureId); + setUserModalOpen(true); + }; + const handleUserModalClose = () => { + setUserModalOpen(false); + userFormik.resetForm(); + }; + const handleRowClick = (rowName) => { + if (selectedRowId === rowName) { + setSelectedRowId(null); + } else { + setSelectedRowId(rowName); + } + }; + const featureFormik = useFormik({ + initialValues: { + name: "", + }, + validationSchema: featureFormValidationSchema, + onSubmit: async (values, { resetForm, setSubmitting }) => { + await featureService.post(values); + featureToLoginRefetch(); + resetForm(); + setSubmitting(false); + handleModalClose(); + }, + }); + const userFormik = useFormik({ + initialValues: { + email: "", + }, + validationSchema: userFormValidationSchema, + onSubmit: async (values, { resetForm, setSubmitting, setFieldError }) => { + try { + const accountResponse = await accountService.getByEmail(values.email); + const loginId = accountResponse.data.data.id; + const featureId = selectedFeatureId; + await featureToLoginService.addUserToFeature(featureId, loginId); + resetForm(); + setSubmitting(false); + handleUserModalClose(); + featureToLoginRefetch(); + } catch (error) { + if (error.response) { + switch (error.response.status) { + case 404: + setFieldError("email", "No user found with this email"); + break; + case 409: + setFieldError( + "email", + "The user has already been granted access to this feature." + ); + break; + default: + setFieldError("email", "An error occurred. Please try again."); + break; + } + } + } + }, + }); + + const handleChangePage = (newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event) => { + setRowsPerPage(+event.target.value); + setPage(0); + }; + return ( + + + + Feature to Logins + + + + {featureToLoginLoading ? ( + + + + ) : ( + + + + + + Feature ID + Feature Name + + + + {rows.map((row, index) => ( + + handleRowClick(row.name)} + sx={{ + "& > *": { borderBottom: "unset", cursor: "pointer" }, + }} + hover + > + + handleRowClick(row.name)} + > + {selectedRowId === row.name ? ( + + ) : ( + + )} + + + + + {row.id} + + + {row.name} + + + + + + + + + Users + + + + handleUserModalOpen(row.name, row.id) + } + > + + + + +
+ + + Login ID + First Name + Last Name + Email + + + + {row.history.map((historyRow) => ( + + + {historyRow.loginId} + + {historyRow.firstName} + {historyRow.lastName} + {historyRow.email} + + {historyRow.featureToLoginId ? ( + { + try { + await featureToLoginService.removeUserFromFeature( + historyRow.featureToLoginId + ); + featureToLoginRefetch(); + } catch (error) { + console.error( + "Failed to remove user from feature:", + error + ); + } + }} + > + + + ) : null} + + + ))} + +
+ + + + + + ))} + + +
+ )} + + + + + Add a new feature + +
+ + + {featureFormik.errors.name && ( + + {featureFormik.errors.name} + + )} + + + + + +
+
+
+ + + + + Add a user to {selectedFeatureName} + +
+ + {/* + + + + +
+
+
+ +
+ ); +}; +export default Features; diff --git a/client/src/components/FoodSeeker/SearchResults/ResultsFilters/ResultsFilters.js b/client/src/components/FoodSeeker/SearchResults/ResultsFilters/ResultsFilters.js index aff414951..672063084 100644 --- a/client/src/components/FoodSeeker/SearchResults/ResultsFilters/ResultsFilters.js +++ b/client/src/components/FoodSeeker/SearchResults/ResultsFilters/ResultsFilters.js @@ -59,6 +59,7 @@ const ResultsFilters = ({ borderTop: "1px solid lightgray", borderBottom: "1px solid lightgray", padding: "0.5rem 0", + zIndex: 2, }} > { +const ResultsList = ({ stakeholders, loading, handleReset, handleFlyTo }) => { const selectedOrganization = useSelectedOrganization(); const { isDesktop } = useBreakpoints(); useEffect(() => { @@ -83,17 +83,28 @@ const ResultsList = ({ stakeholders, loading, handleReset }) => { ( - - - - )} + itemContent={(index) => { + const stakeholder = stakeholders[index]; + return ( + + + handleFlyTo({ + longitude: stakeholder.longitude, + latitude: stakeholder.latitude, + }) + } + /> + + ); + }} /> @@ -106,6 +117,7 @@ ResultsList.propTypes = { stakeholders: PropTypes.arrayOf(PropTypes.object), status: PropTypes.string, handleReset: PropTypes.func, + handleFlyTo: PropTypes.func, }; export default ResultsList; diff --git a/client/src/components/FoodSeeker/SearchResults/ResultsMap/ResultsMap.js b/client/src/components/FoodSeeker/SearchResults/ResultsMap/ResultsMap.js index 6f98e832e..95b3e501f 100644 --- a/client/src/components/FoodSeeker/SearchResults/ResultsMap/ResultsMap.js +++ b/client/src/components/FoodSeeker/SearchResults/ResultsMap/ResultsMap.js @@ -20,7 +20,6 @@ import { defaultViewport } from "helpers/Configuration"; import useBreakpoints from "hooks/useBreakpoints"; import useFeatureFlag from "hooks/useFeatureFlag"; import "mapbox-gl/dist/mapbox-gl.css"; -import ReactMapGL, * as Map from "react-map-gl"; import { useLocation, useNavigate } from "react-router-dom"; import * as analytics from "services/analytics"; import { @@ -30,6 +29,7 @@ import { useSearchCoordinates, useSelectedOrganization, useUserCoordinates, + useListPanel, } from "../../../../appReducer"; import AdvancedFilters from "../AdvancedFilters/AdvancedFilters"; import { @@ -39,27 +39,30 @@ import { useMarkersGeojson, } from "./MarkerHelpers"; import { regionBorderStyle, regionFillStyle } from "./RegionHelpers"; +import Map, { Marker, Source, Layer, NavigationControl } from "react-map-gl"; const ResultsMap = ( - { stakeholders, categoryIds, toggleCategory, loading, searchMapArea }, + { stakeholders, categoryIds, toggleCategory, loading }, ref ) => { const mapRef = useRef(); const [markersLoaded, setMarkersLoaded] = useState(false); + const [cursor, setCursor] = useState("auto"); const searchCoordinates = useSearchCoordinates(); const selectedOrganization = useSelectedOrganization(); const navigate = useNavigate(); const location = useLocation(); const { isMobile } = useBreakpoints(); + const isListPanelOpen = useListPanel(); const longitude = searchCoordinates?.longitude || selectedOrganization?.longitude || DEFAULT_COORDINATES.longitude; const latitude = - searchCoordinates?.latitude || - selectedOrganization?.latitude || - DEFAULT_COORDINATES.latitude; + searchCoordinates?.latitude || selectedOrganization?.latitude || isMobile + ? DEFAULT_COORDINATES.latitude - 0.06 + : DEFAULT_COORDINATES.latitude; const userCoordinates = useUserCoordinates(); const [viewport, setViewport] = useState({ latitude, @@ -72,6 +75,10 @@ const ResultsMap = ( const startIconCoordinates = searchCoordinates || userCoordinates; const hasAdvancedFilterFeatureFlag = useFeatureFlag("advancedFilter"); + const onMouseEnter = useCallback(() => setCursor("pointer"), []); + const onMouseLeave = useCallback(() => setCursor("auto"), []); + const [interactiveLayerIds, setInteractiveLayerIds] = useState(["nonexist"]); + useEffect(() => { analytics.postEvent("showMap"); }, []); @@ -79,51 +86,47 @@ const ResultsMap = ( useEffect(() => { setViewport((viewport) => ({ ...viewport, - latitude, + latitude: latitude, longitude, })); - }, [searchCoordinates, longitude, latitude]); + }, [searchCoordinates, longitude, latitude, isMobile]); const onLoad = useCallback(async () => { const map = mapRef.current.getMap(); await loadMarkerIcons(map); setMarkersLoaded(true); + setInteractiveLayerIds([MARKERS_LAYER_ID]); }, []); - const onClick = useCallback( - (e) => { - if (!e.features || !e.features.length) { - dispatch({ type: "RESET_SELECTED_ORGANIZATION" }); - } else if (stakeholders) { - const { id } = e.features[0]; - const selectedOrganization = stakeholders.find((sh) => sh.id === id); - dispatch({ - type: "SELECTED_ORGANIZATION_UPDATED", - organization: selectedOrganization, - }); - analytics.postEvent("selectOrganization", { - id: selectedOrganization.id, - name: selectedOrganization.name, - }); - - //Update url history - const name = selectedOrganization.name - .toLowerCase() - .replaceAll(" ", "_"); - navigate( - `${location.pathname}?latitude=${selectedOrganization.latitude}&longitude=${selectedOrganization.longitude}&org=${name}&id=${selectedOrganization.id}` - ); - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [stakeholders, dispatch] - ); + const onClick = (e) => { + mapRef.current?.flyTo({ + center: [ + isListPanelOpen ? e.lngLat.lng - 0.08 : e.lngLat.lng, + isMobile ? e.lngLat.lat - 0.06 : e.lngLat.lat, + ], + duration: 2000, + }); + if (!e.features || !e.features.length) { + dispatch({ type: "RESET_SELECTED_ORGANIZATION" }); + } else if (stakeholders) { + const { id } = e.features[0]; + const selectedOrganization = stakeholders.find((sh) => sh.id === id); + dispatch({ + type: "SELECTED_ORGANIZATION_UPDATED", + organization: selectedOrganization, + }); + analytics.postEvent("selectOrganization", { + id: selectedOrganization.id, + name: selectedOrganization.name, + }); - const interactiveLayerIds = markersLoaded ? [MARKERS_LAYER_ID] : undefined; - - const getCursor = useCallback(({ isHovering, isDragging }) => { - return isDragging ? "grabbing" : isHovering ? "pointer" : "grab"; - }, []); + //Update url history + const name = selectedOrganization.name.toLowerCase().replaceAll(" ", "_"); + navigate( + `${location.pathname}?latitude=${selectedOrganization.latitude}&longitude=${selectedOrganization.longitude}&org=${name}&id=${selectedOrganization.id}` + ); + } + }; const markersGeojson = useMarkersGeojson({ stakeholders, @@ -146,28 +149,41 @@ const ResultsMap = ( dimensions: { width, height }, }; }, + flyTo: ({ latitude, longitude }) => { + mapRef.current?.flyTo({ + center: [ + isListPanelOpen ? longitude - 0.08 : longitude, + isMobile ? latitude - 0.06 : latitude, + ], + duration: 2000, + }); + }, }), - [] + [isMobile, isListPanelOpen] ); return (
- setViewport(e.viewState)} + mapboxAccessToken={process.env.REACT_APP_MAPBOX_ACCESS_TOKEN} + mapStyle={MAPBOX_STYLE} + draggable={true} onLoad={onLoad} + interactive={true} onClick={onClick} interactiveLayerIds={interactiveLayerIds} - getCursor={getCursor} - width="100%" - height="100%" - style={{ position: "relative" }} + cursor={cursor} + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} > + {!isMobile && ( + + )} {startIconCoordinates && ( - - - )} - {!isMobile && ( - + )} {markersLoaded && ( - - - + + + )} {regionGeoJSON && ( - - - - + + + + )} - - - {!loading && hasAdvancedFilterFeatureFlag && ( + + {!loading && hasAdvancedFilterFeatureFlag && isMobile && ( + - )} - + + )}
); }; @@ -228,7 +238,6 @@ ResultsMap.propTypes = { stakeholders: PropTypes.arrayOf(PropTypes.object), categoryIds: PropTypes.any, loading: PropTypes.bool, - searchMapArea: PropTypes.any, }; const StartIcon = () => { diff --git a/client/src/components/FoodSeeker/SearchResults/SearchResults.js b/client/src/components/FoodSeeker/SearchResults/SearchResults.js index e4694ba47..3b19f248a 100644 --- a/client/src/components/FoodSeeker/SearchResults/SearchResults.js +++ b/client/src/components/FoodSeeker/SearchResults/SearchResults.js @@ -75,7 +75,7 @@ const SearchResults = () => { useEffect(() => { if (!location.search) { - dispatch({ type: "RESET_SELECTED_ORGANIZATION", organization: null }); + dispatch({ type: "RESET_SELECTED_ORGANIZATION" }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [location.search]); @@ -95,6 +95,13 @@ const SearchResults = () => { dispatch({ type: "SEARCH_COORDINATES_UPDATED", coordinates: center }); }, [dispatch]); + const flyTo = useCallback(({ longitude, latitude }) => { + mapRef.current?.flyTo({ + longitude, + latitude, + }); + }, []); + const resetOrigin = useCallback(() => { dispatch({ type: "RESET_COORDINATES" }); }, [dispatch]); @@ -129,6 +136,7 @@ const SearchResults = () => { stakeholders={stakeholders || []} loading={loading} handleReset={resetOrigin} + handleFlyTo={flyTo} /> ); diff --git a/client/src/components/FoodSeeker/SearchResults/StakeholderDetails/StakeholderDetails.js b/client/src/components/FoodSeeker/SearchResults/StakeholderDetails/StakeholderDetails.js index f99f96e82..934072045 100644 --- a/client/src/components/FoodSeeker/SearchResults/StakeholderDetails/StakeholderDetails.js +++ b/client/src/components/FoodSeeker/SearchResults/StakeholderDetails/StakeholderDetails.js @@ -202,7 +202,7 @@ const StakeholderDetails = () => { stakeholder={selectedOrganization} setToast={setToast} /> - + { return minutesToClosing <= minutesToCloseFlag; }; -const StakeholderPreview = ({ stakeholder }) => { +const StakeholderPreview = ({ stakeholder, handleFlyTo }) => { const dispatch = useAppDispatch(); const searchCoordinates = useSearchCoordinates(); const userCoordinates = useUserCoordinates(); @@ -125,6 +125,7 @@ const StakeholderPreview = ({ stakeholder }) => { const location = useLocation(); const handleSelectOrganization = (organization) => { + handleFlyTo(); dispatch({ type: "SELECTED_ORGANIZATION_UPDATED", organization }); analytics.postEvent("selectOrganization", { id: organization.id, @@ -317,6 +318,7 @@ const StakeholderPreview = ({ stakeholder }) => { StakeholderPreview.propTypes = { stakeholder: PropTypes.object.isRequired, + handleFlyTo: PropTypes.func, }; export default StakeholderPreview; diff --git a/client/src/components/FoodSeeker/SearchResults/layouts/Desktop.js b/client/src/components/FoodSeeker/SearchResults/layouts/Desktop.js index 7f925512a..2f3b24c52 100644 --- a/client/src/components/FoodSeeker/SearchResults/layouts/Desktop.js +++ b/client/src/components/FoodSeeker/SearchResults/layouts/Desktop.js @@ -1,8 +1,31 @@ -import { Box, styled } from "@mui/material"; -import { useFilterPanel } from "../../../../appReducer"; +import { + Stack, + Box, + Grid, + styled, + Tooltip, + tooltipClasses, +} from "@mui/material"; +import { + useFilterPanel, + useListPanel, + useAppDispatch, +} from "../../../../appReducer"; +import * as React from "react"; +import DrawerLeftArrowButton from "../../../../icons/DrawerLeftArrowButton"; +import DrawerRightArrowButton from "../../../../icons/DrawerRightArrowButton"; +import useCategoryIds from "hooks/useCategoryIds"; +import AdvancedFilters from "../AdvancedFilters/AdvancedFilters"; +import useBreakpoints from "hooks/useBreakpoints"; +import useFeatureFlag from "hooks/useFeatureFlag"; const DesktopLayout = ({ filters, list, map }) => { const isFilterPanelOpen = useFilterPanel(); + const { categoryIds, toggleCategory } = useCategoryIds([]); + const { isMobile } = useBreakpoints(); + const hasAdvancedFilterFeatureFlag = useFeatureFlag("advancedFilter"); + const isListPanelOpen = useListPanel(); + const dispatch = useAppDispatch(); const FilterPanelPlaceholder = styled("div", { shouldForwardProp: (prop) => prop !== "isFilterPanelOpen", @@ -21,6 +44,26 @@ const DesktopLayout = ({ filters, list, map }) => { }), })); + const toggleDrawer = (event) => { + if ( + event.type === "keydown" && + (event.key === "Tab" || event.key === "Shift") + ) { + return; + } + + dispatch({ type: "TOGGLE_LIST_PANEL", listPanel: !isListPanelOpen }); + }; + const LightTooltip = styled(({ className, ...props }) => ( + + ))(({ theme }) => ({ + [`& .${tooltipClasses.tooltip}`]: { + backgroundColor: theme.palette.common.white, + boxShadow: theme.shadows[1], + fontSize: 11, + padding: "10px", + }, + })); return ( <> {filters} @@ -34,14 +77,76 @@ const DesktopLayout = ({ filters, list, map }) => { - - {list} - + + {list} + + + + + {hasAdvancedFilterFeatureFlag && !isMobile && ( + + + + )} + { if (!showList) { setPosition({ x: 0, - y: 60 * (window.innerHeight / 100), + y: 57 * (window.innerHeight / 100), }); } else { setPosition({ x: 0, - y: 10, + y: 0, }); } }, [showList]); + useEffect(() => { if (filterPanelOpen) { setPosition({ @@ -39,35 +40,45 @@ const MobileLayout = ({ filters, map, list, showList }) => { } else { setPosition({ x: 0, - y: 60, + y: 0, }); } }, [filterPanelOpen]); // Define the bounds for vertical dragging - const minY = 50; + const minY = 60; return ( <> {filters} {map} {list && ( { - setPosition({ x: 0, y: ui.y }); + onStop={(e, ui) => { + if(ui.y < 20 * (window.innerHeight / 100)){ + setPosition({ x: 0, y: 3}); + } + if(ui.y > 20 * (window.innerHeight / 100) && ui.y < 40 * (window.innerHeight / 100)){ + setPosition({ x: 0, y: 25 * (window.innerHeight / 100)}); + } + if(ui.y > 40 * (window.innerHeight / 100)){ + setPosition({ x: 0, y: 57 * (window.innerHeight / 100)}); + } + }} handle=".handle" - bounds={{ top: minY, bottom: minY * (window.innerHeight / 100) }} + bounds={{ top: 0, bottom: minY * (window.innerHeight / 100) }} defaultPosition={{ x: 0, y: minY * (window.innerHeight / 100) }} axis="y" sx={{ diff --git a/client/src/components/Layout/Menu.js b/client/src/components/Layout/Menu.js index 5e4a8921d..a0119bcbc 100644 --- a/client/src/components/Layout/Menu.js +++ b/client/src/components/Layout/Menu.js @@ -15,6 +15,7 @@ import { useState } from "react"; import { useUserContext } from "../../contexts/userContext"; import { IconButton } from "../UI/StandardButton"; import MenuItemLink from "./MenuItemLink"; +// import Features from "../Admin/Features"; export default function Menu() { const { isHomePage } = useLocationHook(); @@ -109,6 +110,11 @@ export default function Menu() { text="Suggestions" /> + )} diff --git a/client/src/components/StaticPages/Contact.js b/client/src/components/StaticPages/Contact.js index 04796005a..53fcbec49 100644 --- a/client/src/components/StaticPages/Contact.js +++ b/client/src/components/StaticPages/Contact.js @@ -17,6 +17,7 @@ import { sendContactForm } from "services/contact-service"; import * as Yup from "yup"; import * as analytics from "../../services/analytics"; import Footer from "../Layout/Footer"; +import { useSiteContext } from "contexts/siteContext"; const validationSchema = Yup.object().shape({ name: Yup.string().required("Please enter your name"), @@ -96,6 +97,7 @@ const Contact = () => { const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); const { isMobile } = useBreakpoints(); + const { tenantId } = useSiteContext(); const navigate = useNavigate(); @@ -161,6 +163,7 @@ const Contact = () => { phone: "", title: "", message: "", + tenantId, }} validationSchema={validationSchema} onSubmit={(values) => { diff --git a/client/src/components/StaticPages/Donate.js b/client/src/components/StaticPages/Donate.js index a25eb3da9..42f6c9a29 100644 --- a/client/src/components/StaticPages/Donate.js +++ b/client/src/components/StaticPages/Donate.js @@ -14,10 +14,6 @@ import { import donationStep1 from "images/donationStep1.png"; import donationStep2 from "images/donationStep2.png"; import donationStep3 from "images/donationStep3.png"; -// import donationStep4 from "images/donationStep4.png"; -// import donationStep5 from "images/donationStep5.png"; -// import donationStep6 from "images/donationStep6.png"; -// import donationStep7 from "images/donationStep7.png"; import logo from "images/foodoasis.svg"; import { useState, useEffect } from "react"; import * as analytics from "../../services/analytics"; diff --git a/client/src/hooks/useFeatureToLogin.js b/client/src/hooks/useFeatureToLogin.js new file mode 100644 index 000000000..9d39ead80 --- /dev/null +++ b/client/src/hooks/useFeatureToLogin.js @@ -0,0 +1,28 @@ +import { useState, useEffect, useCallback } from "react"; +import * as useFeatureToLoginService from "../services/feature-to-login-service"; + +export const useFeatureToLogin = (id, type) => { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const fetch = useCallback(() => { + const fetchApi = async () => { + setLoading(true); + try { + const response = await useFeatureToLoginService.getLoginsByFeature(); + setData(response); + setLoading(false); + } catch (err) { + setError(err); + console.error(err); + } + }; + fetchApi(); + }, []); + + useEffect(() => { + fetch(); + }, [fetch]); + return { data, error, loading, refetch: fetch }; +}; diff --git a/client/src/hooks/useFeatures.js b/client/src/hooks/useFeatures.js new file mode 100644 index 000000000..0c1d65e79 --- /dev/null +++ b/client/src/hooks/useFeatures.js @@ -0,0 +1,29 @@ +import { useState, useEffect, useCallback } from "react"; +import * as featureService from "../services/feature-service"; + +export const useFeatures = () => { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const fetch = useCallback(() => { + const fetchApi = async () => { + setLoading(true); + try { + const tags = await featureService.getAllFeatures(); + + setData(tags); + setLoading(false); + } catch (err) { + setError(err); + console.error(err); + } + }; + fetchApi(); + }, []); + + useEffect(() => { + fetch(); + }, [fetch]); + return { data, error, loading, refetch: fetch }; +}; diff --git a/client/src/icons/DrawerLeftArrowButton.js b/client/src/icons/DrawerLeftArrowButton.js new file mode 100644 index 000000000..09f478d17 --- /dev/null +++ b/client/src/icons/DrawerLeftArrowButton.js @@ -0,0 +1,63 @@ +function DrawerLeftArrowButton() { + return ( + + + + + + + + + + + + + + + + + + + ); +} + +export default DrawerLeftArrowButton; diff --git a/client/src/icons/DrawerRightArrowButton.js b/client/src/icons/DrawerRightArrowButton.js new file mode 100644 index 000000000..436024724 --- /dev/null +++ b/client/src/icons/DrawerRightArrowButton.js @@ -0,0 +1,63 @@ +function DrawerRightArrowButton() { + return ( + + + + + + + + + + + + + + + + + + + ); +} + +export default DrawerRightArrowButton; diff --git a/client/src/images/donationStep4.png b/client/src/images/donationStep4.png deleted file mode 100644 index 472d6e915..000000000 Binary files a/client/src/images/donationStep4.png and /dev/null differ diff --git a/client/src/images/donationStep5.png b/client/src/images/donationStep5.png deleted file mode 100644 index 5de8b6381..000000000 Binary files a/client/src/images/donationStep5.png and /dev/null differ diff --git a/client/src/images/donationStep6.png b/client/src/images/donationStep6.png deleted file mode 100644 index 4f775e469..000000000 Binary files a/client/src/images/donationStep6.png and /dev/null differ diff --git a/client/src/images/donationStep7.png b/client/src/images/donationStep7.png deleted file mode 100644 index 331b22099..000000000 Binary files a/client/src/images/donationStep7.png and /dev/null differ diff --git a/client/src/services/contact-service.js b/client/src/services/contact-service.js index 9fd4fa765..af0f65b6f 100644 --- a/client/src/services/contact-service.js +++ b/client/src/services/contact-service.js @@ -4,6 +4,9 @@ const baseUrl = "/api/emails/contact"; const clientUrl = window.location.origin; export const sendContactForm = async (formData) => { - const response = await axios.post(baseUrl, { ...formData, clientUrl }); + const response = await axios.post(baseUrl, { + ...formData, + clientUrl, + }); return response.data; }; diff --git a/client/src/services/feature-service.js b/client/src/services/feature-service.js new file mode 100644 index 000000000..d72e8e287 --- /dev/null +++ b/client/src/services/feature-service.js @@ -0,0 +1,19 @@ +import axios from "axios"; + +const baseUrl = "api/features"; + +export const getAllFeatures = async () => { + try { + const response = await axios.get(`${baseUrl}`); + return response.data; + } catch (error) { + throw new Error(error.message); + } +}; + +export const post = async (feature) => { + const response = await axios.post(`${baseUrl}`, { + ...feature, + }); + return response.data; +}; diff --git a/client/src/services/feature-to-login-service.js b/client/src/services/feature-to-login-service.js new file mode 100644 index 000000000..13898f590 --- /dev/null +++ b/client/src/services/feature-to-login-service.js @@ -0,0 +1,31 @@ +import axios from "axios"; + +const baseUrl = "api/features-to-login/"; + +export const getLoginsByFeature = async (id) => { + try { + const response = await axios.get(`${baseUrl}`); + return response.data; + } catch (err) { + throw new Error(err.message); + } +}; + +export const addUserToFeature = async (feature_id, login_id) => { + const data = { feature_id, login_id }; + try { + const response = await axios.post(`${baseUrl}`, data); + return response.data; + } catch (err) { + throw err; + } +}; + +export const removeUserFromFeature = async (ftl_id) => { + try { + const response = await axios.delete(`${baseUrl}/${ftl_id}`); + return response.data; + } catch (err) { + throw new Error(err.message); + } +}; diff --git a/package-lock.json b/package-lock.json index 2dc344a67..d623a32bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4792,9 +4792,9 @@ } }, "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", "dev": true }, "node_modules/is-arrayish": { @@ -15907,9 +15907,9 @@ } }, "ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", "dev": true }, "is-arrayish": { diff --git a/server/app/controllers/account-controller.ts b/server/app/controllers/account-controller.ts index cd512c39e..a57a6aae0 100644 --- a/server/app/controllers/account-controller.ts +++ b/server/app/controllers/account-controller.ts @@ -49,18 +49,28 @@ const getById: RequestHandler< const getByEmail: RequestHandler< { email: string }, - { isSuccess: boolean }, + { isSuccess: boolean; message?: string; data?: any }, never, { tenantId: string } > = async (req, res) => { try { const { email } = req.params; const { tenantId } = req.query; - await accountService.selectByEmail(email, tenantId); - res.send({ isSuccess: true }); - } catch (err) { + const response = await accountService.selectByEmail(email, tenantId); + res.send({ isSuccess: true, data: response }); + } catch (err: any) { console.error(err); - res.sendStatus(500); + if ( + err.code === "queryResultErrorCode.noData" || + err.message.includes("No data returned") + ) { + return res + .status(404) + .send({ isSuccess: false, message: "User not found" }); + } + res + .status(500) + .send({ isSuccess: false, message: "Internal server error" }); } }; diff --git a/server/app/controllers/feature-to-login-controller.ts b/server/app/controllers/feature-to-login-controller.ts new file mode 100644 index 000000000..3b733330d --- /dev/null +++ b/server/app/controllers/feature-to-login-controller.ts @@ -0,0 +1,61 @@ +import { RequestHandler } from "express"; +import { FeatureToLogin } from "../../types/feature-to-login-types"; +import featureToLoginService from "../services/feature-to-login-service"; + +const getLoginsByFeature: RequestHandler< + never, + FeatureToLogin[] | { error: string }, + never +> = async (req, res) => { + try { + const resp = await featureToLoginService.getLoginsByFeature(); + res.status(200).json(resp); + } catch (error) { + console.error(error); + res.status(500).json({ error: "Internal server error" }); + } +}; + +const post: RequestHandler< + never, + { id: number } | { error: string } | { message: string }, + FeatureToLogin +> = async (req, res) => { + try { + const resp = await featureToLoginService.insert(req.body); + res.status(201).json(resp); + } catch (error: any) { + console.error(error); + if (error.message === "AssociationAlreadyExists") { + return res + .status(409) + .json({ message: "User is already added to feature." }); + } else { + console.error(error); + res.status(500).json({ error: "Internal server error" }); + } + } +}; + +const remove: RequestHandler< + { id: string }, + Response | { error: string }, + never +> = async (req, res) => { + try { + const rowCount = await featureToLoginService.remove(req.params.id); + if (rowCount !== 1) { + return res.status(400).json({ error: "Record not found" }); + } + res.sendStatus(204); + } catch (error) { + console.error(error); + res.status(500).json({ error: "Internal server error" }); + } +}; + +export default { + post, + remove, + getLoginsByFeature, +}; diff --git a/server/app/routes/feature-router.ts b/server/app/routes/feature-router.ts index aa77ab972..c65c8de9c 100644 --- a/server/app/routes/feature-router.ts +++ b/server/app/routes/feature-router.ts @@ -1,9 +1,17 @@ import { Router } from "express"; import featureController from "../controllers/feature-controller"; +import jwtSession from "../../middleware/jwt-session"; +import { requestValidationMiddleware } from "../../middleware/request-validation-middlewares"; +import { FeaturePostRequestSchema } from "../validation-schema/feature-schema"; const router = Router(); router.get("/", featureController.getAll); -router.post("/", featureController.post); +router.post( + "/", + jwtSession.validateUserHasRequiredRoles(["admin"]), + requestValidationMiddleware(FeaturePostRequestSchema), + featureController.post +); export default router; diff --git a/server/app/routes/feature-to-login-router.ts b/server/app/routes/feature-to-login-router.ts new file mode 100644 index 000000000..2b5955b6e --- /dev/null +++ b/server/app/routes/feature-to-login-router.ts @@ -0,0 +1,24 @@ +import { Router } from "express"; +import featureToLoginController from "../controllers/feature-to-login-controller"; +import jwtSession from "../../middleware/jwt-session"; +import { requestValidationMiddleware } from "../../middleware/request-validation-middlewares"; +import { FeatureToLoginPostRequestSchema } from "../validation-schema/feature-to-login-schema"; + +const router = Router(); + +router.get("/", featureToLoginController.getLoginsByFeature); + +router.post( + "/", + jwtSession.validateUserHasRequiredRoles(["admin"]), + requestValidationMiddleware(FeatureToLoginPostRequestSchema), + featureToLoginController.post +); + +router.delete( + "/:id", + jwtSession.validateUserHasRequiredRoles(["admin"]), + featureToLoginController.remove +); + +export default router; diff --git a/server/app/routes/index.ts b/server/app/routes/index.ts index 1cd9e486b..3c0b279e7 100644 --- a/server/app/routes/index.ts +++ b/server/app/routes/index.ts @@ -18,6 +18,7 @@ import loadRouter from "./load-router"; import emailRouter from "./email-router"; import awsRouter from "./aws-router"; import featureRouter from "./feature-router"; +import featureToLoginRouter from "./feature-to-login-router"; const router = Router(); @@ -40,5 +41,6 @@ router.use("/api/tags", tagRouter); router.use("/api/tenants", tenantRouter); router.use("/api/logins", loginsRouter); router.use("/api/features", featureRouter); +router.use("/api/features-to-login", featureToLoginRouter); export default router; diff --git a/server/app/services/feature-service.ts b/server/app/services/feature-service.ts index fba254d62..a0e0bdfdf 100644 --- a/server/app/services/feature-service.ts +++ b/server/app/services/feature-service.ts @@ -3,7 +3,7 @@ import { Feature } from "../../types/feature-types"; const getAll = async (): Promise => { const sql = ` - select id, name from feature_flag + SELECT id, name FROM feature_flag `; const result = await db.manyOrNone(sql); return result; @@ -11,9 +11,9 @@ const getAll = async (): Promise => { const insert = async (model: Feature): Promise<{ id: number }> => { const sql = ` - insert into feature_flag(name) - values ($) - returning id, name + INSERT INTO feature_flag(name) + VALUES ($) + RETURNING id, name `; const result = await db.one(sql, model); return result; diff --git a/server/app/services/feature-to-login-service.ts b/server/app/services/feature-to-login-service.ts new file mode 100644 index 000000000..005ed5ce4 --- /dev/null +++ b/server/app/services/feature-to-login-service.ts @@ -0,0 +1,58 @@ +import { FeatureToLogin } from "../../types/feature-to-login-types"; +import db from "./db"; + +const getLoginsByFeature = async (): Promise => { + const sql = ` + SELECT u.id as login_id, u.first_name, u.last_name, u.email, ff.name as feature_name, ff.id as feature_id, ftl.id as ftl_id + FROM feature_flag ff + LEFT JOIN feature_to_login ftl ON ff.id = ftl.feature_id + LEFT JOIN login u ON u.id = ftl.login_id + ORDER BY ff.id DESC; +`; + const result = await db.manyOrNone(sql); + return result; +}; + +const insert = async ( + model: FeatureToLogin +): Promise<{ + id: number; + feature_id: number; + login_id: number; +}> => { + try { + const existingAssociation = await db.oneOrNone( + `SELECT 1 FROM feature_to_login WHERE feature_id = $ AND login_id = $`, + model + ); + if (existingAssociation) { + throw new Error("AssociationAlreadyExists"); + } else { + const sql = ` + INSERT INTO feature_to_login (feature_id, login_id) + VALUES ($, $) + RETURNING id, feature_id, login_id; + `; + const result = await db.one(sql, model); + return result; + } + } catch (error) { + console.error("Error in insert function:", error); + throw error; + } +}; + +const remove = async (id: string) => { + const sql = ` + DELETE FROM feature_to_login + WHERE id = $ + `; + const result = await db.result(sql, { id: Number(id) }); + return result.rowCount; +}; + +export default { + insert, + remove, + getLoginsByFeature, +}; diff --git a/server/app/services/sendgrid-service.ts b/server/app/services/sendgrid-service.ts index 11845595c..2cf242c94 100644 --- a/server/app/services/sendgrid-service.ts +++ b/server/app/services/sendgrid-service.ts @@ -4,7 +4,7 @@ import { ContactFormData, Email } from "../../types/email-type"; const emailUser: string = process.env.EMAIL_USER || ""; const sendgridKey: string = process.env.SENDGRID_API_KEY || ""; -const staffEmail: string = process.env.CONTACT_US_EMAIL || ""; + sgMail.setApiKey(sendgridKey); const send = async (email: Email) => { @@ -119,8 +119,19 @@ const sendContactEmail = async ({ title, message, clientUrl, + tenantId, phone, }: ContactFormData) => { + + const tenantRegions: { [key: number]: string } = { + 1: process.env.CONTACT_US_LA || "", // LA + 3: process.env.CONTACT_US_HAWAII || "", // Hawaii + 5: process.env.CONTACT_US_LA || "", // Texas + 6: process.env.CONTACT_US_LA || "", // Santa Barbara + }; + + const staffEmail: string = tenantId ? tenantRegions[tenantId] : ""; + const emailBody = `

@@ -565,7 +584,7 @@ ContactFormData) => { Date: ${dateString} - Time: ${time} PST + Time: ${time} Subject: ${ diff --git a/server/app/validation-schema/feature-schema.ts b/server/app/validation-schema/feature-schema.ts new file mode 100644 index 000000000..69c2ba24a --- /dev/null +++ b/server/app/validation-schema/feature-schema.ts @@ -0,0 +1,16 @@ +import { JSONSchemaType } from "ajv"; +import { Feature } from "../../types/feature-types"; + +export const FeaturePostRequestSchema: JSONSchemaType = { + type: "object", + required: ["name"], + properties: { + id: { + type: "number", + }, + name: { + type: "string", + }, + }, + additionalProperties: false, +}; diff --git a/server/app/validation-schema/feature-to-login-schema.ts b/server/app/validation-schema/feature-to-login-schema.ts new file mode 100644 index 000000000..cb071d0d8 --- /dev/null +++ b/server/app/validation-schema/feature-to-login-schema.ts @@ -0,0 +1,19 @@ +import { JSONSchemaType } from "ajv"; +import { FeatureToLogin } from "../../types/feature-to-login-types"; + +export const FeatureToLoginPostRequestSchema: JSONSchemaType = { + type: "object", + required: ["feature_id", "login_id"], + properties: { + id: { + type: "number", + }, + feature_id: { + type: "number", + }, + login_id: { + type: "number", + }, + }, + additionalProperties: false, +}; diff --git a/server/package-lock.json b/server/package-lock.json index a9359bef5..834b175c8 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "foodoasis-web-api", - "version": "1.0.77", + "version": "1.0.79", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "foodoasis-web-api", - "version": "1.0.77", + "version": "1.0.79", "license": "GPL-2.0", "dependencies": { "@aws-sdk/client-location": "^3.499.0", @@ -73,8 +73,7 @@ "nodemon": "^2.0.20", "prettier": "^2.6.2", "ts-jest": "^28.0.7", - "ts-node-dev": "^2.0.0", - "typescript": "^4.9.3" + "ts-node-dev": "^2.0.0" } }, "node_modules/@ampproject/remapping": { @@ -13677,6 +13676,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/server/package.json b/server/package.json index 499886f6e..bd90feb33 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "foodoasis-web-api", - "version": "1.0.79", + "version": "1.0.80", "author": "Hack for LA", "description": "Web API Server for Food Oasis", "main": "server.js", diff --git a/server/types/email-type.ts b/server/types/email-type.ts index 84b32fb30..1af64b7dc 100644 --- a/server/types/email-type.ts +++ b/server/types/email-type.ts @@ -9,6 +9,7 @@ export interface ContactFormData { name: string; message: string; clientUrl: string; + tenantId?: number; email?: string; title?: string; phone?: string; diff --git a/server/types/feature-to-login-types.ts b/server/types/feature-to-login-types.ts new file mode 100644 index 000000000..00faf29ca --- /dev/null +++ b/server/types/feature-to-login-types.ts @@ -0,0 +1,5 @@ +export interface FeatureToLogin { + id: number; + feature_id: number; + login_id: number; +}