From 6253c33488575e2cc033a3bff33774440defc4ba Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 15:02:05 +0100 Subject: [PATCH 01/25] [FE] Add node-sass & typed-scss-modules --- packages/fe/package.json | 5 +- yarn.lock | 243 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 235 insertions(+), 13 deletions(-) diff --git a/packages/fe/package.json b/packages/fe/package.json index 15d24d3..6be3d06 100644 --- a/packages/fe/package.json +++ b/packages/fe/package.json @@ -59,6 +59,7 @@ "jest-resolve": "26.6.0", "jest-watch-typeahead": "0.6.1", "mini-css-extract-plugin": "0.11.3", + "node-sass": "^5.0.0", "npm-run-all": "^4.1.5", "optimize-css-assets-webpack-plugin": "5.0.4", "pnp-webpack-plugin": "1.6.4", @@ -123,7 +124,9 @@ "last 1 safari version" ] }, - "devDependencies": {}, + "devDependencies": { + "typed-scss-modules": "^4.1.1" + }, "jest": { "roots": [ "/src" diff --git a/yarn.lock b/yarn.lock index 0aa96ec..bf4f54b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4164,6 +4164,11 @@ alphanum-sort@^1.0.0: resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= + ansi-align@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" @@ -4656,6 +4661,11 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== +async-foreach@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" + integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= + async-limiter@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" @@ -5566,7 +5576,7 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^1.0.0, chalk@^1.1.3: +chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= @@ -5661,7 +5671,7 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.2.2, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.3: +chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.3: version "3.5.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== @@ -6469,6 +6479,18 @@ css-loader@4.3.0: schema-utils "^2.7.1" semver "^7.3.2" +css-modules-loader-core@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz#5908668294a1becd261ae0a4ce21b0b551f21d16" + integrity sha1-WQhmgpShvs0mGuCkziGwtVHyHRY= + dependencies: + icss-replace-symbols "1.1.0" + postcss "6.0.1" + postcss-modules-extract-imports "1.1.0" + postcss-modules-local-by-default "1.2.0" + postcss-modules-scope "1.1.0" + postcss-modules-values "1.3.0" + css-prefers-color-scheme@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" @@ -6491,6 +6513,14 @@ css-select@^2.0.0, css-select@^2.0.2: domutils "^1.7.0" nth-check "^1.0.2" +css-selector-tokenizer@^0.7.0: + version "0.7.3" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1" + integrity sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg== + dependencies: + cssesc "^3.0.0" + fastparse "^1.1.2" + css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" @@ -8091,6 +8121,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fastparse@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" + integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== + fastq@^1.6.0: version "1.11.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" @@ -8565,6 +8600,13 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +gaze@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" + integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== + dependencies: + globule "^1.0.0" + gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -8723,7 +8765,7 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -8844,6 +8886,15 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globule@^1.0.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.2.tgz#d8bdd9e9e4eef8f96e245999a5dee7eb5d8529c4" + integrity sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA== + dependencies: + glob "~7.1.1" + lodash "~4.17.10" + minimatch "~3.0.2" + gonzales-pe@^4.2.3: version "4.3.0" resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" @@ -9078,6 +9129,11 @@ has-bigints@^1.0.0: resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -9462,6 +9518,11 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +icss-replace-symbols@1.1.0, icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= + icss-utils@^4.0.0, icss-utils@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" @@ -10759,6 +10820,11 @@ jest@26.6.0: import-local "^3.0.2" jest-cli "^26.6.0" +js-base64@^2.1.8: + version "2.6.4" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" + integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -11483,7 +11549,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.21, "lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0, lodash@~4.17.20: +lodash@4.17.21, "lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0, lodash@~4.17.10, lodash@~4.17.20: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -11737,7 +11803,7 @@ memorystream@^0.3.1: resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= -meow@^3.3.0: +meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= @@ -11900,7 +11966,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@3.0.4, minimatch@^3.0.4: +minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -12133,7 +12199,7 @@ mute-stream@0.0.8, mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nan@^2.12.1: +nan@^2.12.1, nan@^2.13.2: version "2.14.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== @@ -12362,6 +12428,28 @@ node-releases@^1.1.61, node-releases@^1.1.70: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== +node-sass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-5.0.0.tgz#4e8f39fbef3bac8d2dc72ebe3b539711883a78d2" + integrity sha512-opNgmlu83ZCF792U281Ry7tak9IbVC+AKnXGovcQ8LG8wFaJv6cLnRlc6DIHlmNxWEexB5bZxi9SZ9JyUuOYjw== + dependencies: + async-foreach "^0.1.3" + chalk "^1.1.1" + cross-spawn "^7.0.3" + gaze "^1.0.0" + get-stdin "^4.0.1" + glob "^7.0.3" + lodash "^4.17.15" + meow "^3.7.0" + mkdirp "^0.5.1" + nan "^2.13.2" + node-gyp "^7.1.0" + npmlog "^4.0.0" + request "^2.88.0" + sass-graph "2.2.5" + stdout-stream "^1.4.0" + "true-case-path" "^1.0.2" + node-source-walk@^4.0.0, node-source-walk@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/node-source-walk/-/node-source-walk-4.2.0.tgz#c2efe731ea8ba9c03c562aa0a9d984e54f27bc2c" @@ -12585,7 +12673,7 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.2, npmlog@^4.1.2: +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -13067,7 +13155,7 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" -param-case@^3.0.3, param-case@^3.0.4: +param-case@^3.0.2, param-case@^3.0.3, param-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== @@ -13304,6 +13392,14 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +path@^0.12.7: + version "0.12.7" + resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" + integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8= + dependencies: + process "^0.11.1" + util "^0.10.3" + pbkdf2@^3.0.3: version "3.1.1" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" @@ -13742,6 +13838,13 @@ postcss-minify-selectors@^4.0.2: postcss "^7.0.0" postcss-selector-parser "^3.0.0" +postcss-modules-extract-imports@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz#b614c9720be6816eaee35fb3a5faa1dba6a05ddb" + integrity sha1-thTJcgvmgW6u41+zpfqh26agXds= + dependencies: + postcss "^6.0.1" + postcss-modules-extract-imports@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" @@ -13749,6 +13852,14 @@ postcss-modules-extract-imports@^2.0.0: dependencies: postcss "^7.0.5" +postcss-modules-local-by-default@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + postcss-modules-local-by-default@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" @@ -13759,6 +13870,14 @@ postcss-modules-local-by-default@^3.0.3: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" +postcss-modules-scope@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + postcss-modules-scope@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" @@ -13767,6 +13886,14 @@ postcss-modules-scope@^2.2.0: postcss "^7.0.6" postcss-selector-parser "^6.0.0" +postcss-modules-values@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" + integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= + dependencies: + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" + postcss-modules-values@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" @@ -14072,6 +14199,15 @@ postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: indexes-of "^1.0.1" uniq "^1.0.1" +postcss@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.1.tgz#000dbd1f8eef217aa368b9a212c5fc40b2a8f3f2" + integrity sha1-AA29H47vIXqjaLmiEsX8QLKo8/I= + dependencies: + chalk "^1.1.3" + source-map "^0.5.6" + supports-color "^3.2.3" + postcss@7.0.21: version "7.0.21" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17" @@ -14081,6 +14217,15 @@ postcss@7.0.21: source-map "^0.6.1" supports-color "^6.1.0" +postcss@^6.0.1: + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: version "7.0.35" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" @@ -14178,7 +14323,7 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process@^0.11.10: +process@^0.11.1, process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= @@ -15122,6 +15267,11 @@ reselect@^4.0.0: resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== +reserved-words@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1" + integrity sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE= + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -15410,6 +15560,16 @@ sanitize.css@^10.0.0: resolved "https://registry.yarnpkg.com/sanitize.css/-/sanitize.css-10.0.0.tgz#b5cb2547e96d8629a60947544665243b1dc3657a" integrity sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg== +sass-graph@2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.5.tgz#a981c87446b8319d96dce0671e487879bd24c2e8" + integrity sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag== + dependencies: + glob "^7.0.0" + lodash "^4.0.0" + scss-tokenizer "^0.2.3" + yargs "^13.3.2" + sass-loader@^10.0.5: version "10.1.1" resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.1.1.tgz#4ddd5a3d7638e7949065dd6e9c7c04037f7e663d" @@ -15475,6 +15635,14 @@ schema-utils@^3.0.0: ajv "^6.12.5" ajv-keywords "^3.5.2" +scss-tokenizer@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" + integrity sha1-jrBtualyMzOCTT9VMGQRSYR85dE= + dependencies: + js-base64 "^2.1.8" + source-map "^0.4.2" + scuid@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/scuid/-/scuid-1.1.0.tgz#d3f9f920956e737a60f72d0e4ad280bf324d5dab" @@ -15893,6 +16061,13 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + integrity sha1-66T12pwNyZneaAMti092FzZSA2s= + dependencies: + amdefine ">=0.0.4" + source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -16103,6 +16278,13 @@ static-extend@^0.1.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +stdout-stream@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" + integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA== + dependencies: + readable-stream "^2.0.1" + stealthy-require@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" @@ -16411,7 +16593,14 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.3.0, supports-color@^5.5.0: +supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= + dependencies: + has-flag "^1.0.0" + +supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -16842,6 +17031,13 @@ trim-off-newlines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= +"true-case-path@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" + integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew== + dependencies: + glob "^7.1.2" + tryer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" @@ -17023,6 +17219,22 @@ type@^2.0.0: resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== +typed-scss-modules@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/typed-scss-modules/-/typed-scss-modules-4.1.1.tgz#eab12f25511a329f8e4837842c3b484ffd66b449" + integrity sha512-eVRej2pMRRbrMQ7oyLNR+GcMCD4Tmc+MMzq+6qo8pTXiz2GQR1dTaYTs9vDeoCE7iJv9JZ7U36fATbIVECJ+fQ== + dependencies: + camelcase "^5.0.0" + chalk "^3.0.0" + chokidar "^3.3.0" + css-modules-loader-core "^1.1.0" + glob "^7.1.6" + param-case "^3.0.2" + path "^0.12.7" + reserved-words "^0.1.2" + slash "^3.0.0" + yargs "^15.0.2" + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -17343,6 +17555,13 @@ util@0.10.3: dependencies: inherits "2.0.1" +util@^0.10.3: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + dependencies: + inherits "2.0.3" + util@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" @@ -18119,7 +18338,7 @@ yargs@^13.3.2: y18n "^4.0.0" yargs-parser "^13.1.2" -yargs@^15.3.1, yargs@^15.4.1: +yargs@^15.0.2, yargs@^15.3.1, yargs@^15.4.1: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== From 75690ea0972eadf45c238e17d2925d8b856209bd Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 15:05:01 +0100 Subject: [PATCH 02/25] [BE] Add marina field resolvers - city - country - amenities --- packages/be/src/db/amenity.ts | 11 +++++++++++ packages/be/src/schemas/marina.ts | 14 +++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/be/src/db/amenity.ts b/packages/be/src/db/amenity.ts index b80e22c..439a91e 100644 --- a/packages/be/src/db/amenity.ts +++ b/packages/be/src/db/amenity.ts @@ -1,4 +1,15 @@ import { amenityDb } from "../types/GeneratedDb"; import kx from "./kx"; +export const getAmenityByMarinaId = (marinaId: number) => { + return getAmenityBase() + .join( + "marina_and_amenity", + "amenity.code", + "marina_and_amenity.amenity_code" + ) + .where("marina_id", marinaId) + .select("code"); +}; + export const getAmenityBase = () => kx("amenity"); diff --git a/packages/be/src/schemas/marina.ts b/packages/be/src/schemas/marina.ts index fb12452..0aa0753 100644 --- a/packages/be/src/schemas/marina.ts +++ b/packages/be/src/schemas/marina.ts @@ -1,9 +1,10 @@ import { gql, IResolverObject } from "apollo-server-koa"; import { toGlobalId } from "graphql-relay"; -import type { cityDb } from "../types/GeneratedDb"; import { MarinaResolvers } from "../types/GeneratedGql"; import { getCityBase } from "../db/city"; +import { getCountryBase } from "../db/country"; +import { getAmenityByMarinaId } from "../db/amenity"; export const TYPE = "Marina"; @@ -31,6 +32,13 @@ export const schema = gql` export const resolver: MarinaResolvers = { id: ({ id }) => toGlobalId(TYPE, String(id)), - city: ({ cityCode }) => getCityBase().where({ code: cityCode }).first() - // TODO: + city: ({ cityCode }) => + getCityBase() + .where({ code: cityCode }) + .first(), + country: ({ countryCode }) => + getCountryBase() + .where({ code: countryCode }) + .first(), + amenities: ({ id }) => getAmenityByMarinaId(id) }; From 23ee3b0cb078f0dcdcb68c03df9475c0104509b7 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 15:05:40 +0100 Subject: [PATCH 03/25] [BE] Resolve city, country, amenity id from code --- packages/be/src/schemas/amenity.ts | 5 +++-- packages/be/src/schemas/city.ts | 5 +++-- packages/be/src/schemas/country.ts | 5 +++-- packages/be/src/schemas/photo.ts | 2 -- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/be/src/schemas/amenity.ts b/packages/be/src/schemas/amenity.ts index edc12dc..78811a6 100644 --- a/packages/be/src/schemas/amenity.ts +++ b/packages/be/src/schemas/amenity.ts @@ -1,7 +1,6 @@ import { gql, IResolverObject } from "apollo-server-koa"; import { toGlobalId } from "graphql-relay"; -import type { cityDb } from "../types/GeneratedDb"; import { AmenityResolvers } from "../types/GeneratedGql"; export const TYPE = "Amenity"; @@ -13,4 +12,6 @@ export const schema = gql` } `; -export const resolver: AmenityResolvers = {}; +export const resolver: AmenityResolvers = { + id: ({ code }) => toGlobalId(TYPE, code) +}; diff --git a/packages/be/src/schemas/city.ts b/packages/be/src/schemas/city.ts index 993c1f3..c1e2c76 100644 --- a/packages/be/src/schemas/city.ts +++ b/packages/be/src/schemas/city.ts @@ -1,7 +1,6 @@ import { gql, IResolverObject } from "apollo-server-koa"; import { toGlobalId } from "graphql-relay"; -import type { cityDb } from "../types/GeneratedDb"; import { CityResolvers } from "../types/GeneratedGql"; export const TYPE = "City"; @@ -16,4 +15,6 @@ export const schema = gql` } `; -export const resolver: CityResolvers = {}; +export const resolver: CityResolvers = { + id: ({ code }) => toGlobalId(TYPE, code) +}; diff --git a/packages/be/src/schemas/country.ts b/packages/be/src/schemas/country.ts index d886389..e5311ea 100644 --- a/packages/be/src/schemas/country.ts +++ b/packages/be/src/schemas/country.ts @@ -1,7 +1,6 @@ import { gql, IResolverObject } from "apollo-server-koa"; import { toGlobalId } from "graphql-relay"; -import type { cityDb } from "../types/GeneratedDb"; import { CountryResolvers } from "../types/GeneratedGql"; export const TYPE = "Country"; @@ -13,4 +12,6 @@ export const schema = gql` } `; -export const resolver: CountryResolvers = {}; +export const resolver: CountryResolvers = { + id: ({ code }) => toGlobalId(TYPE, code) +}; diff --git a/packages/be/src/schemas/photo.ts b/packages/be/src/schemas/photo.ts index 7dbaf20..83cb574 100644 --- a/packages/be/src/schemas/photo.ts +++ b/packages/be/src/schemas/photo.ts @@ -1,7 +1,5 @@ import { gql, IResolverObject } from "apollo-server-koa"; -import { toGlobalId } from "graphql-relay"; -import type { cityDb } from "../types/GeneratedDb"; import { PhotoResolvers } from "../types/GeneratedGql"; export const TYPE = "Photo"; From 6ac492926f996a343b3dedfd9e34a6522a219f50 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 15:06:04 +0100 Subject: [PATCH 04/25] [BE] Fix amenity.TYPE not using amenity.resolver --- packages/be/src/root.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/be/src/root.ts b/packages/be/src/root.ts index 6531c8f..0a82737 100644 --- a/packages/be/src/root.ts +++ b/packages/be/src/root.ts @@ -1,6 +1,6 @@ import { ApolloServer, IResolvers } from "apollo-server-koa"; -import type { DocumentNode } from "graphql"; -import type { Context as KoaContext } from "koa"; +import { DocumentNode } from "graphql"; +import { Context as KoaContext } from "koa"; import { ApolloContext } from "./types/ApolloContext"; import * as query from "./query"; @@ -25,7 +25,7 @@ const typeDefs: DocumentNode[] = [ marina.schema, query.schema, - mutations.schema, + mutations.schema ]; const resolvers: IResolvers = { @@ -36,9 +36,10 @@ const resolvers: IResolvers = { [country.TYPE]: country.resolver, [photo.TYPE]: photo.resolver, [marina.TYPE]: marina.resolver, + [amenity.TYPE]: amenity.resolver, Query: query.resolver, - Mutation: mutations.resolver, + Mutation: mutations.resolver }; type Request = { @@ -57,7 +58,7 @@ const server = new ApolloServer({ }, introspection: true, debug: true, - context: async({ ctx}: Request): Promise => { + context: async ({ ctx }: Request): Promise => { return {}; } }); From c0de4b64e446c8d56fb48015bdabfab27ff8ae2b Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 15:08:19 +0100 Subject: [PATCH 05/25] [BE] Add marina (by id) query --- packages/be/src/db/marina.ts | 10 +++++++++- packages/be/src/query/index.ts | 16 ++++++++++++++-- packages/be/src/types/GeneratedGql.ts | 7 +++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/be/src/db/marina.ts b/packages/be/src/db/marina.ts index defc5ca..7394c0d 100644 --- a/packages/be/src/db/marina.ts +++ b/packages/be/src/db/marina.ts @@ -1,4 +1,12 @@ import { marinaDb } from "../types/GeneratedDb"; import kx from "./kx"; -export const getMarinaBase = () => kx("marina"); +export const getMarinaById = (id: number) => { + return getMarinaBase() + .where("id", id) + .first(); +}; + +export const getMarinaBase = () => { + return kx("marina"); +}; diff --git a/packages/be/src/query/index.ts b/packages/be/src/query/index.ts index 08cccc6..bb4bf56 100644 --- a/packages/be/src/query/index.ts +++ b/packages/be/src/query/index.ts @@ -1,6 +1,11 @@ import { gql } from "apollo-server-koa"; -import { getMarinaBase } from "../db/marina"; +import { getMarinaBase, getMarinaById } from "../db/marina"; import { QueryResolvers } from "../types/GeneratedGql"; +import { fromGlobalId } from "graphql-relay"; +import { getAmenityBase } from "../db/amenity"; +import { getCityBase } from "../db/city"; +import { getCountryBase } from "../db/country"; +import { getPhotoBase } from "../db/photo"; export const schema = gql` type Query { @@ -9,10 +14,17 @@ export const schema = gql` countries: [Country!] amenities: [Amenity!] photos: [Photo!] + + marina(id: ID!): Marina } `; export const resolver: QueryResolvers = { marinas: () => getMarinaBase(), - // TODO: + marina: (parent, args) => getMarinaById(parseInt(fromGlobalId(args.id).id)), + + amenities: () => getAmenityBase(), + cities: () => getCityBase(), + countries: () => getCountryBase(), + photos: () => getPhotoBase() }; diff --git a/packages/be/src/types/GeneratedGql.ts b/packages/be/src/types/GeneratedGql.ts index d4f42a5..eba9267 100644 --- a/packages/be/src/types/GeneratedGql.ts +++ b/packages/be/src/types/GeneratedGql.ts @@ -99,6 +99,12 @@ export type Query = { countries?: Maybe>; amenities?: Maybe>; photos?: Maybe>; + marina?: Maybe; +}; + + +export type QueryMarinaArgs = { + id: Scalars['ID']; }; @@ -290,6 +296,7 @@ export type QueryResolvers>, ParentType, ContextType>; amenities?: Resolver>, ParentType, ContextType>; photos?: Resolver>, ParentType, ContextType>; + marina?: Resolver, ParentType, ContextType, RequireFields>; }; export type Resolvers = { From 0be25528482cae847f6d34630f1ea2776a0a3661 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 15:09:46 +0100 Subject: [PATCH 06/25] [FE] Add simple Routes class for path config --- packages/fe/src/App.tsx | 38 +++++++++----------------------------- packages/fe/src/routes.ts | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 29 deletions(-) create mode 100644 packages/fe/src/routes.ts diff --git a/packages/fe/src/App.tsx b/packages/fe/src/App.tsx index 6b6ea8d..99fa56e 100644 --- a/packages/fe/src/App.tsx +++ b/packages/fe/src/App.tsx @@ -1,44 +1,24 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; -import logo from "./logo.svg"; -import "./App.css"; - -import NotFound from "./scenes/NotFound"; import MarinaList from "./scenes/MarinaList"; import MarinaDetail from "./scenes/MarinaDetail"; +import NotFound from "./scenes/NotFound"; + +import { Routes } from "routes"; + +import styles from './App.module.scss'; function App() { return ( - <> +
- - + + - +
); - - // return ( - //
- //
- // logo - //

- // Edit src/App.tsx and save to reload. - //

- // - // Learn React - // - //
- //
- //
- //
- // ); } export default App; diff --git a/packages/fe/src/routes.ts b/packages/fe/src/routes.ts new file mode 100644 index 0000000..864d776 --- /dev/null +++ b/packages/fe/src/routes.ts @@ -0,0 +1,14 @@ +export class Routes { + static MARINA_LIST = "/marina-list"; + static MARINA_DETAIL = "/marina-detail/:id"; + + static getTo = (route: string, args: { id?: string }) => { + if (route === Routes.MARINA_LIST) { + return Routes.MARINA_LIST; + } else if (route === Routes.MARINA_DETAIL) { + return `/marina-detail/${args.id}/`; + } + + throw Error(`invalid route ${route}`); + }; +} From 7ecb7f64fbc9194e7aac27d679cca6dfb8f1c5e9 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 15:12:18 +0100 Subject: [PATCH 07/25] [FE] Implement basic layout --- packages/fe/src/App.module.scss | 12 ++++ packages/fe/src/App.module.scss.d.ts | 1 + packages/fe/src/sass/_constants.scss | 6 ++ packages/fe/src/scenes/MarinaDetail/index.tsx | 27 +++++++- packages/fe/src/scenes/MarinaList/bg.jpg | Bin 0 -> 89893 bytes packages/fe/src/scenes/MarinaList/index.tsx | 38 ++++++++++- .../scenes/MarinaList/marina-list.module.scss | 62 ++++++++++++++++++ .../MarinaList/marina-list.module.scss.d.ts | 6 ++ 8 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 packages/fe/src/App.module.scss create mode 100644 packages/fe/src/App.module.scss.d.ts create mode 100644 packages/fe/src/sass/_constants.scss create mode 100644 packages/fe/src/scenes/MarinaList/bg.jpg create mode 100644 packages/fe/src/scenes/MarinaList/marina-list.module.scss create mode 100644 packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts diff --git a/packages/fe/src/App.module.scss b/packages/fe/src/App.module.scss new file mode 100644 index 0000000..14928f6 --- /dev/null +++ b/packages/fe/src/App.module.scss @@ -0,0 +1,12 @@ +@import "sass/constants.scss"; + +.wrapper { + background-color: $white; + margin: 0 auto; + padding: 20px; + + @media #{$md} { + max-width: 896px; + padding: 0; + } +} diff --git a/packages/fe/src/App.module.scss.d.ts b/packages/fe/src/App.module.scss.d.ts new file mode 100644 index 0000000..e3282a4 --- /dev/null +++ b/packages/fe/src/App.module.scss.d.ts @@ -0,0 +1 @@ +export const wrapper: string; diff --git a/packages/fe/src/sass/_constants.scss b/packages/fe/src/sass/_constants.scss new file mode 100644 index 0000000..f806c6a --- /dev/null +++ b/packages/fe/src/sass/_constants.scss @@ -0,0 +1,6 @@ +$mdBreakpoint: 896px; + +$md: "(min-width: #{$mdBreakpoint})"; + +$black: #000; +$white: #fff; diff --git a/packages/fe/src/scenes/MarinaDetail/index.tsx b/packages/fe/src/scenes/MarinaDetail/index.tsx index 1113f09..3bfa0b1 100644 --- a/packages/fe/src/scenes/MarinaDetail/index.tsx +++ b/packages/fe/src/scenes/MarinaDetail/index.tsx @@ -1,5 +1,30 @@ import React from "react"; +import graphql from "babel-plugin-relay/macro"; +import { useQuery } from "relay-hooks"; +import { useRouteMatch } from "react-router"; +import { MarinaDetailQuery } from "__generated__/MarinaDetailQuery.graphql"; export default function MarinaDetail() { - return <>MarinaDetail; + const match = useRouteMatch<{id: string}>(); + + const { data, error } = useQuery( + graphql` + query MarinaDetailQuery($id: ID!) { + marina(id: $id) { + id + name + } + } + `, + {id: match.params.id}, + { + fetchPolicy: "store-and-network" + } + ); + + return <>MarinaDetail + + {JSON.stringify(data)} + {JSON.stringify(error)} + ; } diff --git a/packages/fe/src/scenes/MarinaList/bg.jpg b/packages/fe/src/scenes/MarinaList/bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c2a9e0ca744887ca38b781a304d6d75b25511d77 GIT binary patch literal 89893 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

x-dvCtd8M4fe7KPu|$3u_QoqC&fMp~jxlGr${ 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

;$zpkIpVOG-tks-on6X1B%Pa= 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ξjYaA$ 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 graphql` @@ -11,6 +15,20 @@ export default function MarinaList() { marinas { id name + photo { + id + url + } + city { + id + lat + lon + code + } + country { + id + code + } } } `, @@ -22,9 +40,23 @@ export default function MarinaList() { return ( <> -

MarinaList
-
-
{JSON.stringify(data || {}, null, 2)}
+
+

+ CROATIA +

+
{data?.marinas?.length ? data?.marinas?.length - 1 : 130}+ marines for booking
+
+
+ {data?.marinas?.map((marina, index) => ( +
+ + {marina.name} + {marina.name} + +
+ ))}
); diff --git a/packages/fe/src/scenes/MarinaList/marina-list.module.scss b/packages/fe/src/scenes/MarinaList/marina-list.module.scss new file mode 100644 index 0000000..cedca8c --- /dev/null +++ b/packages/fe/src/scenes/MarinaList/marina-list.module.scss @@ -0,0 +1,62 @@ +@import "sass/constants.scss"; + +.title { + height: 160px; + border-radius: 10px; + + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + + background-image: url("./bg.jpg"); + background-repeat: no-repeat; + background-position: center; + background-size: cover; + + @media #{$md} { + height: 160px; + } +} + +.h1 { + padding: 0; + margin: 0 0 10px; + font-size: 3.6rem; + + color: $white; + + @media #{$md} { + font-size: 4.8rem; + } +} + +.count { + font-size: 1.6rem; + + color: $white; + + @media #{$md} { + font-size: 1.5rem; + } +} + +.marinas { + margin-top: 20px; + + display: flex; + flex-wrap: wrap; + justify-content: space-between; +} + +.marinaCard { + width: 32%; + margin-bottom: 15px; + + .cardImage { + width: 290px; + height: 180px; + + border-radius: 10px; + } +} diff --git a/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts b/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts new file mode 100644 index 0000000..3fe44cd --- /dev/null +++ b/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts @@ -0,0 +1,6 @@ +export const cardImage: string; +export const count: string; +export const h1: string; +export const marinaCard: string; +export const marinas: string; +export const title: string; From bbda8e099c377a647e11e9693093e5d1451e1527 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 15:13:25 +0100 Subject: [PATCH 08/25] [FE & BE] Config, schema & codegen --- packages/be/README.md | 2 +- packages/be/src/db/kx.ts | 9 +- packages/fe/logo.png | Bin 0 -> 3751 bytes packages/fe/schema.graphql | 1 + .../MarinaDetailQuery.graphql.ts | 102 ++++++++++++++++ .../__generated__/MarinaListQuery.graphql.ts | 113 ++++++++++++++++-- packages/fe/src/index.tsx | 13 +- packages/fe/src/types/GeneratedGQL.ts | 6 + packages/fe/tsconfig.json | 2 +- 9 files changed, 221 insertions(+), 27 deletions(-) create mode 100644 packages/fe/logo.png create mode 100644 packages/fe/src/__generated__/MarinaDetailQuery.graphql.ts diff --git a/packages/be/README.md b/packages/be/README.md index bca3375..a1086bd 100644 --- a/packages/be/README.md +++ b/packages/be/README.md @@ -7,7 +7,7 @@ ## gql types / codegen -- `yarn codegen` +- `yarn generate` ## db types diff --git a/packages/be/src/db/kx.ts b/packages/be/src/db/kx.ts index 27be1bb..9ebd8c4 100644 --- a/packages/be/src/db/kx.ts +++ b/packages/be/src/db/kx.ts @@ -17,11 +17,8 @@ const kxConfig = { const kx = Knex(knexStringcase(kxConfig)); -// kx.on("query-response", (res) => { -// // if (res && res[0] && res[0].dateTo) { -// // console.log(res.length); -// // } -// console.log(res); -// }); +kx.on("query-response", res => { + // console.log(res); +}); export default kx; diff --git a/packages/fe/logo.png b/packages/fe/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a3f2bda3221ba447e60c552f3dce2bcc26c61157 GIT binary patch literal 3751 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 | null; }; export type MarinaListQuery = { @@ -22,12 +36,40 @@ query MarinaListQuery { marinas { id name + photo { + id + url + } + city { + id + lat + lon + code + } + country { + id + code + } } } */ const node: ConcreteRequest = (function(){ -var v0 = [ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "code", + "storageKey": null +}, +v2 = [ { "alias": null, "args": null, @@ -36,18 +78,71 @@ var v0 = [ "name": "marinas", "plural": true, "selections": [ + (v0/*: any*/), { "alias": null, "args": null, "kind": "ScalarField", - "name": "id", + "name": "name", "storageKey": null }, { "alias": null, "args": null, - "kind": "ScalarField", - "name": "name", + "concreteType": "Photo", + "kind": "LinkedField", + "name": "photo", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "url", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "City", + "kind": "LinkedField", + "name": "city", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lat", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lon", + "storageKey": null + }, + (v1/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Country", + "kind": "LinkedField", + "name": "country", + "plural": false, + "selections": [ + (v0/*: any*/), + (v1/*: any*/) + ], "storageKey": null } ], @@ -60,7 +155,7 @@ return { "kind": "Fragment", "metadata": null, "name": "MarinaListQuery", - "selections": (v0/*: any*/), + "selections": (v2/*: any*/), "type": "Query", "abstractKey": null }, @@ -69,17 +164,17 @@ return { "argumentDefinitions": [], "kind": "Operation", "name": "MarinaListQuery", - "selections": (v0/*: any*/) + "selections": (v2/*: any*/) }, "params": { - "cacheID": "946a0736d178d80c2050413ecb4f6b1e", + "cacheID": "842950e155d323021942663a1f09c135", "id": null, "metadata": {}, "name": "MarinaListQuery", "operationKind": "query", - "text": "query MarinaListQuery {\n marinas {\n id\n name\n }\n}\n" + "text": "query MarinaListQuery {\n marinas {\n id\n name\n photo {\n id\n url\n }\n city {\n id\n lat\n lon\n code\n }\n country {\n id\n code\n }\n }\n}\n" } }; })(); -(node as any).hash = 'b5e576de9d78c837a5d48724fbead9d0'; +(node as any).hash = '181d6ffd28367b54abce4a7dad70d815'; export default node; diff --git a/packages/fe/src/index.tsx b/packages/fe/src/index.tsx index 62b5d67..173ae73 100644 --- a/packages/fe/src/index.tsx +++ b/packages/fe/src/index.tsx @@ -7,8 +7,6 @@ import "./index.css"; import App from "./App"; import { makeRelayEnvironment } from "./environment"; -// import reportWebVitals from './reportWebVitals'; - const { environment } = makeRelayEnvironment(); // @ts-expect-error window.environment = environment; @@ -16,15 +14,10 @@ window.environment = environment; ReactDOM.render( - - - + + + , document.getElementById("root") ); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -// reportWebVitals(); diff --git a/packages/fe/src/types/GeneratedGQL.ts b/packages/fe/src/types/GeneratedGQL.ts index 10331b3..f6f26e6 100644 --- a/packages/fe/src/types/GeneratedGQL.ts +++ b/packages/fe/src/types/GeneratedGQL.ts @@ -94,4 +94,10 @@ export type Query = { countries?: Maybe>; amenities?: Maybe>; photos?: Maybe>; + marina?: Maybe; +}; + + +export type QueryMarinaArgs = { + id: Scalars['ID']; }; diff --git a/packages/fe/tsconfig.json b/packages/fe/tsconfig.json index c500d9b..da5d2dc 100644 --- a/packages/fe/tsconfig.json +++ b/packages/fe/tsconfig.json @@ -19,7 +19,7 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react" }, "include": [ "src" From e4f55a78d0c8e5acc400b5d326494c930b864981 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 15:35:09 +0100 Subject: [PATCH 09/25] [FE] Polish MarinaList design & add basic responsivity --- packages/fe/src/App.module.scss | 2 +- packages/fe/src/sass/_constants.scss | 2 + packages/fe/src/scenes/MarinaList/index.tsx | 30 +++++++--- .../scenes/MarinaList/marina-list.module.scss | 58 +++++++++++++++++-- .../MarinaList/marina-list.module.scss.d.ts | 4 ++ 5 files changed, 83 insertions(+), 13 deletions(-) diff --git a/packages/fe/src/App.module.scss b/packages/fe/src/App.module.scss index 14928f6..9465017 100644 --- a/packages/fe/src/App.module.scss +++ b/packages/fe/src/App.module.scss @@ -7,6 +7,6 @@ @media #{$md} { max-width: 896px; - padding: 0; + padding: 40px 0 0; } } diff --git a/packages/fe/src/sass/_constants.scss b/packages/fe/src/sass/_constants.scss index f806c6a..1c18ecc 100644 --- a/packages/fe/src/sass/_constants.scss +++ b/packages/fe/src/sass/_constants.scss @@ -4,3 +4,5 @@ $md: "(min-width: #{$mdBreakpoint})"; $black: #000; $white: #fff; +$blue: rgb(97, 97, 163); +$grey: rgb(58, 58, 58); diff --git a/packages/fe/src/scenes/MarinaList/index.tsx b/packages/fe/src/scenes/MarinaList/index.tsx index 6896297..f16412e 100644 --- a/packages/fe/src/scenes/MarinaList/index.tsx +++ b/packages/fe/src/scenes/MarinaList/index.tsx @@ -6,7 +6,7 @@ import { Routes } from "routes"; import { MarinaListQuery } from "__generated__/MarinaListQuery.graphql"; -import styles from './marina-list.module.scss'; +import styles from "./marina-list.module.scss"; export default function MarinaList() { const { data } = useQuery( @@ -34,27 +34,43 @@ export default function MarinaList() { `, {}, { - fetchPolicy: "store-and-network" + fetchPolicy: "store-and-network", } ); return ( <>
-

- CROATIA -

-
{data?.marinas?.length ? data?.marinas?.length - 1 : 130}+ marines for booking
+

CROATIA

+
+ {data?.marinas?.length ? data?.marinas?.length - 1 : 130}+ marines for + booking +
{data?.marinas?.map((marina, index) => (
+ {marina.name} +
+ {marina.city?.code} |{" "} + {marina.country?.code} +
- {marina.name} {marina.name} +
+ 76€ per night +
))}
diff --git a/packages/fe/src/scenes/MarinaList/marina-list.module.scss b/packages/fe/src/scenes/MarinaList/marina-list.module.scss index cedca8c..b18a8dc 100644 --- a/packages/fe/src/scenes/MarinaList/marina-list.module.scss +++ b/packages/fe/src/scenes/MarinaList/marina-list.module.scss @@ -45,18 +45,66 @@ margin-top: 20px; display: flex; - flex-wrap: wrap; - justify-content: space-between; + flex-direction: column; + + @media #{$md} { + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + } } .marinaCard { - width: 32%; + width: 100%; margin-bottom: 15px; + display: flex; + flex-direction: column; + align-items: center; + + @media #{$md} { + width: 32%; + + align-items: flex-start; + } + .cardImage { - width: 290px; + width: 100%; height: 180px; - border-radius: 10px; + + object-fit: cover; + + @media #{$md} { + width: 290px; + height: 180px; + } + } + + .locationRow { + margin-top: 10px; + } + + .location { + color: $blue; + } + + .name { + margin-top: 5px; + + display: block; + + font-size: 2.1rem; + font-weight: 600; + text-decoration: none; + color: $black; + } + + .price { + margin-top: 5px; + color: $grey; + + font-size: 1.3rem; + font-weight: 300; } } diff --git a/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts b/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts index 3fe44cd..60c716f 100644 --- a/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts +++ b/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts @@ -1,6 +1,10 @@ export const cardImage: string; export const count: string; export const h1: string; +export const location: string; +export const locationRow: string; export const marinaCard: string; export const marinas: string; +export const name: string; +export const price: string; export const title: string; From 9d21fc1c406f80849e8ae65d40031f26dc4b7e79 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 18:19:55 +0100 Subject: [PATCH 10/25] [FE] Add mapbox lib --- packages/fe/package.json | 4 + yarn.lock | 406 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 404 insertions(+), 6 deletions(-) diff --git a/packages/fe/package.json b/packages/fe/package.json index 6be3d06..8945907 100644 --- a/packages/fe/package.json +++ b/packages/fe/package.json @@ -12,12 +12,15 @@ "@graphql-codegen/schema-ast": "^1.18.1", "@graphql-codegen/typescript": "^1.21.1", "@graphql-codegen/typescript-resolvers": "^1.19.0", + "@material-ui/core": "^4.11.3", + "@material-ui/icons": "^4.11.2", "@pmmmwh/react-refresh-webpack-plugin": "0.4.3", "@svgr/webpack": "5.5.0", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@types/jest": "^26.0.15", + "@types/mapbox-gl": "^2.1.1", "@types/node": "^12.0.0", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", @@ -58,6 +61,7 @@ "jest-circus": "26.6.0", "jest-resolve": "26.6.0", "jest-watch-typeahead": "0.6.1", + "mapbox-gl": "^2.2.0", "mini-css-extract-plugin": "0.11.3", "node-sass": "^5.0.0", "npm-run-all": "^4.1.5", diff --git a/yarn.lock b/yarn.lock index bf4f54b..d30ed6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1243,7 +1243,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.13.10" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d" integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw== @@ -1330,6 +1330,11 @@ resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== +"@emotion/hash@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" + integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== + "@endemolshinegroup/cosmiconfig-typescript-loader@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz#eea4635828dde372838b0909693ebd9aafeec22d" @@ -2708,6 +2713,127 @@ npmlog "^4.1.2" write-file-atomic "^3.0.3" +"@mapbox/geojson-rewind@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@mapbox/geojson-rewind/-/geojson-rewind-0.5.0.tgz#91f0ad56008c120caa19414b644d741249f4f560" + integrity sha512-73l/qJQgj/T/zO1JXVfuVvvKDgikD/7D/rHAD28S9BG1OTstgmftrmqfCx4U+zQAmtsB6HcDA3a7ymdnJZAQgg== + dependencies: + concat-stream "~2.0.0" + minimist "^1.2.5" + +"@mapbox/geojson-types@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@mapbox/geojson-types/-/geojson-types-1.0.2.tgz#9aecf642cb00eab1080a57c4f949a65b4a5846d6" + integrity sha512-e9EBqHHv3EORHrSfbR9DqecPNn+AmuAoQxV6aL8Xu30bJMJR1o8PZLZzpk1Wq7/NfCbuhmakHTPYRhoqLsXRnw== + +"@mapbox/jsonlint-lines-primitives@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234" + integrity sha1-zlblOfg1UrWNENZy6k1vya3HsjQ= + +"@mapbox/mapbox-gl-supported@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.0.tgz#bb133cd91e562c006713fbc83f21e4b6f711a388" + integrity sha512-zu4udqYiBrKMQKwpKJ4hhPON7tz0QR/JZ3iGpHnNWFmH3Sv/ysxlICATUtGCFpsyJf2v1WpFhlzaZ3GhhKmPMA== + +"@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2" + integrity sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI= + +"@mapbox/tiny-sdf@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@mapbox/tiny-sdf/-/tiny-sdf-1.2.5.tgz#424c620a96442b20402552be70a7f62a8407cc59" + integrity sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw== + +"@mapbox/unitbezier@^0.0.0": + version "0.0.0" + resolved "https://registry.yarnpkg.com/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz#15651bd553a67b8581fb398810c98ad86a34524e" + integrity sha1-FWUb1VOme4WB+zmIEMmK2Go0Uk4= + +"@mapbox/vector-tile@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz#d3a74c90402d06e89ec66de49ec817ff53409666" + integrity sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw== + dependencies: + "@mapbox/point-geometry" "~0.1.0" + +"@mapbox/whoots-js@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" + integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== + +"@material-ui/core@^4.11.3": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.11.3.tgz#f22e41775b0bd075e36a7a093d43951bf7f63850" + integrity sha512-Adt40rGW6Uds+cAyk3pVgcErpzU/qxc7KBR94jFHBYretU4AtWZltYcNsbeMn9tXL86jjVL1kuGcIHsgLgFGRw== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/styles" "^4.11.3" + "@material-ui/system" "^4.11.3" + "@material-ui/types" "^5.1.0" + "@material-ui/utils" "^4.11.2" + "@types/react-transition-group" "^4.2.0" + clsx "^1.0.4" + hoist-non-react-statics "^3.3.2" + popper.js "1.16.1-lts" + prop-types "^15.7.2" + react-is "^16.8.0 || ^17.0.0" + react-transition-group "^4.4.0" + +"@material-ui/icons@^4.11.2": + version "4.11.2" + resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.11.2.tgz#b3a7353266519cd743b6461ae9fdfcb1b25eb4c5" + integrity sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ== + dependencies: + "@babel/runtime" "^7.4.4" + +"@material-ui/styles@^4.11.3": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.3.tgz#1b8d97775a4a643b53478c895e3f2a464e8916f2" + integrity sha512-HzVzCG+PpgUGMUYEJ2rTEmQYeonGh41BYfILNFb/1ueqma+p1meSdu4RX6NjxYBMhf7k+jgfHFTTz+L1SXL/Zg== + dependencies: + "@babel/runtime" "^7.4.4" + "@emotion/hash" "^0.8.0" + "@material-ui/types" "^5.1.0" + "@material-ui/utils" "^4.11.2" + clsx "^1.0.4" + csstype "^2.5.2" + hoist-non-react-statics "^3.3.2" + jss "^10.5.1" + jss-plugin-camel-case "^10.5.1" + jss-plugin-default-unit "^10.5.1" + jss-plugin-global "^10.5.1" + jss-plugin-nested "^10.5.1" + jss-plugin-props-sort "^10.5.1" + jss-plugin-rule-value-function "^10.5.1" + jss-plugin-vendor-prefixer "^10.5.1" + prop-types "^15.7.2" + +"@material-ui/system@^4.11.3": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.11.3.tgz#466bc14c9986798fd325665927c963eb47cc4143" + integrity sha512-SY7otguNGol41Mu2Sg6KbBP1ZRFIbFLHGK81y4KYbsV2yIcaEPOmsCK6zwWlp+2yTV3J/VwT6oSBARtGIVdXPw== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/utils" "^4.11.2" + csstype "^2.5.2" + prop-types "^15.7.2" + +"@material-ui/types@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2" + integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A== + +"@material-ui/utils@^4.11.2": + version "4.11.2" + resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.11.2.tgz#f1aefa7e7dff2ebcb97d31de51aecab1bb57540a" + integrity sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA== + dependencies: + "@babel/runtime" "^7.4.4" + prop-types "^15.7.2" + react-is "^16.8.0 || ^17.0.0" + "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents": version "2.1.8-no-fsevents" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz#da7c3996b8e6e19ebd14d82eaced2313e7769f9b" @@ -3351,6 +3477,11 @@ dependencies: "@types/node" "*" +"@types/geojson@*": + version "7946.0.7" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad" + integrity sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ== + "@types/glob@^7.1.1": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" @@ -3511,6 +3642,13 @@ resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== +"@types/mapbox-gl@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@types/mapbox-gl/-/mapbox-gl-2.1.1.tgz#c1ef62f5e296ec58899f7a039ca56a3d61a5f99a" + integrity sha512-oDgHhkAC9iYIj/H/9iwm6gHHKKPzweTUgOlozcrvWr8T07dyymGty6mON2j4kEeBGyTTI291vI6gP9k2ArxvHw== + dependencies: + "@types/geojson" "*" + "@types/mime@^1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" @@ -3616,6 +3754,13 @@ "@types/history" "*" "@types/react" "*" +"@types/react-transition-group@^4.2.0": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.1.tgz#e1a3cb278df7f47f17b5082b1b3da17170bd44b1" + integrity sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^17.0.0": version "17.0.3" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79" @@ -5828,6 +5973,11 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= +clsx@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" + integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== + cmd-shim@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-4.1.0.tgz#b3a904a6743e9fede4148c6f3800bf2a08135bdd" @@ -6037,7 +6187,7 @@ concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-stream@^2.0.0: +concat-stream@^2.0.0, concat-stream@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== @@ -6537,6 +6687,14 @@ css-tree@^1.1.2: mdn-data "2.0.14" source-map "^0.6.1" +css-vendor@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d" + integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ== + dependencies: + "@babel/runtime" "^7.8.3" + is-in-browser "^1.0.2" + css-what@^3.2.1: version "3.4.2" resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" @@ -6566,6 +6724,11 @@ css@^3.0.0: source-map "^0.6.1" source-map-resolve "^0.6.0" +csscolorparser@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b" + integrity sha1-s085HupNqPPpgjHizNjfnAQfFxs= + cssdb@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" @@ -6678,6 +6841,11 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" +csstype@^2.5.2: + version "2.6.16" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.16.tgz#544d69f547013b85a40d15bff75db38f34fe9c39" + integrity sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q== + csstype@^3.0.2: version "3.0.7" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b" @@ -7249,6 +7417,14 @@ dom-converter@^0.2: dependencies: utila "~0.4" +dom-helpers@^5.0.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b" + integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -7358,6 +7534,11 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" +earcut@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.2.tgz#41b0bc35f63e0fe80da7cddff28511e7e2e80d11" + integrity sha512-eZoZPPJcUHnfRZ0PjLvx2qBordSiO8ofC3vt+qACLM95u+4DovnbYNpQtJh0DNsWj8RnxrQytD4WA8gj5cRIaQ== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -8612,6 +8793,11 @@ gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== +geojson-vt@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.2.1.tgz#f8adb614d2c1d3f6ee7c4265cad4bbf3ad60c8b7" + integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg== + get-amd-module-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-amd-module-type/-/get-amd-module-type-3.0.0.tgz#bb334662fa04427018c937774570de495845c288" @@ -8750,6 +8936,11 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" +gl-matrix@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.3.0.tgz#232eef60b1c8b30a28cbbe75b2caf6c48fd6358b" + integrity sha512-COb7LDz+SXaHtl/h4LeaFcNdJdAQSDeVqjiIihSXNrkWObZLhDI4hIkZC11Aeqp7bcE72clzB0BnDXr2SmslRA== + glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -9064,6 +9255,11 @@ graphviz@0.0.9: dependencies: temp "~0.4.0" +grid-index@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7" + integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA== + growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -9499,6 +9695,11 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" +hyphenate-style-name@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" + integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== + i@0.3.x: version "0.3.6" resolved "https://registry.yarnpkg.com/i/-/i-0.3.6.tgz#d96c92732076f072711b6b10fd7d4f65ad8ee23d" @@ -9537,7 +9738,7 @@ identity-obj-proxy@3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.13, ieee754@^1.1.4: +ieee754@^1.1.12, ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -9647,6 +9848,13 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +indefinite-observable@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/indefinite-observable/-/indefinite-observable-2.0.1.tgz#574af29bfbc17eb5947793797bddc94c9d859400" + integrity sha512-G8vgmork+6H9S8lUAg1gtXEj2JxIQTo0g2PbFiYOdjkziSI0F7UYBiVwhZRuixhBCNGczAls34+5HJPyZysvxQ== + dependencies: + symbol-observable "1.2.0" + indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -10020,6 +10228,11 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" +is-in-browser@^1.0.2, is-in-browser@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" + integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= + is-installed-globally@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" @@ -11023,6 +11236,77 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jss-plugin-camel-case@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.6.0.tgz#93d2cd704bf0c4af70cc40fb52d74b8a2554b170" + integrity sha512-JdLpA3aI/npwj3nDMKk308pvnhoSzkW3PXlbgHAzfx0yHWnPPVUjPhXFtLJzgKZge8lsfkUxvYSQ3X2OYIFU6A== + dependencies: + "@babel/runtime" "^7.3.1" + hyphenate-style-name "^1.0.3" + jss "10.6.0" + +jss-plugin-default-unit@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.6.0.tgz#af47972486819b375f0f3a9e0213403a84b5ef3b" + integrity sha512-7y4cAScMHAxvslBK2JRK37ES9UT0YfTIXWgzUWD5euvR+JR3q+o8sQKzBw7GmkQRfZijrRJKNTiSt1PBsLI9/w== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.6.0" + +jss-plugin-global@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.6.0.tgz#3e8011f760f399cbadcca7f10a485b729c50e3ed" + integrity sha512-I3w7ji/UXPi3VuWrTCbHG9rVCgB4yoBQLehGDTmsnDfXQb3r1l3WIdcO8JFp9m0YMmyy2CU7UOV6oPI7/Tmu+w== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.6.0" + +jss-plugin-nested@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.6.0.tgz#5f83c5c337d3b38004834e8426957715a0251641" + integrity sha512-fOFQWgd98H89E6aJSNkEh2fAXquC9aZcAVjSw4q4RoQ9gU++emg18encR4AT4OOIFl4lQwt5nEyBBRn9V1Rk8g== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.6.0" + tiny-warning "^1.0.2" + +jss-plugin-props-sort@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.6.0.tgz#297879f35f9fe21196448579fee37bcde28ce6bc" + integrity sha512-oMCe7hgho2FllNc60d9VAfdtMrZPo9n1Iu6RNa+3p9n0Bkvnv/XX5San8fTPujrTBScPqv9mOE0nWVvIaohNuw== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.6.0" + +jss-plugin-rule-value-function@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.6.0.tgz#3c1a557236a139d0151e70a82c810ccce1c1c5ea" + integrity sha512-TKFqhRTDHN1QrPTMYRlIQUOC2FFQb271+AbnetURKlGvRl/eWLswcgHQajwuxI464uZk91sPiTtdGi7r7XaWfA== + dependencies: + "@babel/runtime" "^7.3.1" + jss "10.6.0" + tiny-warning "^1.0.2" + +jss-plugin-vendor-prefixer@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.6.0.tgz#e1fcd499352846890c38085b11dbd7aa1c4f2c78" + integrity sha512-doJ7MouBXT1lypLLctCwb4nJ6lDYqrTfVS3LtXgox42Xz0gXusXIIDboeh6UwnSmox90QpVnub7au8ybrb0krQ== + dependencies: + "@babel/runtime" "^7.3.1" + css-vendor "^2.0.8" + jss "10.6.0" + +jss@10.6.0, jss@^10.5.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/jss/-/jss-10.6.0.tgz#d92ff9d0f214f65ca1718591b68e107be4774149" + integrity sha512-n7SHdCozmxnzYGXBHe0NsO0eUf9TvsHVq2MXvi4JmTn3x5raynodDVE/9VQmBdWFyyj9HpHZ2B4xNZ7MMy7lkw== + dependencies: + "@babel/runtime" "^7.3.1" + csstype "^3.0.2" + indefinite-observable "^2.0.1" + is-in-browser "^1.1.3" + tiny-warning "^1.0.2" + "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" @@ -11048,6 +11332,11 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" +kdbush@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" + integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== + keygrip@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226" @@ -11758,6 +12047,35 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +mapbox-gl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-2.2.0.tgz#f0228a251c5fb733a341528be0114612bfc5a982" + integrity sha512-/9OQjaOIpQcZfXMFMxnjjAjhVGPjuAxQFAbdvG6CXD5aLhm1j2D3zxCCfxxMCEuILRBCbkxEAhVf3JoT+aFXEQ== + dependencies: + "@mapbox/geojson-rewind" "^0.5.0" + "@mapbox/geojson-types" "^1.0.2" + "@mapbox/jsonlint-lines-primitives" "^2.0.2" + "@mapbox/mapbox-gl-supported" "^2.0.0" + "@mapbox/point-geometry" "^0.1.0" + "@mapbox/tiny-sdf" "^1.2.5" + "@mapbox/unitbezier" "^0.0.0" + "@mapbox/vector-tile" "^1.3.1" + "@mapbox/whoots-js" "^3.1.0" + csscolorparser "~1.0.3" + earcut "^2.2.2" + geojson-vt "^3.2.1" + gl-matrix "^3.3.0" + grid-index "^1.1.0" + minimist "^1.2.5" + murmurhash-js "^1.0.0" + pbf "^3.2.1" + potpack "^1.0.1" + quickselect "^2.0.0" + rw "^1.3.3" + supercluster "^7.1.2" + tinyqueue "^2.0.3" + vt-pbf "^3.1.1" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -12194,6 +12512,11 @@ multimatch@^5.0.0: arrify "^2.0.1" minimatch "^3.0.4" +murmurhash-js@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51" + integrity sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E= + mute-stream@0.0.8, mute-stream@~0.0.4: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -13400,6 +13723,14 @@ path@^0.12.7: process "^0.11.1" util "^0.10.3" +pbf@^3.0.5, pbf@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.2.1.tgz#b4c1b9e72af966cd82c6531691115cc0409ffe2a" + integrity sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ== + dependencies: + ieee754 "^1.1.12" + resolve-protobuf-schema "^2.1.0" + pbkdf2@^3.0.3: version "3.1.1" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" @@ -13510,6 +13841,11 @@ pnp-webpack-plugin@1.6.4: dependencies: ts-pnp "^1.1.6" +popper.js@1.16.1-lts: + version "1.16.1-lts" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05" + integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA== + portfinder@^1.0.26: version "1.0.28" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" @@ -14244,6 +14580,11 @@ postcss@^8.1.0, postcss@^8.1.7: nanoid "^3.1.20" source-map "^0.6.1" +potpack@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.1.tgz#d1b1afd89e4c8f7762865ec30bd112ab767e2ebf" + integrity sha512-15vItUAbViaYrmaB/Pbw7z6qX2xENbFSTA7Ii4tgbPtasxm5v6ryKhKtL91tpWovDJzTiZqdwzhcFBCwiMVdVw== + precinct@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/precinct/-/precinct-7.1.0.tgz#a0311e0b59029647eaf57c2d30b8efa9c85d129a" @@ -14405,6 +14746,11 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= +protocol-buffers-schema@^3.3.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.5.1.tgz#8388e768d383ac8cbea23e1280dfadb79f4122ad" + integrity sha512-YVCvdhxWNDP8/nJDyXLuM+UFsuPk4+1PB7WGPVDzm3HTHbzFLxQYeW2iZpS4mmnXrQJGBzt230t/BbEb7PrQaw== + protocols@^1.1.0, protocols@^1.4.0: version "1.4.8" resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8" @@ -14562,6 +14908,11 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +quickselect@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018" + integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw== + raf@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" @@ -14680,7 +15031,7 @@ react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1: +"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== @@ -14737,6 +15088,16 @@ react-router@5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-transition-group@^4.4.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" + integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -15319,6 +15680,13 @@ resolve-pathname@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== +resolve-protobuf-schema@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz#9ca9a9e69cf192bbdaf1006ec1973948aa4a3758" + integrity sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ== + dependencies: + protocol-buffers-schema "^3.3.1" + resolve-url-loader@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz#235e2c28e22e3e432ba7a5d4e305c59a58edfc08" @@ -15511,6 +15879,11 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" +rw@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= + rxjs@^6.3.3, rxjs@^6.6.0: version "6.6.6" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.6.tgz#14d8417aa5a07c5e633995b525e1e3c0dec03b70" @@ -16588,6 +16961,13 @@ subscriptions-transport-ws@^0.9.11: symbol-observable "^1.0.4" ws "^5.2.0" +supercluster@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.2.tgz#cf02a60283a0118212024f3bf02e4e63bb148e2c" + integrity sha512-bGA0pk3DYMjLTY1h+rbh0imi/I8k/Lg0rzdBGfyQs0Xkiix7jK2GUmH1qSD8+jq6U0Vu382QHr3+rbbiHqdKJA== + dependencies: + kdbush "^3.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -16660,7 +17040,7 @@ swap-case@^2.0.1: dependencies: tslib "^2.0.3" -symbol-observable@^1.0.4, symbol-observable@^1.1.0: +symbol-observable@1.2.0, symbol-observable@^1.0.4, symbol-observable@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== @@ -16904,11 +17284,16 @@ tiny-invariant@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== -tiny-warning@^1.0.0, tiny-warning@^1.0.3: +tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== +tinyqueue@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08" + integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA== + title-case@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982" @@ -17671,6 +18056,15 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== +vt-pbf@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/vt-pbf/-/vt-pbf-3.1.1.tgz#b0f627e39a10ce91d943b898ed2363d21899fb82" + integrity sha512-pHjWdrIoxurpmTcbfBWXaPwSmtPAHS105253P1qyEfSTV2HJddqjM+kIHquaT/L6lVJIk9ltTGc0IxR/G47hYA== + dependencies: + "@mapbox/point-geometry" "0.1.0" + "@mapbox/vector-tile" "^1.3.1" + pbf "^3.0.5" + w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" From 57b98e0777118473d09057d308637e6b26d3ffde Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 18:21:07 +0100 Subject: [PATCH 11/25] [FE] Add 'AddMarina' form (without mutation) --- packages/fe/public/index.html | 5 +- packages/fe/src/App.tsx | 10 +- .../AddMarinaAmenitiesQuery.graphql.ts | 85 ++++++ packages/fe/src/routes.ts | 8 +- .../scenes/AddMarina/add-marina.module.scss | 8 + .../src/scenes/AddMarina/add-marina.scss.d.ts | 2 + packages/fe/src/scenes/AddMarina/index.tsx | 245 ++++++++++++++++++ 7 files changed, 353 insertions(+), 10 deletions(-) create mode 100644 packages/fe/src/__generated__/AddMarinaAmenitiesQuery.graphql.ts create mode 100644 packages/fe/src/scenes/AddMarina/add-marina.module.scss create mode 100644 packages/fe/src/scenes/AddMarina/add-marina.scss.d.ts create mode 100644 packages/fe/src/scenes/AddMarina/index.tsx diff --git a/packages/fe/public/index.html b/packages/fe/public/index.html index aa069f2..c9ccacf 100644 --- a/packages/fe/public/index.html +++ b/packages/fe/public/index.html @@ -24,7 +24,10 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + + + + Seasy Test Project diff --git a/packages/fe/src/App.tsx b/packages/fe/src/App.tsx index 99fa56e..006e9f4 100644 --- a/packages/fe/src/App.tsx +++ b/packages/fe/src/App.tsx @@ -1,12 +1,13 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; -import MarinaList from "./scenes/MarinaList"; -import MarinaDetail from "./scenes/MarinaDetail"; -import NotFound from "./scenes/NotFound"; - import { Routes } from "routes"; +import MarinaList from "scenes/MarinaList"; +import MarinaDetail from "scenes/MarinaDetail"; +import NotFound from "scenes/NotFound"; +import AddMarina from "scenes/AddMarina"; + import styles from './App.module.scss'; function App() { @@ -15,6 +16,7 @@ function App() { +
diff --git a/packages/fe/src/__generated__/AddMarinaAmenitiesQuery.graphql.ts b/packages/fe/src/__generated__/AddMarinaAmenitiesQuery.graphql.ts new file mode 100644 index 0000000..ef025bf --- /dev/null +++ b/packages/fe/src/__generated__/AddMarinaAmenitiesQuery.graphql.ts @@ -0,0 +1,85 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from "relay-runtime"; +export type AddMarinaAmenitiesQueryVariables = {}; +export type AddMarinaAmenitiesQueryResponse = { + readonly amenities: ReadonlyArray<{ + readonly id: string; + readonly code: string; + }> | null; +}; +export type AddMarinaAmenitiesQuery = { + readonly response: AddMarinaAmenitiesQueryResponse; + readonly variables: AddMarinaAmenitiesQueryVariables; +}; + + + +/* +query AddMarinaAmenitiesQuery { + amenities { + id + code + } +} +*/ + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "alias": null, + "args": null, + "concreteType": "Amenity", + "kind": "LinkedField", + "name": "amenities", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "code", + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "AddMarinaAmenitiesQuery", + "selections": (v0/*: any*/), + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "AddMarinaAmenitiesQuery", + "selections": (v0/*: any*/) + }, + "params": { + "cacheID": "270e6a2e1e36a974eb16a4ce5bfb2e54", + "id": null, + "metadata": {}, + "name": "AddMarinaAmenitiesQuery", + "operationKind": "query", + "text": "query AddMarinaAmenitiesQuery {\n amenities {\n id\n code\n }\n}\n" + } +}; +})(); +(node as any).hash = '92b725d718b47a30b8135f8b6f36eeb8'; +export default node; diff --git a/packages/fe/src/routes.ts b/packages/fe/src/routes.ts index 864d776..05a9fe5 100644 --- a/packages/fe/src/routes.ts +++ b/packages/fe/src/routes.ts @@ -1,14 +1,12 @@ export class Routes { static MARINA_LIST = "/marina-list"; static MARINA_DETAIL = "/marina-detail/:id"; + static ADD_MARINA = "/add-marina"; static getTo = (route: string, args: { id?: string }) => { - if (route === Routes.MARINA_LIST) { - return Routes.MARINA_LIST; - } else if (route === Routes.MARINA_DETAIL) { + if (route === Routes.MARINA_DETAIL) { return `/marina-detail/${args.id}/`; } - - throw Error(`invalid route ${route}`); + return route; }; } diff --git a/packages/fe/src/scenes/AddMarina/add-marina.module.scss b/packages/fe/src/scenes/AddMarina/add-marina.module.scss new file mode 100644 index 0000000..4d8ce8b --- /dev/null +++ b/packages/fe/src/scenes/AddMarina/add-marina.module.scss @@ -0,0 +1,8 @@ +.inputMargin { + margin-top: 20px; +} + +.map { + min-height: 400px; + margin-top: 30px; +} diff --git a/packages/fe/src/scenes/AddMarina/add-marina.scss.d.ts b/packages/fe/src/scenes/AddMarina/add-marina.scss.d.ts new file mode 100644 index 0000000..fc4f0f2 --- /dev/null +++ b/packages/fe/src/scenes/AddMarina/add-marina.scss.d.ts @@ -0,0 +1,2 @@ +export const inputMargin: string; +export const map: string; diff --git a/packages/fe/src/scenes/AddMarina/index.tsx b/packages/fe/src/scenes/AddMarina/index.tsx new file mode 100644 index 0000000..efb8937 --- /dev/null +++ b/packages/fe/src/scenes/AddMarina/index.tsx @@ -0,0 +1,245 @@ +import React, { useEffect, useRef, useState } from "react"; +import mapboxgl from "mapbox-gl"; +import { + TextField, + Button, + FormGroup, + FormControlLabel, + Checkbox, + CircularProgress, + FormLabel, +} from "@material-ui/core"; +import { useQuery } from "relay-hooks"; +import graphql from "babel-plugin-relay/macro"; +import { Add, CheckCircleOutlineOutlined } from "@material-ui/icons"; +import "mapbox-gl/dist/mapbox-gl.css"; +import { AddMarinaAmenitiesQuery } from "__generated__/AddMarinaAmenitiesQuery.graphql"; + +import styles from "./add-marina.module.scss"; + +const MARINA_KREMIK_LON_LAT: [number, number] = [15.9379, 43.5696]; +const MAPBOX_WEBSERVICES_URL = "https://api.mapbox.com"; +const ACCESS_TOKEN_MAPBOX = + "pk.eyJ1Ijoib2h1c2FyIiwiYSI6ImNrbXJ1bDRyMzBia2IycHJzbmdpbjVobWYifQ.7EthsV5t9R6ve15oUewRjQ"; + +const getCityCountryFromLonLat = async (lon: number, lat: number) => { + const url = `${MAPBOX_WEBSERVICES_URL}/geocoding/v5/mapbox.places/${lon},${lat}.json?access_token=${ACCESS_TOKEN_MAPBOX}`; + const response = (await (await fetch(url)).json()) as any; + + let city = "", + country = ""; + + if (response.features && response.features.length) { + (response.features as Array).forEach((feature: any) => { + if ( + !city && + (feature["place_type"].indexOf("place") !== -1 || + feature["place_type"].indexOf("region") !== -1) + ) { + city = feature.text; + } + }); + + (response.features as Array).reverse().forEach((feature: any) => { + if (!country && feature["place_type"].indexOf("country") !== -1) { + country = feature.text; + } + }); + } + return [city, country]; +}; + +interface FormState { + name: string; + photoUrl: string; + amenities: Set; + city: string; + country: string; +} + +export default function AddMarina() { + const mapElementRef = useRef(null); + const map = useRef(); + const locationMarker = useRef(); + + const [loading, setLoading] = useState(false); + const [submitted, setSubmitted] = useState(false); + const [formState, setFormState] = useState({ + amenities: new Set(), + name: "", + photoUrl: "", + city: "", + country: "", + }); + + const handleInputChange = (event: React.ChangeEvent) => { + const target = event.target; + const value = target.type === "checkbox" ? target.checked : target.value; + const name = target.name; + + setFormState({ + ...formState, + [name]: value, + }); + }; + + const handleAmenityChange = (code: string) => { + const amenities = new Set(formState.amenities); + + if (amenities.has(code)) { + amenities.delete(code); + } else { + amenities.add(code); + } + + setFormState({ + ...formState, + amenities, + }); + }; + + const { data } = useQuery( + graphql` + query AddMarinaAmenitiesQuery { + amenities { + id + code + } + } + `, + {}, + { + fetchPolicy: "store-and-network", + onComplete: () => {}, + } + ); + + useEffect(() => { + if (!map.current) { + mapboxgl.accessToken = ACCESS_TOKEN_MAPBOX; + map.current = new mapboxgl.Map({ + style: "mapbox://styles/mapbox/streets-v11", // style URL + container: mapElementRef.current?.id || "mapElementId", // container ID + center: MARINA_KREMIK_LON_LAT, // starting position [lng, lat] + zoom: 12, // starting zoom + }); + + locationMarker.current = new mapboxgl.Marker({ + draggable: true, + }) + .setLngLat(MARINA_KREMIK_LON_LAT) + .addTo(map.current); + + locationMarker.current.on('dragend', async (e) => { + if (locationMarker.current) { + const lngLat = locationMarker.current.getLngLat(); + + const [city, country] = await getCityCountryFromLonLat(lngLat.lng, lngLat.lat); + + setFormState(prev => ({ + ...prev, + city, + country + })); + + } + }); + } + + return () => map.current && map.current.remove(); + }, []); + + const submit = () => { + setLoading(true); + setTimeout(() => { + setLoading(false); + setSubmitted(true); + }, 2000); + }; + + return ( + <> +

Add Marina

+
+ + + + + Amenities + + + {data?.amenities?.map((amenity) => ( + handleAmenityChange(amenity.code)} + name={amenity.code} + color="primary" + /> + } + label={amenity.code} + /> + ))} + + + + + +
+ + + + ); +} From 8d525aca9e7f2f8775312b9df746fb8a004676a3 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 18:21:45 +0100 Subject: [PATCH 12/25] [BE] Add country resolver to City node --- packages/be/src/schemas/city.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/be/src/schemas/city.ts b/packages/be/src/schemas/city.ts index c1e2c76..39f6b48 100644 --- a/packages/be/src/schemas/city.ts +++ b/packages/be/src/schemas/city.ts @@ -2,6 +2,7 @@ import { gql, IResolverObject } from "apollo-server-koa"; import { toGlobalId } from "graphql-relay"; import { CityResolvers } from "../types/GeneratedGql"; +import { getCountryBase } from "../db/country"; export const TYPE = "City"; @@ -16,5 +17,9 @@ export const schema = gql` `; export const resolver: CityResolvers = { - id: ({ code }) => toGlobalId(TYPE, code) + id: ({ code }) => toGlobalId(TYPE, code), + country: ({ countryCode }) => + getCountryBase() + .where("code", countryCode) + .first(), }; From cdb794ba029f2b7e614b001c60514cbf438507d6 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 18:26:12 +0100 Subject: [PATCH 13/25] [BE] Add yup --- packages/be/package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/be/package.json b/packages/be/package.json index 78cf993..441bd6f 100644 --- a/packages/be/package.json +++ b/packages/be/package.json @@ -21,6 +21,7 @@ "dependencies": { "@babel/runtime": "^7.13.10", "@koa/cors": "^3.1.0", + "@types/yup": "^0.29.11", "apollo-server-koa": "^2.21.2", "cross-env": "^7.0.3", "dotenv-safe": "^8.2.0", diff --git a/yarn.lock b/yarn.lock index d30ed6f..a06792b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3878,6 +3878,11 @@ dependencies: "@types/yargs-parser" "*" +"@types/yup@^0.29.11": + version "0.29.11" + resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.11.tgz#d654a112973f5e004bf8438122bd7e56a8e5cd7e" + integrity sha512-9cwk3c87qQKZrT251EDoibiYRILjCmxBvvcb4meofCmx1vdnNcR9gyildy5vOHASpOKMsn42CugxUvcwK5eu1g== + "@types/zen-observable@^0.8.0": version "0.8.2" resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.2.tgz#808c9fa7e4517274ed555fa158f2de4b4f468e71" From 8a1b5b028c005176cc4de2aa6ce8f0f1f2db86e6 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 18:52:20 +0100 Subject: [PATCH 14/25] [BE] Add AddMarina mutation & Yup validation --- packages/be/src/mutations/marina.ts | 25 ++++++++++++++++++++++--- packages/be/src/schemas/marina.ts | 11 ++++++++--- packages/be/src/types/GeneratedGql.ts | 20 +++++++++++++------- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/packages/be/src/mutations/marina.ts b/packages/be/src/mutations/marina.ts index 5ae1e29..ce55f21 100644 --- a/packages/be/src/mutations/marina.ts +++ b/packages/be/src/mutations/marina.ts @@ -1,15 +1,34 @@ import { ApolloError } from "apollo-server-koa"; import { fromGlobalId } from "graphql-relay"; +import * as Yup from "yup"; import { ApolloContext } from "../types/ApolloContext"; import { MutationAddMarinaArgs } from "../types/GeneratedGql"; +const schemaValidation = Yup.object().shape({ + name: Yup.string() + .min(3) + .required("Required"), + photoUrl: Yup.string().url(), + lat: Yup.number().required("Required"), + lon: Yup.number().required("Required"), + city: Yup.string() + .min(3) + .required("Required"), + country: Yup.string() + .min(3) + .required("Required"), + amenities: Yup.array() + .of(Yup.string().min(3)) + .required("Required"), +}); + export const addMarina = async ( parent: unknown, { input }: MutationAddMarinaArgs, ctx: ApolloContext ) => { - const dbId = fromGlobalId(input.id); - - // TODO: + schemaValidation.isValid(input).then((result) => { + console.log(result); + }); }; diff --git a/packages/be/src/schemas/marina.ts b/packages/be/src/schemas/marina.ts index 0aa0753..d2a4c23 100644 --- a/packages/be/src/schemas/marina.ts +++ b/packages/be/src/schemas/marina.ts @@ -21,8 +21,13 @@ export const schema = gql` } input Add${TYPE}Input { - id: ID! - # TODO + name: String! + photoUrl: String + lat: Float! + lon: Float! + city: String! + country: String! + amenities: [String!] } type ${TYPE}Payload { @@ -40,5 +45,5 @@ export const resolver: MarinaResolvers = { getCountryBase() .where({ code: countryCode }) .first(), - amenities: ({ id }) => getAmenityByMarinaId(id) + amenities: ({ id }) => getAmenityByMarinaId(id), }; diff --git a/packages/be/src/types/GeneratedGql.ts b/packages/be/src/types/GeneratedGql.ts index eba9267..4bfe19b 100644 --- a/packages/be/src/types/GeneratedGql.ts +++ b/packages/be/src/types/GeneratedGql.ts @@ -18,7 +18,13 @@ export type Scalars = { export type AddMarinaInput = { - id: Scalars['ID']; + name: Scalars['String']; + photoUrl?: Maybe; + lat: Scalars['Float']; + lon: Scalars['Float']; + city: Scalars['String']; + country: Scalars['String']; + amenities?: Maybe>; }; export type Amenity = Node & { @@ -186,12 +192,12 @@ export type DirectiveResolverFn; - Amenity: ResolverTypeWrapper; String: ResolverTypeWrapper; + Float: ResolverTypeWrapper; + Amenity: ResolverTypeWrapper; + ID: ResolverTypeWrapper; CacheControlScope: CacheControlScope; City: ResolverTypeWrapper; - Float: ResolverTypeWrapper; Country: ResolverTypeWrapper; Marina: ResolverTypeWrapper; MarinaPayload: ResolverTypeWrapper & { marina?: Maybe }>; @@ -207,11 +213,11 @@ export type ResolversTypes = { /** Mapping between all available schema types and the resolvers parents */ export type ResolversParentTypes = { AddMarinaInput: AddMarinaInput; - ID: Scalars['ID']; - Amenity: amenityDb; String: Scalars['String']; - City: cityDb; Float: Scalars['Float']; + Amenity: amenityDb; + ID: Scalars['ID']; + City: cityDb; Country: countryDb; Marina: marinaDb; MarinaPayload: Omit & { marina?: Maybe }; From 98d7e48af0aa1d391b2a9a9f5728a773b84fa766 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sat, 27 Mar 2021 18:53:16 +0100 Subject: [PATCH 15/25] [FE] Actually send mutation on submit button click --- packages/fe/schema.graphql | 8 +- .../AddMarinaMutation.graphql.ts | 117 ++++++++++++++++++ packages/fe/src/scenes/AddMarina/index.tsx | 19 ++- packages/fe/src/types/GeneratedGQL.ts | 8 +- 4 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 packages/fe/src/__generated__/AddMarinaMutation.graphql.ts diff --git a/packages/fe/schema.graphql b/packages/fe/schema.graphql index e9d44f9..697676f 100644 --- a/packages/fe/schema.graphql +++ b/packages/fe/schema.graphql @@ -1,7 +1,13 @@ directive @cacheControl(maxAge: Int, scope: CacheControlScope) on FIELD_DEFINITION | OBJECT | INTERFACE input AddMarinaInput { - id: ID! + name: String! + photoUrl: String + lat: Float! + lon: Float! + city: String! + country: String! + amenities: [String!] } type Amenity implements Node { diff --git a/packages/fe/src/__generated__/AddMarinaMutation.graphql.ts b/packages/fe/src/__generated__/AddMarinaMutation.graphql.ts new file mode 100644 index 0000000..0a250c0 --- /dev/null +++ b/packages/fe/src/__generated__/AddMarinaMutation.graphql.ts @@ -0,0 +1,117 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from "relay-runtime"; +export type AddMarinaInput = { + name: string; + photoUrl?: string | null; + lat: number; + lon: number; + city: string; + country: string; + amenities?: Array | null; +}; +export type AddMarinaMutationVariables = { + input: AddMarinaInput; +}; +export type AddMarinaMutationResponse = { + readonly addMarina: { + readonly marina: { + readonly id: string; + } | null; + } | null; +}; +export type AddMarinaMutation = { + readonly response: AddMarinaMutationResponse; + readonly variables: AddMarinaMutationVariables; +}; + + + +/* +mutation AddMarinaMutation( + $input: AddMarinaInput! +) { + addMarina(input: $input) { + marina { + id + } + } +} +*/ + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "MarinaPayload", + "kind": "LinkedField", + "name": "addMarina", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Marina", + "kind": "LinkedField", + "name": "marina", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "AddMarinaMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "AddMarinaMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "1b51e16d567211a0f71abb9f1b210737", + "id": null, + "metadata": {}, + "name": "AddMarinaMutation", + "operationKind": "mutation", + "text": "mutation AddMarinaMutation(\n $input: AddMarinaInput!\n) {\n addMarina(input: $input) {\n marina {\n id\n }\n }\n}\n" + } +}; +})(); +(node as any).hash = '4e17c4add4ebfb5ad685a76298bb91b7'; +export default node; diff --git a/packages/fe/src/scenes/AddMarina/index.tsx b/packages/fe/src/scenes/AddMarina/index.tsx index efb8937..bc9a053 100644 --- a/packages/fe/src/scenes/AddMarina/index.tsx +++ b/packages/fe/src/scenes/AddMarina/index.tsx @@ -9,13 +9,14 @@ import { CircularProgress, FormLabel, } from "@material-ui/core"; -import { useQuery } from "relay-hooks"; +import { useMutation, useQuery } from "relay-hooks"; import graphql from "babel-plugin-relay/macro"; import { Add, CheckCircleOutlineOutlined } from "@material-ui/icons"; import "mapbox-gl/dist/mapbox-gl.css"; import { AddMarinaAmenitiesQuery } from "__generated__/AddMarinaAmenitiesQuery.graphql"; import styles from "./add-marina.module.scss"; +import { AddMarinaMutation } from "__generated__/AddMarinaMutation.graphql"; const MARINA_KREMIK_LON_LAT: [number, number] = [15.9379, 43.5696]; const MAPBOX_WEBSERVICES_URL = "https://api.mapbox.com"; @@ -62,7 +63,6 @@ export default function AddMarina() { const map = useRef(); const locationMarker = useRef(); - const [loading, setLoading] = useState(false); const [submitted, setSubmitted] = useState(false); const [formState, setFormState] = useState({ amenities: new Set(), @@ -114,6 +114,16 @@ export default function AddMarina() { } ); + const [commit, loading] = useMutation(graphql` + mutation AddMarinaMutation($input: AddMarinaInput!) { + addMarina(input: $input) { + marina { + id + } + } + } + `); + useEffect(() => { if (!map.current) { mapboxgl.accessToken = ACCESS_TOKEN_MAPBOX; @@ -150,9 +160,10 @@ export default function AddMarina() { }, []); const submit = () => { - setLoading(true); + const lngLat = locationMarker.current!.getLngLat(); + + commit({variables: {input: {...formState, amenities: Array.from(formState.amenities), lon: lngLat.lng, lat: lngLat.lat}}}); setTimeout(() => { - setLoading(false); setSubmitted(true); }, 2000); }; diff --git a/packages/fe/src/types/GeneratedGQL.ts b/packages/fe/src/types/GeneratedGQL.ts index f6f26e6..b1d3cdb 100644 --- a/packages/fe/src/types/GeneratedGQL.ts +++ b/packages/fe/src/types/GeneratedGQL.ts @@ -13,7 +13,13 @@ export type Scalars = { export type AddMarinaInput = { - id: Scalars['ID']; + name: Scalars['String']; + photoUrl?: Maybe; + lat: Scalars['Float']; + lon: Scalars['Float']; + city: Scalars['String']; + country: Scalars['String']; + amenities?: Maybe>; }; export type Amenity = Node & { From 66358eb7e4efeb81ecda729302b73cee1afb85bf Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sun, 28 Mar 2021 16:31:25 +0200 Subject: [PATCH 16/25] [BE] Add resolver that saves marina to db --- packages/be/src/db/kx.ts | 2 +- packages/be/src/db/marina.ts | 67 ++++++++++++++++++++++++++++- packages/be/src/mutations/marina.ts | 25 +++++++++-- packages/be/src/root.ts | 2 +- packages/be/src/schemas/city.ts | 4 +- packages/be/src/schemas/marina.ts | 2 + 6 files changed, 93 insertions(+), 9 deletions(-) diff --git a/packages/be/src/db/kx.ts b/packages/be/src/db/kx.ts index 9ebd8c4..a808eb5 100644 --- a/packages/be/src/db/kx.ts +++ b/packages/be/src/db/kx.ts @@ -18,7 +18,7 @@ const kxConfig = { const kx = Knex(knexStringcase(kxConfig)); kx.on("query-response", res => { - // console.log(res); + console.log(res); }); export default kx; diff --git a/packages/be/src/db/marina.ts b/packages/be/src/db/marina.ts index 7394c0d..71f697f 100644 --- a/packages/be/src/db/marina.ts +++ b/packages/be/src/db/marina.ts @@ -1,5 +1,6 @@ -import { marinaDb } from "../types/GeneratedDb"; +import { marinaDb, cityDb, countryDb, photoDb } from "../types/GeneratedDb"; import kx from "./kx"; +import { MutationAddMarinaArgs } from "../types/GeneratedGql"; export const getMarinaById = (id: number) => { return getMarinaBase() @@ -10,3 +11,67 @@ export const getMarinaById = (id: number) => { export const getMarinaBase = () => { return kx("marina"); }; + +const getOrCreateEntity = ( + tableName: string, + whereArgName: string, + whereArgValue: string, + extraDataToInsert: Partial +) => { + return kx + .transaction((trx) => { + trx(tableName) + .where(whereArgName, whereArgValue) + .then((res) => { + if (res.length === 0) { + return kx(tableName) + .transacting(trx) + .insert({ [whereArgName]: whereArgValue, ...extraDataToInsert }) + + .then(() => { + return trx(tableName).where(whereArgName, whereArgValue); + }); + } else { + return res; + } + }) + .then(trx.commit) + .catch(trx.rollback); + }) + .then((res) => { + return res[0]; + }); +}; + +export const saveMarinaToDb = async (input: MutationAddMarinaArgs["input"]) => { + const countryPromise = getOrCreateEntity( + "country", + "code", + input.country, + {} + ); + const cityPromise = getOrCreateEntity("city", "code", input.city, { + countryCode: input.country, + lat: input.lat, + lon: input.lon, + }); + let photoPromise: Promise = Promise.resolve(null); + if (input.photoUrl) { + photoPromise = getOrCreateEntity("photo", "url", input.photoUrl, { + url: input.photoUrl, + }); + } + + return Promise.all([countryPromise, cityPromise, photoPromise]).then( + ([country, city, photo]) => { + return kx("marina").insert({ + name: input.name, + cityCode: city.code, + lat: city.lat, + lon: city.lon, + countryCode: country.code, + photoId: photo ? photo.id : undefined, + }); + } + ); +}; diff --git a/packages/be/src/mutations/marina.ts b/packages/be/src/mutations/marina.ts index ce55f21..41884d0 100644 --- a/packages/be/src/mutations/marina.ts +++ b/packages/be/src/mutations/marina.ts @@ -2,6 +2,8 @@ import { ApolloError } from "apollo-server-koa"; import { fromGlobalId } from "graphql-relay"; import * as Yup from "yup"; +import { getMarinaBase, saveMarinaToDb } from "../db/marina"; + import { ApolloContext } from "../types/ApolloContext"; import { MutationAddMarinaArgs } from "../types/GeneratedGql"; @@ -28,7 +30,24 @@ export const addMarina = async ( { input }: MutationAddMarinaArgs, ctx: ApolloContext ) => { - schemaValidation.isValid(input).then((result) => { - console.log(result); - }); + return schemaValidation + .isValid(input) + .then((isValid) => { + if (isValid) { + return saveMarinaToDb(input); + } else { + // return ApolloError (?) + } + }) + .then(async (result) => { + if (result && result.length) { + const marina = await getMarinaBase() + .where({ id: result[0] }) + .first(); + return { marina }; + } + }) + .catch((e) => { + console.log("ERROR", e); + }); }; diff --git a/packages/be/src/root.ts b/packages/be/src/root.ts index 0a82737..dc43966 100644 --- a/packages/be/src/root.ts +++ b/packages/be/src/root.ts @@ -11,7 +11,7 @@ import * as marina from "./schemas/marina"; import * as photo from "./schemas/photo"; import * as amenity from "./schemas/amenity"; import * as country from "./schemas/country"; -// import * as mutation from "./mutation"; + import * as node from "./query/node"; const typeDefs: DocumentNode[] = [ diff --git a/packages/be/src/schemas/city.ts b/packages/be/src/schemas/city.ts index 39f6b48..b2b8977 100644 --- a/packages/be/src/schemas/city.ts +++ b/packages/be/src/schemas/city.ts @@ -19,7 +19,5 @@ export const schema = gql` export const resolver: CityResolvers = { id: ({ code }) => toGlobalId(TYPE, code), country: ({ countryCode }) => - getCountryBase() - .where("code", countryCode) - .first(), + getCountryBase().where("code", countryCode).first(), }; diff --git a/packages/be/src/schemas/marina.ts b/packages/be/src/schemas/marina.ts index d2a4c23..4f1ef55 100644 --- a/packages/be/src/schemas/marina.ts +++ b/packages/be/src/schemas/marina.ts @@ -5,6 +5,7 @@ import { MarinaResolvers } from "../types/GeneratedGql"; import { getCityBase } from "../db/city"; import { getCountryBase } from "../db/country"; import { getAmenityByMarinaId } from "../db/amenity"; +import { getPhotoBase } from "../db/photo"; export const TYPE = "Marina"; @@ -46,4 +47,5 @@ export const resolver: MarinaResolvers = { .where({ code: countryCode }) .first(), amenities: ({ id }) => getAmenityByMarinaId(id), + photo: ({photoId}) => photoId ? getPhotoBase().where({id: photoId}).first() : null }; From 770bf8342306e8ddfa6f9ec211bd40db7222366f Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sun, 28 Mar 2021 16:50:47 +0200 Subject: [PATCH 17/25] [FE] Show success/error snackbar & redirect to marina --- packages/be/seasy.db | Bin 53248 -> 53248 bytes packages/fe/package.json | 1 + packages/fe/src/scenes/AddMarina/index.tsx | 112 +++++++++++++++++---- yarn.lock | 11 ++ 4 files changed, 104 insertions(+), 20 deletions(-) diff --git a/packages/be/seasy.db b/packages/be/seasy.db index 6bc9b5a43b03a19778b343bee6bb7f08c0a7bf34..db3f7fcb7967675be22354d5b91e3c5c85a1925a 100644 GIT binary patch delta 1304 zcmbW1PfXKL9LHPQZv5H$0F8e#;}D~S!`5~T1{&ffpdb#BO#}{(b}QY3wQsa#{JB*0 zYT}+??p8X6fN8?c6-^>My{cQ`F2DYML}pJfu03NORB zM-~&1_rw#5S65-0Ph_Bm)b+~if0%UAKsxduhO1}l{0z%6^?oLq;Z+aRQoNF|Djyne z<~Ww!pKNY0XRVo=-c6F&xzNvEsbrm3LP$!9W16VOk)n%=zTP9PqKQdH=&BbwraIC? zs6EzmGBe=m4-b#E@p99N@l0Q9v`vk`(SebUWauOt?TduRBKjdz`Dzht6}(+36Q0vpIn<{ElL5uiIs}<1I)|r(m4d#Y7L1(3E0+ zx2VWG2|n2NG?n|fSQTWJ9HaSL-L|^2VytGTMTL14DtuK`MG{oM&l}LAE3bXVQjp#H z^6s>giOVvO&*1A^Tb{1et~QYrUrjsX&R@a?fvc zrz*;Dq+5kDn!9c8_y+jA_8oiN^!nkZJoh$828s)SMnTeEfGh~wHVYbmo252O{tc4< z1r`O#{INqfsvM4zB4Rt9Ht!j-Wk1EXI7qOFfv;c{tboOQ#o#f;6xc`M!}5(null); const map = useRef(); const locationMarker = useRef(); - const [submitted, setSubmitted] = useState(false); + const history = useHistory(); + + const [submitState, setSubmitState] = useState({ + submitted: false, + sucess: false, + error: null, + }); const [formState, setFormState] = useState({ amenities: new Set(), name: "", @@ -114,7 +131,7 @@ export default function AddMarina() { } ); - const [commit, loading] = useMutation(graphql` + const [commit, { loading }] = useMutation(graphql` mutation AddMarinaMutation($input: AddMarinaInput!) { addMarina(input: $input) { marina { @@ -140,20 +157,22 @@ export default function AddMarina() { .setLngLat(MARINA_KREMIK_LON_LAT) .addTo(map.current); - locationMarker.current.on('dragend', async (e) => { + locationMarker.current.on("dragend", async (e) => { if (locationMarker.current) { - const lngLat = locationMarker.current.getLngLat(); - - const [city, country] = await getCityCountryFromLonLat(lngLat.lng, lngLat.lat); + const lngLat = locationMarker.current.getLngLat(); - setFormState(prev => ({ - ...prev, - city, - country - })); + const [city, country] = await getCityCountryFromLonLat( + lngLat.lng, + lngLat.lat + ); - } - }); + setFormState((prev) => ({ + ...prev, + city, + country, + })); + } + }); } return () => map.current && map.current.remove(); @@ -162,10 +181,48 @@ export default function AddMarina() { const submit = () => { const lngLat = locationMarker.current!.getLngLat(); - commit({variables: {input: {...formState, amenities: Array.from(formState.amenities), lon: lngLat.lng, lat: lngLat.lat}}}); - setTimeout(() => { - setSubmitted(true); - }, 2000); + const savePromise = commit({ + variables: { + input: { + ...formState, + amenities: Array.from(formState.amenities), + lon: lngLat.lng, + lat: lngLat.lat, + }, + }, + }); + + savePromise.then((result) => { + if (result && result.addMarina?.marina?.id) { + setSubmitState({ + submitted: true, + sucess: true, + error: null, + }); + + setTimeout(() => { + history.push( + Routes.getTo(Routes.MARINA_DETAIL, { + id: result!.addMarina!.marina!.id, + }) + ); + }, 1500); + } else { + setSubmitState({ + submitted: true, + sucess: false, + error: "There was an error creating marina.", + }); + } + }); + }; + + const handleClose = () => { + setSubmitState({ + submitted: false, + sucess: false, + error: null, + }); }; return ( @@ -225,7 +282,7 @@ export default function AddMarina() { onChange={handleInputChange} variant="outlined" label="Country" - style={{ marginTop: 15}} + style={{ marginTop: 15 }} />
@@ -235,7 +292,7 @@ export default function AddMarina() { startIcon={ loading ? ( - ) : submitted ? ( + ) : submitState.sucess ? ( ) : ( @@ -244,13 +301,28 @@ export default function AddMarina() { fullWidth={true} disabled={false} onClick={async () => { - submit(); }} > Save Marina + + + {submitState.sucess + ? "Marina successfully created, you will be redirected." + : submitState.error} + + ); } diff --git a/yarn.lock b/yarn.lock index a06792b..f0683e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2788,6 +2788,17 @@ dependencies: "@babel/runtime" "^7.4.4" +"@material-ui/lab@^4.0.0-alpha.57": + version "4.0.0-alpha.57" + resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.57.tgz#e8961bcf6449e8a8dabe84f2700daacfcafbf83a" + integrity sha512-qo/IuIQOmEKtzmRD2E4Aa6DB4A87kmY6h0uYhjUmrrgmEAgbbw9etXpWPVXuRK6AGIQCjFzV6WO2i21m1R4FCw== + dependencies: + "@babel/runtime" "^7.4.4" + "@material-ui/utils" "^4.11.2" + clsx "^1.0.4" + prop-types "^15.7.2" + react-is "^16.8.0 || ^17.0.0" + "@material-ui/styles@^4.11.3": version "4.11.3" resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.3.tgz#1b8d97775a4a643b53478c895e3f2a464e8916f2" From b261cd1bd05dee6924640ac3397bc19320cd0c0b Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sun, 28 Mar 2021 16:55:08 +0200 Subject: [PATCH 18/25] [FE + BE] Display very slightly better error message --- packages/be/src/mutations/marina.ts | 7 +++---- packages/fe/src/scenes/AddMarina/index.tsx | 9 ++++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/be/src/mutations/marina.ts b/packages/be/src/mutations/marina.ts index 41884d0..08682e8 100644 --- a/packages/be/src/mutations/marina.ts +++ b/packages/be/src/mutations/marina.ts @@ -36,7 +36,7 @@ export const addMarina = async ( if (isValid) { return saveMarinaToDb(input); } else { - // return ApolloError (?) + throw new ApolloError("Invalid input arguments."); } }) .then(async (result) => { @@ -45,9 +45,8 @@ export const addMarina = async ( .where({ id: result[0] }) .first(); return { marina }; + } else { + throw new ApolloError("Marina not created."); } - }) - .catch((e) => { - console.log("ERROR", e); }); }; diff --git a/packages/fe/src/scenes/AddMarina/index.tsx b/packages/fe/src/scenes/AddMarina/index.tsx index 8ec32c4..f28a321 100644 --- a/packages/fe/src/scenes/AddMarina/index.tsx +++ b/packages/fe/src/scenes/AddMarina/index.tsx @@ -20,8 +20,9 @@ import { AddMarinaAmenitiesQuery } from "__generated__/AddMarinaAmenitiesQuery.g import styles from "./add-marina.module.scss"; import { AddMarinaMutation } from "__generated__/AddMarinaMutation.graphql"; -import { useHistory, useLocation } from "react-router"; +import { useHistory } from "react-router"; import { Routes } from "routes"; +import { GraphQLError } from "graphql"; const MARINA_KREMIK_LON_LAT: [number, number] = [15.9379, 43.5696]; const MAPBOX_WEBSERVICES_URL = "https://api.mapbox.com"; @@ -214,6 +215,12 @@ export default function AddMarina() { error: "There was an error creating marina.", }); } + }).catch((error: GraphQLError) => { + setSubmitState({ + submitted: true, + sucess: false, + error: error.message + }); }); }; From ed59bd01690644ea0b926e8626ccb8e1c21e7f43 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sun, 28 Mar 2021 18:09:27 +0200 Subject: [PATCH 19/25] [FE] Add marina detail scene & amenities --- packages/fe/src/App.module.scss | 2 +- .../MarinaDetailQuery.graphql.ts | 132 +++++++++++++- .../__generated__/MarinaListQuery.graphql.ts | 37 +++- .../components/amenity/amenity.module.scss | 24 +++ .../amenity/amenity.module.scss.d.ts | 2 + packages/fe/src/components/amenity/index.tsx | 44 +++++ packages/fe/src/sass/_constants.scss | 3 +- .../AddMarina/add-marina.module.scss.d.ts | 2 + packages/fe/src/scenes/AddMarina/index.tsx | 11 +- packages/fe/src/scenes/MarinaDetail/index.tsx | 73 +++++++- .../MarinaDetail/marina-detail.module.scss | 163 ++++++++++++++++++ .../marina-detail.module.scss.d.ts | 17 ++ packages/fe/src/scenes/MarinaList/index.tsx | 37 ++-- .../scenes/MarinaList/marina-list.module.scss | 14 ++ .../MarinaList/marina-list.module.scss.d.ts | 2 + 15 files changed, 522 insertions(+), 41 deletions(-) create mode 100644 packages/fe/src/components/amenity/amenity.module.scss create mode 100644 packages/fe/src/components/amenity/amenity.module.scss.d.ts create mode 100644 packages/fe/src/components/amenity/index.tsx create mode 100644 packages/fe/src/scenes/AddMarina/add-marina.module.scss.d.ts create mode 100644 packages/fe/src/scenes/MarinaDetail/marina-detail.module.scss create mode 100644 packages/fe/src/scenes/MarinaDetail/marina-detail.module.scss.d.ts diff --git a/packages/fe/src/App.module.scss b/packages/fe/src/App.module.scss index 9465017..fc91b9f 100644 --- a/packages/fe/src/App.module.scss +++ b/packages/fe/src/App.module.scss @@ -7,6 +7,6 @@ @media #{$md} { max-width: 896px; - padding: 40px 0 0; + padding: 40px 0 20px; } } diff --git a/packages/fe/src/__generated__/MarinaDetailQuery.graphql.ts b/packages/fe/src/__generated__/MarinaDetailQuery.graphql.ts index caa1d56..f7aa116 100644 --- a/packages/fe/src/__generated__/MarinaDetailQuery.graphql.ts +++ b/packages/fe/src/__generated__/MarinaDetailQuery.graphql.ts @@ -10,6 +10,24 @@ export type MarinaDetailQueryResponse = { readonly marina: { readonly id: string; readonly name: string; + readonly photo: { + readonly id: string; + readonly url: string; + } | null; + readonly city: { + readonly id: string; + readonly lat: number; + readonly lon: number; + readonly code: string; + } | null; + readonly country: { + readonly id: string; + readonly code: string; + } | null; + readonly amenities: ReadonlyArray<{ + readonly id: string; + readonly code: string; + } | null> | null; } | null; }; export type MarinaDetailQuery = { @@ -26,6 +44,24 @@ query MarinaDetailQuery( marina(id: $id) { id name + photo { + id + url + } + city { + id + lat + lon + code + } + country { + id + code + } + amenities { + id + code + } } } */ @@ -38,7 +74,25 @@ var v0 = [ "name": "id" } ], -v1 = [ +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "code", + "storageKey": null +}, +v3 = [ + (v1/*: any*/), + (v2/*: any*/) +], +v4 = [ { "alias": null, "args": [ @@ -53,18 +107,78 @@ v1 = [ "name": "marina", "plural": false, "selections": [ + (v1/*: any*/), { "alias": null, "args": null, "kind": "ScalarField", - "name": "id", + "name": "name", "storageKey": null }, { "alias": null, "args": null, - "kind": "ScalarField", - "name": "name", + "concreteType": "Photo", + "kind": "LinkedField", + "name": "photo", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "url", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "City", + "kind": "LinkedField", + "name": "city", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lat", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lon", + "storageKey": null + }, + (v2/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Country", + "kind": "LinkedField", + "name": "country", + "plural": false, + "selections": (v3/*: any*/), + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Amenity", + "kind": "LinkedField", + "name": "amenities", + "plural": true, + "selections": (v3/*: any*/), "storageKey": null } ], @@ -77,7 +191,7 @@ return { "kind": "Fragment", "metadata": null, "name": "MarinaDetailQuery", - "selections": (v1/*: any*/), + "selections": (v4/*: any*/), "type": "Query", "abstractKey": null }, @@ -86,17 +200,17 @@ return { "argumentDefinitions": (v0/*: any*/), "kind": "Operation", "name": "MarinaDetailQuery", - "selections": (v1/*: any*/) + "selections": (v4/*: any*/) }, "params": { - "cacheID": "2504e567d7345490665faa59c853ccd1", + "cacheID": "efc0f5dda1afda46c21d418799629094", "id": null, "metadata": {}, "name": "MarinaDetailQuery", "operationKind": "query", - "text": "query MarinaDetailQuery(\n $id: ID!\n) {\n marina(id: $id) {\n id\n name\n }\n}\n" + "text": "query MarinaDetailQuery(\n $id: ID!\n) {\n marina(id: $id) {\n id\n name\n photo {\n id\n url\n }\n city {\n id\n lat\n lon\n code\n }\n country {\n id\n code\n }\n amenities {\n id\n code\n }\n }\n}\n" } }; })(); -(node as any).hash = '895e5a2414edfe0d24539f5564b874ef'; +(node as any).hash = 'f2f654bdff5ec4247efbedf8f371325d'; export default node; diff --git a/packages/fe/src/__generated__/MarinaListQuery.graphql.ts b/packages/fe/src/__generated__/MarinaListQuery.graphql.ts index 4f5080d..3284b14 100644 --- a/packages/fe/src/__generated__/MarinaListQuery.graphql.ts +++ b/packages/fe/src/__generated__/MarinaListQuery.graphql.ts @@ -22,6 +22,10 @@ export type MarinaListQueryResponse = { readonly id: string; readonly code: string; } | null; + readonly amenities: ReadonlyArray<{ + readonly id: string; + readonly code: string; + } | null> | null; }> | null; }; export type MarinaListQuery = { @@ -50,6 +54,10 @@ query MarinaListQuery { id code } + amenities { + id + code + } } } */ @@ -70,6 +78,10 @@ v1 = { "storageKey": null }, v2 = [ + (v0/*: any*/), + (v1/*: any*/) +], +v3 = [ { "alias": null, "args": null, @@ -139,10 +151,17 @@ v2 = [ "kind": "LinkedField", "name": "country", "plural": false, - "selections": [ - (v0/*: any*/), - (v1/*: any*/) - ], + "selections": (v2/*: any*/), + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Amenity", + "kind": "LinkedField", + "name": "amenities", + "plural": true, + "selections": (v2/*: any*/), "storageKey": null } ], @@ -155,7 +174,7 @@ return { "kind": "Fragment", "metadata": null, "name": "MarinaListQuery", - "selections": (v2/*: any*/), + "selections": (v3/*: any*/), "type": "Query", "abstractKey": null }, @@ -164,17 +183,17 @@ return { "argumentDefinitions": [], "kind": "Operation", "name": "MarinaListQuery", - "selections": (v2/*: any*/) + "selections": (v3/*: any*/) }, "params": { - "cacheID": "842950e155d323021942663a1f09c135", + "cacheID": "a5ca6407af3aa3f3fab2c191da2b56a3", "id": null, "metadata": {}, "name": "MarinaListQuery", "operationKind": "query", - "text": "query MarinaListQuery {\n marinas {\n id\n name\n photo {\n id\n url\n }\n city {\n id\n lat\n lon\n code\n }\n country {\n id\n code\n }\n }\n}\n" + "text": "query MarinaListQuery {\n marinas {\n id\n name\n photo {\n id\n url\n }\n city {\n id\n lat\n lon\n code\n }\n country {\n id\n code\n }\n amenities {\n id\n code\n }\n }\n}\n" } }; })(); -(node as any).hash = '181d6ffd28367b54abce4a7dad70d815'; +(node as any).hash = '12f5bb457250f69fbd705114da4eb754'; export default node; diff --git a/packages/fe/src/components/amenity/amenity.module.scss b/packages/fe/src/components/amenity/amenity.module.scss new file mode 100644 index 0000000..6a35187 --- /dev/null +++ b/packages/fe/src/components/amenity/amenity.module.scss @@ -0,0 +1,24 @@ +@import "sass/constants.scss"; + +.inline { + margin-right: 5px; + + display: inline-flex; +} + +.badge { + width: 30px; + height: 30px; + border-radius: 40px; + padding: 6px; + margin-right: 5px; + + display: flex; + align-items: center; + justify-content: center; + + background-color: $white; + color: $grey; + + box-shadow: 3px 3px 19px -5px rgba(0, 0, 0, 0.75); +} diff --git a/packages/fe/src/components/amenity/amenity.module.scss.d.ts b/packages/fe/src/components/amenity/amenity.module.scss.d.ts new file mode 100644 index 0000000..0bd7283 --- /dev/null +++ b/packages/fe/src/components/amenity/amenity.module.scss.d.ts @@ -0,0 +1,2 @@ +export const badge: string; +export const inline: string; diff --git a/packages/fe/src/components/amenity/index.tsx b/packages/fe/src/components/amenity/index.tsx new file mode 100644 index 0000000..6253340 --- /dev/null +++ b/packages/fe/src/components/amenity/index.tsx @@ -0,0 +1,44 @@ +import React from "react"; + +import { MarinaDetailQuery } from "__generated__/MarinaDetailQuery.graphql"; + +import LocalGasStationIcon from "@material-ui/icons/LocalGasStation"; +import FlightIcon from "@material-ui/icons/Flight"; +import BatteryChargingFullIcon from "@material-ui/icons/BatteryChargingFull"; + +import styles from "./amenity.module.scss"; + +type AmenityType = NonNullable< + NonNullable< + NonNullable["amenities"] + >[0] +>; + +interface Props { + isInline: boolean; + amenity: AmenityType; +} + +const CODE_TO_ICON_COMPONENT = { + electricity: BatteryChargingFullIcon, + fuel: LocalGasStationIcon, + helicopter_pad: FlightIcon, +}; + +const Amenity: React.FC = ({ amenity, isInline }) => { + const IconComponent = (CODE_TO_ICON_COMPONENT as any)[amenity.code]; + + return isInline ? ( + + {amenity.code} +   + {React.createElement(IconComponent, { fontSize: "small" })} + + ) : ( +
+ {React.createElement(IconComponent, { fontSize: "small" })} +
+ ); +}; + +export default Amenity; diff --git a/packages/fe/src/sass/_constants.scss b/packages/fe/src/sass/_constants.scss index 1c18ecc..3538ec3 100644 --- a/packages/fe/src/sass/_constants.scss +++ b/packages/fe/src/sass/_constants.scss @@ -5,4 +5,5 @@ $md: "(min-width: #{$mdBreakpoint})"; $black: #000; $white: #fff; $blue: rgb(97, 97, 163); -$grey: rgb(58, 58, 58); +$grey: rgb(59, 57, 57); +$lightgrey: rgb(148, 143, 143); diff --git a/packages/fe/src/scenes/AddMarina/add-marina.module.scss.d.ts b/packages/fe/src/scenes/AddMarina/add-marina.module.scss.d.ts new file mode 100644 index 0000000..fc4f0f2 --- /dev/null +++ b/packages/fe/src/scenes/AddMarina/add-marina.module.scss.d.ts @@ -0,0 +1,2 @@ +export const inputMargin: string; +export const map: string; diff --git a/packages/fe/src/scenes/AddMarina/index.tsx b/packages/fe/src/scenes/AddMarina/index.tsx index f28a321..ee256a0 100644 --- a/packages/fe/src/scenes/AddMarina/index.tsx +++ b/packages/fe/src/scenes/AddMarina/index.tsx @@ -11,18 +11,19 @@ import { Snackbar, } from "@material-ui/core"; import {Alert} from "@material-ui/lab"; - +import { useHistory } from "react-router"; +import { GraphQLError } from "graphql"; import { useMutation, useQuery } from "relay-hooks"; import graphql from "babel-plugin-relay/macro"; import { Add, CheckCircleOutlineOutlined } from "@material-ui/icons"; import "mapbox-gl/dist/mapbox-gl.css"; + +import { Routes } from "routes"; + import { AddMarinaAmenitiesQuery } from "__generated__/AddMarinaAmenitiesQuery.graphql"; +import { AddMarinaMutation } from "__generated__/AddMarinaMutation.graphql"; import styles from "./add-marina.module.scss"; -import { AddMarinaMutation } from "__generated__/AddMarinaMutation.graphql"; -import { useHistory } from "react-router"; -import { Routes } from "routes"; -import { GraphQLError } from "graphql"; const MARINA_KREMIK_LON_LAT: [number, number] = [15.9379, 43.5696]; const MAPBOX_WEBSERVICES_URL = "https://api.mapbox.com"; diff --git a/packages/fe/src/scenes/MarinaDetail/index.tsx b/packages/fe/src/scenes/MarinaDetail/index.tsx index 3bfa0b1..0bada5f 100644 --- a/packages/fe/src/scenes/MarinaDetail/index.tsx +++ b/packages/fe/src/scenes/MarinaDetail/index.tsx @@ -4,6 +4,9 @@ import { useQuery } from "relay-hooks"; import { useRouteMatch } from "react-router"; import { MarinaDetailQuery } from "__generated__/MarinaDetailQuery.graphql"; +import styles from './marina-detail.module.scss'; +import Amenity from "components/amenity"; + export default function MarinaDetail() { const match = useRouteMatch<{id: string}>(); @@ -13,6 +16,24 @@ export default function MarinaDetail() { marina(id: $id) { id name + photo { + id + url + } + city { + id + lat + lon + code + } + country { + id + code + } + amenities { + id + code + } } } `, @@ -22,9 +43,51 @@ export default function MarinaDetail() { } ); - return <>MarinaDetail - - {JSON.stringify(data)} - {JSON.stringify(error)} - ; + const marina = data?.marina; + return marina ? (
+
+ + +
+
+
+
+
MARINA · 16.6 mi
+

{marina.name}

+
{marina.country?.code} · {marina.city?.code}
+
+
+
+
Berths available
+
37/256
+ +
Depth
+
2.5 - 20m
+
+
+
Reservation
+
Possible
+ +
Type of berthing
+
Mooring line
+
+
+
+ +
+

Bacon ipsum dolor amet venison tongue swine, rump jowl chislic turkey chicken. Ball tip turducken short ribs salami fatback beef ribs, pork belly venison burgdoggen tri-tip. Pork belly tri-tip pork chop burgdoggen bacon ham tenderloin, andouille swine short ribs fatback ground round chuck ball tip ham hock. Capicola sausage turducken, flank corned beef tri-tip pastrami prosciutto chicken jowl short ribs biltong pig. Chuck pork sirloin, jerky strip steak pastrami shank porchetta. Biltong ham hock corned beef swine picanha ribeye jowl tenderloin kevin beef ribs boudin pork belly. Prosciutto jerky leberkas, shank fatback strip steak filet mignon sirloin kielbasa.

+

Tri-tip pig venison buffalo shoulder landjaeger fatback kielbasa pork loin turducken ground round alcatra frankfurter meatloaf. Shankle buffalo salami, beef ball tip pork belly pork chop cow porchetta corned beef fatback frankfurter t-bone. Landjaeger porchetta sirloin venison. Ground round hamburger turducken prosciutto jerky, cow ball tip rump pig meatloaf corned beef. Bresaola bacon filet mignon flank hamburger jowl ham t-bone jerky. Chicken corned beef frankfurter leberkas ball tip shank tail venison jerky pork chop capicola chuck. Beef salami short loin picanha.

+
+ + + {marina.amenities ? (
+

Amenities

+ {marina.amenities.map(amenity => ( + + ))}
) : null} + + +
+ +
) :null; } diff --git a/packages/fe/src/scenes/MarinaDetail/marina-detail.module.scss b/packages/fe/src/scenes/MarinaDetail/marina-detail.module.scss new file mode 100644 index 0000000..50045b5 --- /dev/null +++ b/packages/fe/src/scenes/MarinaDetail/marina-detail.module.scss @@ -0,0 +1,163 @@ +@import "sass/constants.scss"; + +.wrapper { + width: 100%; + position: relative; +} + +.imageWrapper { + position: relative; +} + +.image { + width: 100%; + height: 350px; + object-fit: cover; +} + +.info { + position: absolute; + top: 300px; + background-color: $white; + + border: 1px solid $lightgrey; + border-radius: 20px 20px 0 0; + padding: 20px; + + @media #{$md} { + position: static; + margin-top: 30px; + + border: 0; + border-radius: 0; + padding: 0; + } +} + +.sections { + display: flex; + justify-content: space-between; + align-items: flex-start; + flex-direction: column; + + @media #{$md} { + flex-direction: row; + } +} + +.section1 { + width: 100%; + + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-end; + justify-content: space-between; + + @media #{$md} { + width: auto; + + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + flex-wrap: nowrap; + } +} + +.type { + order: 2; + + font-size: 1rem; + color: $blue; + + @media #{$md} { + order: 1; + } +} + +.name { + order: 1; + + margin: 10px 0 0; + padding: 0; + + font-weight: 600; + font-size: 2.5rem; + line-height: 1; + + @media #{$md} { + order: 2; + } +} + +.location { + order: 3; + + margin: 10px 0 0; + + font-size: 1rem; + font-weight: 300; +} + +.section2 { + width: 100%; + + display: flex; + margin-top: 20px; + + @media #{$md} { + margin-top: 0; + width: auto; + } +} + +.section3 { + @media #{$md} { + margin-right: 30px; + } +} + +.section4, +.section3 { + width: 50%; + + display: flex; + flex-direction: column; + + @media #{$md} { + width: 170px; + } +} + +.label { + color: $lightgrey; + font-weight: 500; + + &:not(:first-child) { + margin-top: 20px; + } +} + +.value { + margin-top: 10px; + font-weight: 600; +} + +.about { + margin-top: 40px; + + font-weight: 300; + line-height: 1.75; +} + +.amenities { + margin-top: 10px; +} + +.amenitiesTitle { + margin: 0 0 10px; + padding: 0; + + font-weight: 600; + font-size: 1.3rem; +} diff --git a/packages/fe/src/scenes/MarinaDetail/marina-detail.module.scss.d.ts b/packages/fe/src/scenes/MarinaDetail/marina-detail.module.scss.d.ts new file mode 100644 index 0000000..5b98aa5 --- /dev/null +++ b/packages/fe/src/scenes/MarinaDetail/marina-detail.module.scss.d.ts @@ -0,0 +1,17 @@ +export const about: string; +export const amenities: string; +export const amenitiesTitle: string; +export const image: string; +export const imageWrapper: string; +export const info: string; +export const label: string; +export const location: string; +export const name: string; +export const section1: string; +export const section2: string; +export const section3: string; +export const section4: string; +export const sections: string; +export const type: string; +export const value: string; +export const wrapper: string; diff --git a/packages/fe/src/scenes/MarinaList/index.tsx b/packages/fe/src/scenes/MarinaList/index.tsx index f16412e..0ce4593 100644 --- a/packages/fe/src/scenes/MarinaList/index.tsx +++ b/packages/fe/src/scenes/MarinaList/index.tsx @@ -7,6 +7,7 @@ import { Routes } from "routes"; import { MarinaListQuery } from "__generated__/MarinaListQuery.graphql"; import styles from "./marina-list.module.scss"; +import Amenity from "components/amenity"; export default function MarinaList() { const { data } = useQuery( @@ -29,6 +30,10 @@ export default function MarinaList() { id code } + amenities { + id + code + } } } `, @@ -50,14 +55,26 @@ export default function MarinaList() {
{data?.marinas?.map((marina, index) => (
- {marina.name} +
+ {marina.name} +
+ {marina.amenities + ? marina.amenities.map((amenity) => ( + + )) + : null} +
+
{marina.city?.code} |{" "} {marina.country?.code} @@ -68,9 +85,7 @@ export default function MarinaList() { > {marina.name} -
- 76€ per night -
+
76€ per night
))}
diff --git a/packages/fe/src/scenes/MarinaList/marina-list.module.scss b/packages/fe/src/scenes/MarinaList/marina-list.module.scss index b18a8dc..005369e 100644 --- a/packages/fe/src/scenes/MarinaList/marina-list.module.scss +++ b/packages/fe/src/scenes/MarinaList/marina-list.module.scss @@ -81,6 +81,15 @@ } } + .amenities { + position: absolute; + top: 155px; + right: 10px; + + display: flex; + justify-content: space-evenly; + } + .locationRow { margin-top: 10px; } @@ -108,3 +117,8 @@ font-weight: 300; } } + +.relativeWrapper { + width: 100%; + position: relative; +} diff --git a/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts b/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts index 60c716f..8b17b0d 100644 --- a/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts +++ b/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts @@ -1,3 +1,4 @@ +export const amenities: string; export const cardImage: string; export const count: string; export const h1: string; @@ -7,4 +8,5 @@ export const marinaCard: string; export const marinas: string; export const name: string; export const price: string; +export const relativeWrapper: string; export const title: string; From b7ed9f97c6b62a1d61b085498e84272cc212f675 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sun, 28 Mar 2021 18:31:18 +0200 Subject: [PATCH 20/25] [FE] Add header --- packages/fe/src/App.module.scss | 4 +- packages/fe/src/App.tsx | 2 + .../src/components/header/header.module.scss | 61 ++++++++++++++++++ .../components/header/header.module.scss.d.ts | 6 ++ packages/fe/src/components/header/index.tsx | 34 ++++++++++ .../fe/{ => src/components/header}/logo.png | Bin 6 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 packages/fe/src/components/header/header.module.scss create mode 100644 packages/fe/src/components/header/header.module.scss.d.ts create mode 100644 packages/fe/src/components/header/index.tsx rename packages/fe/{ => src/components/header}/logo.png (100%) diff --git a/packages/fe/src/App.module.scss b/packages/fe/src/App.module.scss index fc91b9f..b405d31 100644 --- a/packages/fe/src/App.module.scss +++ b/packages/fe/src/App.module.scss @@ -3,10 +3,10 @@ .wrapper { background-color: $white; margin: 0 auto; - padding: 20px; + padding: 90px 20px 20px; @media #{$md} { max-width: 896px; - padding: 40px 0 20px; + padding: 110px 0 20px; } } diff --git a/packages/fe/src/App.tsx b/packages/fe/src/App.tsx index 006e9f4..06f4218 100644 --- a/packages/fe/src/App.tsx +++ b/packages/fe/src/App.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; import { Routes } from "routes"; +import Header from "components/header"; import MarinaList from "scenes/MarinaList"; import MarinaDetail from "scenes/MarinaDetail"; @@ -13,6 +14,7 @@ import styles from './App.module.scss'; function App() { return (
+
diff --git a/packages/fe/src/components/header/header.module.scss b/packages/fe/src/components/header/header.module.scss new file mode 100644 index 0000000..17b88a8 --- /dev/null +++ b/packages/fe/src/components/header/header.module.scss @@ -0,0 +1,61 @@ +@import "sass/constants.scss"; + +.header { + width: 100%; + min-height: 70px; + padding: 0px; + + position: fixed; + top: 0px; + left: 0px; + right: 0px; + + margin: 0px; + z-index: 700; + + background-color: $white; + box-shadow: 3px 3px 19px -5px rgba(0, 0, 0, 0.75); +} + +.headerContent { + max-width: 90%; + min-height: 70px; + margin: 0 auto; + + display: flex; + align-items: center; + + @media #{$md} { + max-width: 896px; + } +} + +.logo { + width: 150px; + height: 60px; + object-fit: scale-down; +} + +.links { + margin: 0 auto; + + @media #{$md} { + width: 600px; + } +} + +.link { + text-decoration: none; + color: $black; + font-size: 1.5rem; + + &:not(:last-child) { + margin-right: 20px; + } +} + +.active { + text-decoration: underline; + color: $blue; + font-size: 1.5rem; +} diff --git a/packages/fe/src/components/header/header.module.scss.d.ts b/packages/fe/src/components/header/header.module.scss.d.ts new file mode 100644 index 0000000..1f75266 --- /dev/null +++ b/packages/fe/src/components/header/header.module.scss.d.ts @@ -0,0 +1,6 @@ +export const active: string; +export const header: string; +export const headerContent: string; +export const link: string; +export const links: string; +export const logo: string; diff --git a/packages/fe/src/components/header/index.tsx b/packages/fe/src/components/header/index.tsx new file mode 100644 index 0000000..f508354 --- /dev/null +++ b/packages/fe/src/components/header/index.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { NavLink } from "react-router-dom"; +import { Routes } from "routes"; + +import logo from "./logo.png"; +import styles from "./header.module.scss"; + +const Header: React.FC = () => { + return ( +
+
+ logo +
+ + Marinas + + + Add Marina + +
+
+
+ ); +}; + +export default Header; diff --git a/packages/fe/logo.png b/packages/fe/src/components/header/logo.png similarity index 100% rename from packages/fe/logo.png rename to packages/fe/src/components/header/logo.png From ba6459ff6a4c15644722524f52b8667121aee734 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sun, 28 Mar 2021 18:54:58 +0200 Subject: [PATCH 21/25] [BE] Fix addMarina not creating marina_and_amenity rows --- packages/be/seasy.db | Bin 53248 -> 53248 bytes packages/be/src/db/kx.ts | 2 +- packages/be/src/db/marina.ts | 53 +++++++++------------------- packages/be/src/db/utils.ts | 32 +++++++++++++++++ packages/be/src/mutations/marina.ts | 2 +- 5 files changed, 50 insertions(+), 39 deletions(-) create mode 100644 packages/be/src/db/utils.ts diff --git a/packages/be/seasy.db b/packages/be/seasy.db index db3f7fcb7967675be22354d5b91e3c5c85a1925a..adbc9b7523f95019297bf582273389bfbb8022b8 100644 GIT binary patch delta 866 zcmaKoZ%7ky7{_;;y1VJ_aT_zq$Rmr2b-SH&(?ynUYV*(XPnL5~dfuJe?a$r3ySWTO zg@SsOGLS?{qsYJ)A$ueAwiiW^FM{3)5=QT$UIalD-K?Z6;>Yvm$A{1N`99OjhUsO) zY!%{}I}kuTYr_w!D=@QN2XEjhJcMF=5$`wW&4XrpzPbEM*_0nE(#>EoRZS}=DT-kQ zGR4S($S6)$N>SM~$x5Qiiz<~#Cnbhcs9G28qiC9HZgctSybaEHIvPebhv%0go6R~o zL1CqFUM2-4$%q^o8)xKy3J?7-)>5tpssNEOMjnweGU;;B1@+89V-BTGOhtTgcSkC& z@WI~BvrKd1YBDt%?TWN@pLY+mkF|^Zgs(NM1mdy5mTX_NbHLZxpYUGb(zMh&!G^m% zsf@ob-r6n<(JC1oaW%WWL6Hn7ojnaCol)z1Lft*?xEPmdDU$GK!VxjrTB4PLpQZAQ z^FDI81Ygxb7G6RJSnw@;wM_l{X;F{;2cwa!;w++MlSZCCN%cAX=m( z!|rag@l6R`BAQ%v&jR|}gah4y{el#g8IBMcA|%O~UHI!cM=-%PIp0?ut9ZP1#8_NZ zt*LWNBay4V^|ssuF&$ioSMUj9unl8*e6}BG)L=&n`f(nJ|&Mx~7jOXLOE_(B-?AMqdIU&hbMH=8eHv!j3m-{ug#4UEz} zygm&4<^1RPz4!(BZt%_LOXJh!{ldGQw~2RS<7v)~3zC>cO?WsMl;x$_*%_Vla|?1Z zlM_o)QzrlKQ($H@=AOLP&PbBgnu~)$QyrwlH?b%)FHs>lGbuGMGkdaEzX~&}Ip<_y zdoyMha}FrY4yD06jXBe>wjqpky=u=9%-e75G^B acQEjO;D644m;WUH4xq#`{>eMedjJ5hHdVU- diff --git a/packages/be/src/db/kx.ts b/packages/be/src/db/kx.ts index a808eb5..9ebd8c4 100644 --- a/packages/be/src/db/kx.ts +++ b/packages/be/src/db/kx.ts @@ -18,7 +18,7 @@ const kxConfig = { const kx = Knex(knexStringcase(kxConfig)); kx.on("query-response", res => { - console.log(res); + // console.log(res); }); export default kx; diff --git a/packages/be/src/db/marina.ts b/packages/be/src/db/marina.ts index 71f697f..2674815 100644 --- a/packages/be/src/db/marina.ts +++ b/packages/be/src/db/marina.ts @@ -1,46 +1,17 @@ -import { marinaDb, cityDb, countryDb, photoDb } from "../types/GeneratedDb"; -import kx from "./kx"; +import { marinaDb, cityDb, countryDb, photoDb, marinaAndAmenityDb } from "../types/GeneratedDb"; import { MutationAddMarinaArgs } from "../types/GeneratedGql"; -export const getMarinaById = (id: number) => { - return getMarinaBase() - .where("id", id) - .first(); -}; +import kx from "./kx"; +import { getOrCreateEntity } from "./utils"; export const getMarinaBase = () => { return kx("marina"); }; -const getOrCreateEntity = ( - tableName: string, - whereArgName: string, - whereArgValue: string, - extraDataToInsert: Partial -) => { - return kx - .transaction((trx) => { - trx(tableName) - .where(whereArgName, whereArgValue) - .then((res) => { - if (res.length === 0) { - return kx(tableName) - .transacting(trx) - .insert({ [whereArgName]: whereArgValue, ...extraDataToInsert }) - - .then(() => { - return trx(tableName).where(whereArgName, whereArgValue); - }); - } else { - return res; - } - }) - .then(trx.commit) - .catch(trx.rollback); - }) - .then((res) => { - return res[0]; - }); +export const getMarinaById = (id: number) => { + return getMarinaBase() + .where("id", id) + .first(); }; export const saveMarinaToDb = async (input: MutationAddMarinaArgs["input"]) => { @@ -73,5 +44,13 @@ export const saveMarinaToDb = async (input: MutationAddMarinaArgs["input"]) => { photoId: photo ? photo.id : undefined, }); } - ); + ).then(marina => { + const marinaId = marina[0]; + const marinaAndAmenity = input.amenities ? input.amenities.map(code => ({marinaId, amenityCode: code})) : null; + let amenitiesPromise = Promise.resolve(); + if (marinaAndAmenity) { + amenitiesPromise = kx("marina_and_amenity").insert(marinaAndAmenity); + } + return Promise.all([Promise.resolve(marina), amenitiesPromise]); + }); }; diff --git a/packages/be/src/db/utils.ts b/packages/be/src/db/utils.ts new file mode 100644 index 0000000..1e66b8b --- /dev/null +++ b/packages/be/src/db/utils.ts @@ -0,0 +1,32 @@ +import kx from "./kx"; + +export const getOrCreateEntity = ( + tableName: string, + whereArgName: string, + whereArgValue: string, + extraDataToInsert: Partial + ) => { + return kx + .transaction((trx) => { + trx(tableName) + .where(whereArgName, whereArgValue) + .then((res) => { + if (res.length === 0) { + return kx(tableName) + .transacting(trx) + .insert({ [whereArgName]: whereArgValue, ...extraDataToInsert }) + + .then(() => { + return trx(tableName).where(whereArgName, whereArgValue); + }); + } else { + return res; + } + }) + .then(trx.commit) + .catch(trx.rollback); + }) + .then((res) => { + return res[0]; + }); + }; diff --git a/packages/be/src/mutations/marina.ts b/packages/be/src/mutations/marina.ts index 08682e8..2691fd9 100644 --- a/packages/be/src/mutations/marina.ts +++ b/packages/be/src/mutations/marina.ts @@ -39,7 +39,7 @@ export const addMarina = async ( throw new ApolloError("Invalid input arguments."); } }) - .then(async (result) => { + .then(async ([result]) => { if (result && result.length) { const marina = await getMarinaBase() .where({ id: result[0] }) From 362d678df80c8b9843ef5740cce66e20c4b79126 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sun, 28 Mar 2021 18:55:32 +0200 Subject: [PATCH 22/25] [FE] Add key={..} to components in .map --- packages/fe/src/scenes/MarinaDetail/index.tsx | 2 +- packages/fe/src/scenes/MarinaList/index.tsx | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/fe/src/scenes/MarinaDetail/index.tsx b/packages/fe/src/scenes/MarinaDetail/index.tsx index 0bada5f..2b6633b 100644 --- a/packages/fe/src/scenes/MarinaDetail/index.tsx +++ b/packages/fe/src/scenes/MarinaDetail/index.tsx @@ -83,7 +83,7 @@ export default function MarinaDetail() { {marina.amenities ? (

Amenities

{marina.amenities.map(amenity => ( - + ))}
) : null} diff --git a/packages/fe/src/scenes/MarinaList/index.tsx b/packages/fe/src/scenes/MarinaList/index.tsx index 0ce4593..a490a40 100644 --- a/packages/fe/src/scenes/MarinaList/index.tsx +++ b/packages/fe/src/scenes/MarinaList/index.tsx @@ -64,16 +64,18 @@ export default function MarinaList() { } className={styles.cardImage} /> -
- {marina.amenities - ? marina.amenities.map((amenity) => ( - - )) - : null} -
+ + {marina.amenities ? ( +
+ {marina.amenities.map((amenity) => ( + + ))} +
+ ) : null}
{marina.city?.code} |{" "} From 9b909a827fd4fff4b1dc80fbdf41d9cdd7c34960 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sun, 28 Mar 2021 19:08:11 +0200 Subject: [PATCH 23/25] [FE] Tidy up (slightly :)) --- packages/fe/src/scenes/AddMarina/index.tsx | 243 +++++++++--------- packages/fe/src/scenes/AddMarina/utils.ts | 31 +++ packages/fe/src/scenes/MarinaDetail/index.tsx | 113 ++++---- 3 files changed, 216 insertions(+), 171 deletions(-) create mode 100644 packages/fe/src/scenes/AddMarina/utils.ts diff --git a/packages/fe/src/scenes/AddMarina/index.tsx b/packages/fe/src/scenes/AddMarina/index.tsx index ee256a0..25fc201 100644 --- a/packages/fe/src/scenes/AddMarina/index.tsx +++ b/packages/fe/src/scenes/AddMarina/index.tsx @@ -10,7 +10,7 @@ import { FormLabel, Snackbar, } from "@material-ui/core"; -import {Alert} from "@material-ui/lab"; +import { Alert } from "@material-ui/lab"; import { useHistory } from "react-router"; import { GraphQLError } from "graphql"; import { useMutation, useQuery } from "relay-hooks"; @@ -23,39 +23,10 @@ import { Routes } from "routes"; import { AddMarinaAmenitiesQuery } from "__generated__/AddMarinaAmenitiesQuery.graphql"; import { AddMarinaMutation } from "__generated__/AddMarinaMutation.graphql"; +import { ACCESS_TOKEN_MAPBOX, getCityCountryFromLonLat } from "./utils"; import styles from "./add-marina.module.scss"; const MARINA_KREMIK_LON_LAT: [number, number] = [15.9379, 43.5696]; -const MAPBOX_WEBSERVICES_URL = "https://api.mapbox.com"; -const ACCESS_TOKEN_MAPBOX = - "pk.eyJ1Ijoib2h1c2FyIiwiYSI6ImNrbXJ1bDRyMzBia2IycHJzbmdpbjVobWYifQ.7EthsV5t9R6ve15oUewRjQ"; - -const getCityCountryFromLonLat = async (lon: number, lat: number) => { - const url = `${MAPBOX_WEBSERVICES_URL}/geocoding/v5/mapbox.places/${lon},${lat}.json?access_token=${ACCESS_TOKEN_MAPBOX}`; - const response = (await (await fetch(url)).json()) as any; - - let city = "", - country = ""; - - if (response.features && response.features.length) { - (response.features as Array).forEach((feature: any) => { - if ( - !city && - (feature["place_type"].indexOf("place") !== -1 || - feature["place_type"].indexOf("region") !== -1) - ) { - city = feature.text; - } - }); - - (response.features as Array).reverse().forEach((feature: any) => { - if (!country && feature["place_type"].indexOf("country") !== -1) { - country = feature.text; - } - }); - } - return [city, country]; -}; interface FormState { name: string; @@ -72,12 +43,12 @@ interface FormSubmitState { } export default function AddMarina() { + const history = useHistory(); + const mapElementRef = useRef(null); const map = useRef(); const locationMarker = useRef(); - const history = useHistory(); - const [submitState, setSubmitState] = useState({ submitted: false, sucess: false, @@ -91,32 +62,6 @@ export default function AddMarina() { country: "", }); - const handleInputChange = (event: React.ChangeEvent) => { - const target = event.target; - const value = target.type === "checkbox" ? target.checked : target.value; - const name = target.name; - - setFormState({ - ...formState, - [name]: value, - }); - }; - - const handleAmenityChange = (code: string) => { - const amenities = new Set(formState.amenities); - - if (amenities.has(code)) { - amenities.delete(code); - } else { - amenities.add(code); - } - - setFormState({ - ...formState, - amenities, - }); - }; - const { data } = useQuery( graphql` query AddMarinaAmenitiesQuery { @@ -145,87 +90,130 @@ export default function AddMarina() { useEffect(() => { if (!map.current) { - mapboxgl.accessToken = ACCESS_TOKEN_MAPBOX; - map.current = new mapboxgl.Map({ - style: "mapbox://styles/mapbox/streets-v11", // style URL - container: mapElementRef.current?.id || "mapElementId", // container ID - center: MARINA_KREMIK_LON_LAT, // starting position [lng, lat] - zoom: 12, // starting zoom - }); - - locationMarker.current = new mapboxgl.Marker({ - draggable: true, - }) - .setLngLat(MARINA_KREMIK_LON_LAT) - .addTo(map.current); - - locationMarker.current.on("dragend", async (e) => { - if (locationMarker.current) { - const lngLat = locationMarker.current.getLngLat(); - - const [city, country] = await getCityCountryFromLonLat( - lngLat.lng, - lngLat.lat - ); - - setFormState((prev) => ({ - ...prev, - city, - country, - })); - } - }); + loadMapbox(); } return () => map.current && map.current.remove(); }, []); - const submit = () => { - const lngLat = locationMarker.current!.getLngLat(); + const loadMapbox = () => { + mapboxgl.accessToken = ACCESS_TOKEN_MAPBOX; + map.current = new mapboxgl.Map({ + style: "mapbox://styles/mapbox/streets-v11", // style URL + container: mapElementRef.current?.id || "mapElementId", // container ID + center: MARINA_KREMIK_LON_LAT, // starting position [lng, lat] + zoom: 12, // starting zoom + }); + + locationMarker.current = new mapboxgl.Marker({ + draggable: true, + }) + .setLngLat(MARINA_KREMIK_LON_LAT) + .addTo(map.current); + + locationMarker.current.on("dragend", async (e) => { + if (locationMarker.current) { + const lngLat = locationMarker.current.getLngLat(); + + const [city, country] = await getCityCountryFromLonLat( + lngLat.lng, + lngLat.lat + ); + + setFormState((prev) => ({ + ...prev, + city, + country, + })); + } + }); + }; + const handleInputChange = (event: React.ChangeEvent) => { + const target = event.target; + const value = target.type === "checkbox" ? target.checked : target.value; + const name = target.name; + + setFormState({ + ...formState, + [name]: value, + }); + }; + + const handleAmenityChange = (code: string) => { + const amenities = new Set(formState.amenities); + + if (amenities.has(code)) { + amenities.delete(code); + } else { + amenities.add(code); + } + + setFormState({ + ...formState, + amenities, + }); + }; + + const submit = () => { const savePromise = commit({ variables: { - input: { - ...formState, - amenities: Array.from(formState.amenities), - lon: lngLat.lng, - lat: lngLat.lat, - }, + input: getMutationInput(), }, }); - savePromise.then((result) => { - if (result && result.addMarina?.marina?.id) { - setSubmitState({ - submitted: true, - sucess: true, - error: null, - }); - - setTimeout(() => { - history.push( - Routes.getTo(Routes.MARINA_DETAIL, { - id: result!.addMarina!.marina!.id, - }) - ); - }, 1500); - } else { - setSubmitState({ - submitted: true, - sucess: false, - error: "There was an error creating marina.", - }); - } - }).catch((error: GraphQLError) => { - setSubmitState({ - submitted: true, - sucess: false, - error: error.message + savePromise + .then((result) => { + if (result && result.addMarina?.marina?.id) { + setSubmitSuccess(); + redirectToMarinaAfterDelay(result!.addMarina!.marina!.id); + } else { + setSubmitError("There was an error creating marina."); + } + }) + .catch((error: GraphQLError) => { + setSubmitError(error.message); }); + }; + + const getMutationInput = () => { + const lngLat = locationMarker.current!.getLngLat(); + + return { + ...formState, + amenities: Array.from(formState.amenities), + lon: lngLat.lng, + lat: lngLat.lat, + }; + }; + + const setSubmitSuccess = () => { + setSubmitState({ + submitted: true, + sucess: true, + error: null, }); }; - const handleClose = () => { + const setSubmitError = (msg: string) => { + setSubmitState({ + submitted: true, + sucess: false, + error: msg, + }); + }; + + const redirectToMarinaAfterDelay = (marinaId: string) => { + setTimeout(() => { + history.push( + Routes.getTo(Routes.MARINA_DETAIL, { + id: marinaId, + }) + ); + }, 1500); + }; + + const handleAlertClose = () => { setSubmitState({ submitted: false, sucess: false, @@ -255,7 +243,6 @@ export default function AddMarina() { label="Photo URL" style={{ marginTop: 15 }} /> - Amenities @@ -318,13 +305,13 @@ export default function AddMarina() { {submitState.sucess ? "Marina successfully created, you will be redirected." diff --git a/packages/fe/src/scenes/AddMarina/utils.ts b/packages/fe/src/scenes/AddMarina/utils.ts new file mode 100644 index 0000000..392b9cc --- /dev/null +++ b/packages/fe/src/scenes/AddMarina/utils.ts @@ -0,0 +1,31 @@ + +const MAPBOX_WEBSERVICES_URL = "https://api.mapbox.com"; +export const ACCESS_TOKEN_MAPBOX = + "pk.eyJ1Ijoib2h1c2FyIiwiYSI6ImNrbXJ1bDRyMzBia2IycHJzbmdpbjVobWYifQ.7EthsV5t9R6ve15oUewRjQ"; + +export const getCityCountryFromLonLat = async (lon: number, lat: number) => { + const url = `${MAPBOX_WEBSERVICES_URL}/geocoding/v5/mapbox.places/${lon},${lat}.json?access_token=${ACCESS_TOKEN_MAPBOX}`; + const response = (await (await fetch(url)).json()) as any; + + let city = "", + country = ""; + + if (response.features && response.features.length) { + (response.features as Array).forEach((feature: any) => { + if ( + !city && + (feature["place_type"].indexOf("place") !== -1 || + feature["place_type"].indexOf("region") !== -1) + ) { + city = feature.text; + } + }); + + (response.features as Array).reverse().forEach((feature: any) => { + if (!country && feature["place_type"].indexOf("country") !== -1) { + country = feature.text; + } + }); + } + return [city, country]; + }; \ No newline at end of file diff --git a/packages/fe/src/scenes/MarinaDetail/index.tsx b/packages/fe/src/scenes/MarinaDetail/index.tsx index 2b6633b..10e923b 100644 --- a/packages/fe/src/scenes/MarinaDetail/index.tsx +++ b/packages/fe/src/scenes/MarinaDetail/index.tsx @@ -4,11 +4,11 @@ import { useQuery } from "relay-hooks"; import { useRouteMatch } from "react-router"; import { MarinaDetailQuery } from "__generated__/MarinaDetailQuery.graphql"; -import styles from './marina-detail.module.scss'; +import styles from "./marina-detail.module.scss"; import Amenity from "components/amenity"; export default function MarinaDetail() { - const match = useRouteMatch<{id: string}>(); + const match = useRouteMatch<{ id: string }>(); const { data, error } = useQuery( graphql` @@ -37,57 +37,84 @@ export default function MarinaDetail() { } } `, - {id: match.params.id}, + { id: match.params.id }, { - fetchPolicy: "store-and-network" + fetchPolicy: "store-and-network", } ); const marina = data?.marina; - return marina ? (
-
- - -
-
-
-
-
MARINA · 16.6 mi
-

{marina.name}

-
{marina.country?.code} · {marina.city?.code}
-
-
-
-
Berths available
-
37/256
- -
Depth
-
2.5 - 20m
+ return marina ? ( +
+
+ marina-detail +
+
+
+
+
MARINA · 16.6 mi
+

{marina.name}

+
+ {marina.country?.code} · {marina.city?.code}{" "} +
-
-
Reservation
-
Possible
+
+
+
Berths available
+
37/256
+ +
Depth
+
2.5 - 20m
+
+
+
Reservation
+
Possible
-
Type of berthing
-
Mooring line
+
Type of berthing
+
Mooring line
+
-
-
-

Bacon ipsum dolor amet venison tongue swine, rump jowl chislic turkey chicken. Ball tip turducken short ribs salami fatback beef ribs, pork belly venison burgdoggen tri-tip. Pork belly tri-tip pork chop burgdoggen bacon ham tenderloin, andouille swine short ribs fatback ground round chuck ball tip ham hock. Capicola sausage turducken, flank corned beef tri-tip pastrami prosciutto chicken jowl short ribs biltong pig. Chuck pork sirloin, jerky strip steak pastrami shank porchetta. Biltong ham hock corned beef swine picanha ribeye jowl tenderloin kevin beef ribs boudin pork belly. Prosciutto jerky leberkas, shank fatback strip steak filet mignon sirloin kielbasa.

-

Tri-tip pig venison buffalo shoulder landjaeger fatback kielbasa pork loin turducken ground round alcatra frankfurter meatloaf. Shankle buffalo salami, beef ball tip pork belly pork chop cow porchetta corned beef fatback frankfurter t-bone. Landjaeger porchetta sirloin venison. Ground round hamburger turducken prosciutto jerky, cow ball tip rump pig meatloaf corned beef. Bresaola bacon filet mignon flank hamburger jowl ham t-bone jerky. Chicken corned beef frankfurter leberkas ball tip shank tail venison jerky pork chop capicola chuck. Beef salami short loin picanha.

+
+

+ Bacon ipsum dolor amet venison tongue swine, rump jowl chislic + turkey chicken. Ball tip turducken short ribs salami fatback beef + ribs, pork belly venison burgdoggen tri-tip. Pork belly tri-tip pork + chop burgdoggen bacon ham tenderloin, andouille swine short ribs + fatback ground round chuck ball tip ham hock. Capicola sausage + turducken, flank corned beef tri-tip pastrami prosciutto chicken + jowl short ribs biltong pig. Chuck pork sirloin, jerky strip steak + pastrami shank porchetta. Biltong ham hock corned beef swine picanha + ribeye jowl tenderloin kevin beef ribs boudin pork belly. Prosciutto + jerky leberkas, shank fatback strip steak filet mignon sirloin + kielbasa. +

+

+ Tri-tip pig venison buffalo shoulder landjaeger fatback kielbasa + pork loin turducken ground round alcatra frankfurter meatloaf. + Shankle buffalo salami, beef ball tip pork belly pork chop cow + porchetta corned beef fatback frankfurter t-bone. Landjaeger + porchetta sirloin venison. Ground round hamburger turducken + prosciutto jerky, cow ball tip rump pig meatloaf corned beef. + Bresaola bacon filet mignon flank hamburger jowl ham t-bone jerky. + Chicken corned beef frankfurter leberkas ball tip shank tail venison + jerky pork chop capicola chuck. Beef salami short loin picanha. +

+
+ {marina.amenities ? ( +
+

Amenities

+ {marina.amenities.map((amenity) => ( + + ))} +
+ ) : null}
- - - {marina.amenities ? (
-

Amenities

- {marina.amenities.map(amenity => ( - - ))}
) : null} - -
- -
) :null; + ) : null; } From 0eca2336bec2660a1ca48630cb382cd3bfaeb343 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sun, 28 Mar 2021 19:11:41 +0200 Subject: [PATCH 24/25] [BE] Add query for all marinas in city / country --- packages/be/src/db/marina.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/be/src/db/marina.ts b/packages/be/src/db/marina.ts index 2674815..3a96670 100644 --- a/packages/be/src/db/marina.ts +++ b/packages/be/src/db/marina.ts @@ -14,6 +14,14 @@ export const getMarinaById = (id: number) => { .first(); }; +export const getAllMarinasInACity = (cityCode: string) => { + return getMarinaBase().where("city_code", cityCode); +} + +export const getAllMarinasInACountry = (countryCode: string) => { + return getMarinaBase().where("country_code", countryCode); +} + export const saveMarinaToDb = async (input: MutationAddMarinaArgs["input"]) => { const countryPromise = getOrCreateEntity( "country", From 441b7f5f124762b81439de5b318d5bfb3713c968 Mon Sep 17 00:00:00 2001 From: Ondrej Husar Date: Sun, 28 Mar 2021 20:08:56 +0200 Subject: [PATCH 25/25] [BE + FE] Add MarinaConnection & very simple FE pagination --- packages/be/src/query/index.ts | 44 ++- packages/be/src/types/GeneratedGql.ts | 38 +++ packages/fe/schema.graphql | 11 + .../__generated__/MarinaListQuery.graphql.ts | 280 +++++++++++------- packages/fe/src/scenes/MarinaList/index.tsx | 93 +++--- .../scenes/MarinaList/marina-list.module.scss | 8 + .../MarinaList/marina-list.module.scss.d.ts | 1 + packages/fe/src/types/GeneratedGQL.ts | 19 ++ 8 files changed, 356 insertions(+), 138 deletions(-) diff --git a/packages/be/src/query/index.ts b/packages/be/src/query/index.ts index bb4bf56..e72544a 100644 --- a/packages/be/src/query/index.ts +++ b/packages/be/src/query/index.ts @@ -6,8 +6,19 @@ import { getAmenityBase } from "../db/amenity"; import { getCityBase } from "../db/city"; import { getCountryBase } from "../db/country"; import { getPhotoBase } from "../db/photo"; +import { marinaDb } from "../types/GeneratedDb"; export const schema = gql` + type MarinaConnection { + edges: [MarinaEdge] + pageInfo: PageInfo! + } + + type MarinaEdge { + cursor: String! + node: Marina + } + type Query { marinas: [Marina!] cities: [City!] @@ -16,15 +27,42 @@ export const schema = gql` photos: [Photo!] marina(id: ID!): Marina + + marinaConnection(first: Int, after: String): MarinaConnection } `; export const resolver: QueryResolvers = { marinas: () => getMarinaBase(), - marina: (parent, args) => getMarinaById(parseInt(fromGlobalId(args.id).id)), - amenities: () => getAmenityBase(), cities: () => getCityBase(), countries: () => getCountryBase(), - photos: () => getPhotoBase() + photos: () => getPhotoBase(), + + marina: (parent, args) => getMarinaById(parseInt(fromGlobalId(args.id).id)), + + marinaConnection: async (parent, args) => { + const first = args.first; + const after = parseInt(args.after || "0"); // could be more opaque + const length = (await getMarinaBase().count("id"))[0]['count(`id`)'] || 0; + + let marinasQuery = getMarinaBase().offset(after); + if (first) { + marinasQuery = getMarinaBase().offset(after).limit(first); + } + const marinas = await marinasQuery; + + return { + edges: marinas.map((marina, index) => ({ + node: marina, + cursor: `${index + after}`, + })), + pageInfo: { + hasNextPage: first ? first + after < length : false, + hasPreviousPage: after > 0, + endCursor: first ? `${first + after}` : `${length}`, + startCursor: `${after}` + } + }; + }, }; diff --git a/packages/be/src/types/GeneratedGql.ts b/packages/be/src/types/GeneratedGql.ts index 4bfe19b..038e4ae 100644 --- a/packages/be/src/types/GeneratedGql.ts +++ b/packages/be/src/types/GeneratedGql.ts @@ -65,6 +65,18 @@ export type Marina = Node & { amenities?: Maybe>>; }; +export type MarinaConnection = { + __typename?: 'MarinaConnection'; + edges?: Maybe>>; + pageInfo: PageInfo; +}; + +export type MarinaEdge = { + __typename?: 'MarinaEdge'; + cursor: Scalars['String']; + node?: Maybe; +}; + export type MarinaPayload = { __typename?: 'MarinaPayload'; marina?: Maybe; @@ -106,6 +118,7 @@ export type Query = { amenities?: Maybe>; photos?: Maybe>; marina?: Maybe; + marinaConnection?: Maybe; }; @@ -114,6 +127,12 @@ export type QueryMarinaArgs = { }; +export type QueryMarinaConnectionArgs = { + first?: Maybe; + after?: Maybe; +}; + + export type ResolverTypeWrapper = Promise | T; @@ -200,6 +219,8 @@ export type ResolversTypes = { City: ResolverTypeWrapper; Country: ResolverTypeWrapper; Marina: ResolverTypeWrapper; + MarinaConnection: ResolverTypeWrapper & { edges?: Maybe>> }>; + MarinaEdge: ResolverTypeWrapper & { node?: Maybe }>; MarinaPayload: ResolverTypeWrapper & { marina?: Maybe }>; Mutation: ResolverTypeWrapper<{}>; Node: ResolversTypes['Amenity'] | ResolversTypes['City'] | ResolversTypes['Country'] | ResolversTypes['Marina'] | ResolversTypes['Photo']; @@ -220,6 +241,8 @@ export type ResolversParentTypes = { City: cityDb; Country: countryDb; Marina: marinaDb; + MarinaConnection: Omit & { edges?: Maybe>> }; + MarinaEdge: Omit & { node?: Maybe }; MarinaPayload: Omit & { marina?: Maybe }; Mutation: {}; Node: ResolversParentTypes['Amenity'] | ResolversParentTypes['City'] | ResolversParentTypes['Country'] | ResolversParentTypes['Marina'] | ResolversParentTypes['Photo']; @@ -268,6 +291,18 @@ export type MarinaResolvers; }; +export type MarinaConnectionResolvers = { + edges?: Resolver>>, ParentType, ContextType>; + pageInfo?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type MarinaEdgeResolvers = { + cursor?: Resolver; + node?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type MarinaPayloadResolvers = { marina?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -303,6 +338,7 @@ export type QueryResolvers>, ParentType, ContextType>; photos?: Resolver>, ParentType, ContextType>; marina?: Resolver, ParentType, ContextType, RequireFields>; + marinaConnection?: Resolver, ParentType, ContextType, RequireFields>; }; export type Resolvers = { @@ -310,6 +346,8 @@ export type Resolvers = { City?: CityResolvers; Country?: CountryResolvers; Marina?: MarinaResolvers; + MarinaConnection?: MarinaConnectionResolvers; + MarinaEdge?: MarinaEdgeResolvers; MarinaPayload?: MarinaPayloadResolvers; Mutation?: MutationResolvers; Node?: NodeResolvers; diff --git a/packages/fe/schema.graphql b/packages/fe/schema.graphql index 697676f..b6c33e0 100644 --- a/packages/fe/schema.graphql +++ b/packages/fe/schema.graphql @@ -44,6 +44,16 @@ type Marina implements Node { amenities: [Amenity] } +type MarinaConnection { + edges: [MarinaEdge] + pageInfo: PageInfo! +} + +type MarinaEdge { + cursor: String! + node: Marina +} + type MarinaPayload { marina: Marina } @@ -75,4 +85,5 @@ type Query { amenities: [Amenity!] photos: [Photo!] marina(id: ID!): Marina + marinaConnection(first: Int, after: String): MarinaConnection } diff --git a/packages/fe/src/__generated__/MarinaListQuery.graphql.ts b/packages/fe/src/__generated__/MarinaListQuery.graphql.ts index 3284b14..3aaeb66 100644 --- a/packages/fe/src/__generated__/MarinaListQuery.graphql.ts +++ b/packages/fe/src/__generated__/MarinaListQuery.graphql.ts @@ -3,30 +3,40 @@ // @ts-nocheck import { ConcreteRequest } from "relay-runtime"; -export type MarinaListQueryVariables = {}; +export type MarinaListQueryVariables = { + first?: number | null; +}; export type MarinaListQueryResponse = { - readonly marinas: ReadonlyArray<{ - readonly id: string; - readonly name: string; - readonly photo: { - readonly id: string; - readonly url: string; - } | null; - readonly city: { - readonly id: string; - readonly lat: number; - readonly lon: number; - readonly code: string; - } | null; - readonly country: { - readonly id: string; - readonly code: string; - } | null; - readonly amenities: ReadonlyArray<{ - readonly id: string; - readonly code: string; + readonly marinaConnection: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly name: string; + readonly photo: { + readonly id: string; + readonly url: string; + } | null; + readonly city: { + readonly id: string; + readonly lat: number; + readonly lon: number; + readonly code: string; + } | null; + readonly country: { + readonly id: string; + readonly code: string; + } | null; + readonly amenities: ReadonlyArray<{ + readonly id: string; + readonly code: string; + } | null> | null; + } | null; } | null> | null; - }> | null; + readonly pageInfo: { + readonly hasNextPage: boolean | null; + readonly endCursor: string | null; + }; + } | null; }; export type MarinaListQuery = { readonly response: MarinaListQueryResponse; @@ -36,82 +46,174 @@ export type MarinaListQuery = { /* -query MarinaListQuery { - marinas { - id - name - photo { - id - url - } - city { - id - lat - lon - code - } - country { - id - code +query MarinaListQuery( + $first: Int +) { + marinaConnection(first: $first) { + edges { + node { + id + name + photo { + id + url + } + city { + id + lat + lon + code + } + country { + id + code + } + amenities { + id + code + } + } } - amenities { - id - code + pageInfo { + hasNextPage + endCursor } } } */ const node: ConcreteRequest = (function(){ -var v0 = { +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "first" + } +], +v1 = { "alias": null, "args": null, "kind": "ScalarField", "name": "id", "storageKey": null }, -v1 = { +v2 = { "alias": null, "args": null, "kind": "ScalarField", "name": "code", "storageKey": null }, -v2 = [ - (v0/*: any*/), - (v1/*: any*/) -], v3 = [ + (v1/*: any*/), + (v2/*: any*/) +], +v4 = [ { "alias": null, - "args": null, - "concreteType": "Marina", + "args": [ + { + "kind": "Variable", + "name": "first", + "variableName": "first" + } + ], + "concreteType": "MarinaConnection", "kind": "LinkedField", - "name": "marinas", - "plural": true, + "name": "marinaConnection", + "plural": false, "selections": [ - (v0/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "name", - "storageKey": null - }, { "alias": null, "args": null, - "concreteType": "Photo", + "concreteType": "MarinaEdge", "kind": "LinkedField", - "name": "photo", - "plural": false, + "name": "edges", + "plural": true, "selections": [ - (v0/*: any*/), { "alias": null, "args": null, - "kind": "ScalarField", - "name": "url", + "concreteType": "Marina", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Photo", + "kind": "LinkedField", + "name": "photo", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "url", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "City", + "kind": "LinkedField", + "name": "city", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lat", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lon", + "storageKey": null + }, + (v2/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Country", + "kind": "LinkedField", + "name": "country", + "plural": false, + "selections": (v3/*: any*/), + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Amenity", + "kind": "LinkedField", + "name": "amenities", + "plural": true, + "selections": (v3/*: any*/), + "storageKey": null + } + ], "storageKey": null } ], @@ -120,49 +222,27 @@ v3 = [ { "alias": null, "args": null, - "concreteType": "City", + "concreteType": "PageInfo", "kind": "LinkedField", - "name": "city", + "name": "pageInfo", "plural": false, "selections": [ - (v0/*: any*/), { "alias": null, "args": null, "kind": "ScalarField", - "name": "lat", + "name": "hasNextPage", "storageKey": null }, { "alias": null, "args": null, "kind": "ScalarField", - "name": "lon", + "name": "endCursor", "storageKey": null - }, - (v1/*: any*/) + } ], "storageKey": null - }, - { - "alias": null, - "args": null, - "concreteType": "Country", - "kind": "LinkedField", - "name": "country", - "plural": false, - "selections": (v2/*: any*/), - "storageKey": null - }, - { - "alias": null, - "args": null, - "concreteType": "Amenity", - "kind": "LinkedField", - "name": "amenities", - "plural": true, - "selections": (v2/*: any*/), - "storageKey": null } ], "storageKey": null @@ -170,30 +250,30 @@ v3 = [ ]; return { "fragment": { - "argumentDefinitions": [], + "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, "name": "MarinaListQuery", - "selections": (v3/*: any*/), + "selections": (v4/*: any*/), "type": "Query", "abstractKey": null }, "kind": "Request", "operation": { - "argumentDefinitions": [], + "argumentDefinitions": (v0/*: any*/), "kind": "Operation", "name": "MarinaListQuery", - "selections": (v3/*: any*/) + "selections": (v4/*: any*/) }, "params": { - "cacheID": "a5ca6407af3aa3f3fab2c191da2b56a3", + "cacheID": "72f99122b78e4858f99ba2618441a60a", "id": null, "metadata": {}, "name": "MarinaListQuery", "operationKind": "query", - "text": "query MarinaListQuery {\n marinas {\n id\n name\n photo {\n id\n url\n }\n city {\n id\n lat\n lon\n code\n }\n country {\n id\n code\n }\n amenities {\n id\n code\n }\n }\n}\n" + "text": "query MarinaListQuery(\n $first: Int\n) {\n marinaConnection(first: $first) {\n edges {\n node {\n id\n name\n photo {\n id\n url\n }\n city {\n id\n lat\n lon\n code\n }\n country {\n id\n code\n }\n amenities {\n id\n code\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n}\n" } }; })(); -(node as any).hash = '12f5bb457250f69fbd705114da4eb754'; +(node as any).hash = 'cdafb735a878fd6fa9df82594dae8bfd'; export default node; diff --git a/packages/fe/src/scenes/MarinaList/index.tsx b/packages/fe/src/scenes/MarinaList/index.tsx index a490a40..3f96f7f 100644 --- a/packages/fe/src/scenes/MarinaList/index.tsx +++ b/packages/fe/src/scenes/MarinaList/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import { useQuery } from "relay-hooks"; import graphql from "babel-plugin-relay/macro"; import { Link } from "react-router-dom"; @@ -8,66 +8,77 @@ import { MarinaListQuery } from "__generated__/MarinaListQuery.graphql"; import styles from "./marina-list.module.scss"; import Amenity from "components/amenity"; +import { Button } from "@material-ui/core"; export default function MarinaList() { + const [first, setFirst] = useState(3); + const { data } = useQuery( graphql` - query MarinaListQuery { - marinas { - id - name - photo { - id - url - } - city { - id - lat - lon - code - } - country { - id - code + query MarinaListQuery($first: Int) { + marinaConnection(first: $first) { + edges { + node { + id + name + photo { + id + url + } + city { + id + lat + lon + code + } + country { + id + code + } + amenities { + id + code + } + } } - amenities { - id - code + pageInfo { + hasNextPage + endCursor } } } `, - {}, + {first}, { fetchPolicy: "store-and-network", } ); + + return ( <>

CROATIA

- {data?.marinas?.length ? data?.marinas?.length - 1 : 130}+ marines for - booking + 130+ marines for booking
- {data?.marinas?.map((marina, index) => ( -
+ {data?.marinaConnection?.edges?.map((marinaEdge, index) => ( +
{marina.name} - - {marina.amenities ? ( + {marinaEdge?.node?.amenities ? (
- {marina.amenities.map((amenity) => ( + {marinaEdge?.node?.amenities.map((amenity) => (
- {marina.city?.code} |{" "} - {marina.country?.code} + {marinaEdge?.node?.city?.code} |{" "} + {marinaEdge?.node?.country?.code}
- {marina.name} + {marinaEdge?.node?.name}
76€ per night
))}
+
+ + +
); } diff --git a/packages/fe/src/scenes/MarinaList/marina-list.module.scss b/packages/fe/src/scenes/MarinaList/marina-list.module.scss index 005369e..34f4356 100644 --- a/packages/fe/src/scenes/MarinaList/marina-list.module.scss +++ b/packages/fe/src/scenes/MarinaList/marina-list.module.scss @@ -122,3 +122,11 @@ width: 100%; position: relative; } + +.fetchMore { + width: 100%; + margin-top: 20px; + + display: flex; + justify-content: center; +} diff --git a/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts b/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts index 8b17b0d..adea5a0 100644 --- a/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts +++ b/packages/fe/src/scenes/MarinaList/marina-list.module.scss.d.ts @@ -1,6 +1,7 @@ export const amenities: string; export const cardImage: string; export const count: string; +export const fetchMore: string; export const h1: string; export const location: string; export const locationRow: string; diff --git a/packages/fe/src/types/GeneratedGQL.ts b/packages/fe/src/types/GeneratedGQL.ts index b1d3cdb..f480ae1 100644 --- a/packages/fe/src/types/GeneratedGQL.ts +++ b/packages/fe/src/types/GeneratedGQL.ts @@ -60,6 +60,18 @@ export type Marina = Node & { amenities?: Maybe>>; }; +export type MarinaConnection = { + __typename?: 'MarinaConnection'; + edges?: Maybe>>; + pageInfo: PageInfo; +}; + +export type MarinaEdge = { + __typename?: 'MarinaEdge'; + cursor: Scalars['String']; + node?: Maybe; +}; + export type MarinaPayload = { __typename?: 'MarinaPayload'; marina?: Maybe; @@ -101,9 +113,16 @@ export type Query = { amenities?: Maybe>; photos?: Maybe>; marina?: Maybe; + marinaConnection?: Maybe; }; export type QueryMarinaArgs = { id: Scalars['ID']; }; + + +export type QueryMarinaConnectionArgs = { + first?: Maybe; + after?: Maybe; +};