From 36dc570d33eefb1c418d3d3d9266c0799085e0be Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Sun, 11 Feb 2024 11:41:00 +0100 Subject: [PATCH 01/21] add empty bulk-upload page --- .gitignore | 5 ++++- .vscode/settings.json | 3 ++- src/pages/bulk-upload.tsx | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 src/pages/bulk-upload.tsx diff --git a/.gitignore b/.gitignore index 4f507c9fe..f878091bf 100644 --- a/.gitignore +++ b/.gitignore @@ -78,7 +78,10 @@ content .env.local .env.test.local *.pem -.vscode/launch.json .lighthouseci + +# IDE .idea/ +.vscode/launch.json +.vscode/settings.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 0b0b4ab16..39b622e3d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,6 @@ "standard.treatErrorsAsWarnings": true, "javascript.format.enable": false, "javascript.format.semicolons": "remove", - "typescript.format.enable": true + "typescript.format.enable": true, + "workbench.colorTheme": "Material Theme DeepForest High Contrast" } \ No newline at end of file diff --git a/src/pages/bulk-upload.tsx b/src/pages/bulk-upload.tsx new file mode 100644 index 000000000..a6ca7d60e --- /dev/null +++ b/src/pages/bulk-upload.tsx @@ -0,0 +1,18 @@ +import Layout from '../components/layout' + +const BulkUpload = (): JSX.Element => { + return ( + +
+
+

Bulk Upload Page

+

+ This page is under construction. Please check back later for updates. +

+
+
+
+ ) +} + +export default BulkUpload From 3a4213607bdc13ea1d2fd721c526a92581ad6afe Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:56:41 +0100 Subject: [PATCH 02/21] commit first working barebones version w MUI --- .vscode/settings.json | 2 +- package.json | 5 + src/pages/bulk-upload.tsx | 18 -- yarn.lock | 380 +++++++++++++++++++++++++++++++++++++- 4 files changed, 380 insertions(+), 25 deletions(-) delete mode 100644 src/pages/bulk-upload.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 39b622e3d..ecb2334dd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,5 +16,5 @@ "javascript.format.enable": false, "javascript.format.semicolons": "remove", "typescript.format.enable": true, - "workbench.colorTheme": "Material Theme DeepForest High Contrast" + "workbench.colorTheme": "Tomorrow Night Blue" } \ No newline at end of file diff --git a/package.json b/package.json index 3ad0f63ff..db534959b 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,15 @@ "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", + "@emotion/react": "^11.11.3", + "@emotion/styled": "^11.11.0", "@google-cloud/storage": "^6.11.0", "@headlessui/react": "^1.7.15", "@heroicons/react": "2.0.13", "@hookform/resolvers": "^3.1.1", "@lexical/react": "^0.7.5", "@math.gl/web-mercator": "3.6.2", + "@mui/material": "^5.15.10", "@openbeta/sandbag": "^0.0.51", "@phosphor-icons/react": "^2.0.14", "@radix-ui/react-alert-dialog": "^1.0.0", @@ -30,10 +33,12 @@ "@turf/line-to-polygon": "^6.5.0", "@udecode/zustood": "^1.1.3", "@vercel/edge": "^1.1.1", + "ajv-formats": "^2.1.1", "auth0": "^2.42.0", "awesome-debounce-promise": "^2.1.0", "aws-sdk": "^2.1265.0", "axios": "^0.24.0", + "better-ajv-errors": "^1.2.0", "classnames": "^2.3.1", "csvtojson": "^2.0.10", "daisyui": "^3.9.4", diff --git a/src/pages/bulk-upload.tsx b/src/pages/bulk-upload.tsx deleted file mode 100644 index a6ca7d60e..000000000 --- a/src/pages/bulk-upload.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import Layout from '../components/layout' - -const BulkUpload = (): JSX.Element => { - return ( - -
-
-

Bulk Upload Page

-

- This page is under construction. Please check back later for updates. -

-
-
-
- ) -} - -export default BulkUpload diff --git a/yarn.lock b/yarn.lock index fce313d10..848ef3bb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -87,6 +87,14 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" +"@babel/code-frame@^7.16.0": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + "@babel/compat-data@^7.22.20", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0" @@ -210,7 +218,7 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5": +"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== @@ -321,6 +329,15 @@ chalk "^2.4.2" js-tokens "^4.0.0" +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16": version "7.22.16" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" @@ -1063,6 +1080,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" + integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.22.5", "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -1133,6 +1157,113 @@ dependencies: tslib "^2.0.0" +"@emotion/babel-plugin@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" + integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/serialize" "^1.1.2" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + +"@emotion/cache@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" + integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + stylis "4.2.0" + +"@emotion/hash@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" + integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== + +"@emotion/is-prop-valid@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" + integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== + dependencies: + "@emotion/memoize" "^0.8.1" + +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + +"@emotion/react@^11.11.3": + version "11.11.3" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.3.tgz#96b855dc40a2a55f52a72f518a41db4f69c31a25" + integrity sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.3.tgz#84b77bfcfe3b7bb47d326602f640ccfcacd5ffb0" + integrity sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA== + dependencies: + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/unitless" "^0.8.1" + "@emotion/utils" "^1.2.1" + csstype "^3.0.2" + +"@emotion/sheet@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" + integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== + +"@emotion/styled@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.0.tgz#26b75e1b5a1b7a629d7c0a8b708fbf5a9cdce346" + integrity sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/is-prop-valid" "^1.2.1" + "@emotion/serialize" "^1.1.2" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + +"@emotion/unitless@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + +"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" + integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== + +"@emotion/utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" + integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== + +"@emotion/weak-memoize@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" + integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== + "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -1165,6 +1296,13 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.49.0.tgz#86f79756004a97fa4df866835093f1df3d03c333" integrity sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w== +"@floating-ui/core@^1.0.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" + integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== + dependencies: + "@floating-ui/utils" "^0.2.1" + "@floating-ui/core@^1.4.2": version "1.5.0" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.0.tgz#5c05c60d5ae2d05101c3021c1a2a350ddc027f8c" @@ -1180,6 +1318,14 @@ "@floating-ui/core" "^1.4.2" "@floating-ui/utils" "^0.1.3" +"@floating-ui/dom@^1.6.1": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef" + integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + "@floating-ui/react-dom@^2.0.0": version "2.0.2" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.2.tgz#fab244d64db08e6bed7be4b5fcce65315ef44d20" @@ -1187,11 +1333,23 @@ dependencies: "@floating-ui/dom" "^1.5.1" +"@floating-ui/react-dom@^2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" + integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== + dependencies: + "@floating-ui/dom" "^1.6.1" + "@floating-ui/utils@^0.1.3": version "0.1.4" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.4.tgz#19654d1026cc410975d46445180e70a5089b3e7d" integrity sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA== +"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" + integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== + "@google-cloud/paginator@^3.0.7": version "3.0.7" resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-3.0.7.tgz#fb6f8e24ec841f99defaebf62c75c2e744dd419b" @@ -1270,6 +1428,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== +"@humanwhocodes/momoa@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@humanwhocodes/momoa/-/momoa-2.0.4.tgz#8b9e7a629651d15009c3587d07a222deeb829385" + integrity sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA== + "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" @@ -1734,6 +1897,90 @@ "@babel/runtime" "^7.12.0" gl-matrix "^3.4.0" +"@mui/base@5.0.0-beta.36": + version "5.0.0-beta.36" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.36.tgz#29ca2de9d387f6d3943b6f18a84415c43e5f206c" + integrity sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@floating-ui/react-dom" "^2.0.8" + "@mui/types" "^7.2.13" + "@mui/utils" "^5.15.9" + "@popperjs/core" "^2.11.8" + clsx "^2.1.0" + prop-types "^15.8.1" + +"@mui/core-downloads-tracker@^5.15.10": + version "5.15.10" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.10.tgz#616bfb54e3860268d56ff59cd187d47044d954f3" + integrity sha512-qPv7B+LeMatYuzRjB3hlZUHqinHx/fX4YFBiaS19oC02A1e9JFuDKDvlyRQQ5oRSbJJt0QlaLTlr0IcauVcJRQ== + +"@mui/material@^5.15.10": + version "5.15.10" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.10.tgz#6533ba53edbd0790dbc5bb7e9e173f6069ffd7e6" + integrity sha512-YJJGHjwDOucecjDEV5l9ISTCo+l9YeWrho623UajzoHRYxuKUmwrGVYOW4PKwGvCx9SU9oklZnbbi2Clc5XZHw== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/base" "5.0.0-beta.36" + "@mui/core-downloads-tracker" "^5.15.10" + "@mui/system" "^5.15.9" + "@mui/types" "^7.2.13" + "@mui/utils" "^5.15.9" + "@types/react-transition-group" "^4.4.10" + clsx "^2.1.0" + csstype "^3.1.3" + prop-types "^15.8.1" + react-is "^18.2.0" + react-transition-group "^4.4.5" + +"@mui/private-theming@^5.15.9": + version "5.15.9" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.9.tgz#3ea3514ed2f6bf68541dbe9206665a82cd89cb01" + integrity sha512-/aMJlDOxOTAXyp4F2rIukW1O0anodAMCkv1DfBh/z9vaKHY3bd5fFf42wmP+0GRmwMinC5aWPpNfHXOED1fEtg== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/utils" "^5.15.9" + prop-types "^15.8.1" + +"@mui/styled-engine@^5.15.9": + version "5.15.9" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.9.tgz#444605039ec3fe456bdd5d5cb94330183be62b91" + integrity sha512-NRKtYkL5PZDH7dEmaLEIiipd3mxNnQSO+Yo8rFNBNptY8wzQnQ+VjayTq39qH7Sast5cwHKYFusUrQyD+SS4Og== + dependencies: + "@babel/runtime" "^7.23.9" + "@emotion/cache" "^11.11.0" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/system@^5.15.9": + version "5.15.9" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.9.tgz#8a34ac0ab133af2550cc7ab980a35174142fd265" + integrity sha512-SxkaaZ8jsnIJ77bBXttfG//LUf6nTfOcaOuIgItqfHv60ZCQy/Hu7moaob35kBb+guxVJnoSZ+7vQJrA/E7pKg== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/private-theming" "^5.15.9" + "@mui/styled-engine" "^5.15.9" + "@mui/types" "^7.2.13" + "@mui/utils" "^5.15.9" + clsx "^2.1.0" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/types@^7.2.13": + version "7.2.13" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.13.tgz#d1584912942f9dc042441ecc2d1452be39c666b8" + integrity sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g== + +"@mui/utils@^5.15.9": + version "5.15.9" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.9.tgz#2bdf925e274d87cbe90c14eb52d0835318205e86" + integrity sha512-yDYfr61bCYUz1QtwvpqYy/3687Z8/nS4zv7lv/ih/6ZFGMl1iolEvxRmR84v2lOYxlds+kq1IVYbXxDKh8Z9sg== + dependencies: + "@babel/runtime" "^7.23.9" + "@types/prop-types" "^15.7.11" + prop-types "^15.8.1" + react-is "^18.2.0" + "@next/env@13.5.6": version "13.5.6" resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.6.tgz#c1148e2e1aa166614f05161ee8f77ded467062bc" @@ -1820,6 +2067,11 @@ resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.0.14.tgz#3c8977cc81cc376d0c6afda46882eb5dc9b8b54d" integrity sha512-VaZ7/JEQ7dW+Up23l7t6lqJ3dPJupM03916Pat+ZOLX1vex9OeX9t8RZLJWt0oVrdc/GcrAyRD5FESDeP+M4tQ== +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@radix-ui/primitive@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" @@ -2729,6 +2981,11 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.6.tgz#bbf819813d6be21011b8f5801058498bec555572" integrity sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg== +"@types/prop-types@^15.7.11": + version "15.7.11" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" + integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== + "@types/qs@*": version "6.9.8" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.8.tgz#f2a7de3c107b89b441e071d5472e6b726b4adf45" @@ -2753,6 +3010,13 @@ dependencies: "@types/react" "*" +"@types/react-transition-group@^4.4.10": + version "4.4.10" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac" + integrity sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^18.0.9": version "18.2.22" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.22.tgz#abe778a1c95a07fa70df40a52d7300a40b949ccb" @@ -2996,6 +3260,13 @@ agent-base@6: dependencies: debug "4" +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -3006,6 +3277,16 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + ansi-escapes@^4.2.1: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -3342,6 +3623,15 @@ babel-plugin-jest-hoist@^29.6.3: "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + babel-plugin-polyfill-corejs2@^0.4.5: version "0.4.5" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz#8097b4cb4af5b64a1d11332b6fb72ef5e64a054c" @@ -3407,6 +3697,17 @@ base64-js@^1.0.2, base64-js@^1.3.0: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +better-ajv-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/better-ajv-errors/-/better-ajv-errors-1.2.0.tgz#6412d58fa4d460ff6ccbd9e65c5fef9781cc5286" + integrity sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA== + dependencies: + "@babel/code-frame" "^7.16.0" + "@humanwhocodes/momoa" "^2.0.2" + chalk "^4.1.2" + jsonpointer "^5.0.0" + leven "^3.1.0 < 4" + bignumber.js@^9.0.0: version "9.1.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" @@ -3565,7 +3866,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -3654,6 +3955,11 @@ clsx@^1.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== +clsx@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" + integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -3740,7 +4046,7 @@ constant-case@^1.1.0: snake-case "^1.1.0" upper-case "^1.1.1" -convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== @@ -3774,7 +4080,7 @@ core-js-compat@^3.31.0: dependencies: browserslist "^4.21.10" -cosmiconfig@^7.0.1: +cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== @@ -3900,6 +4206,11 @@ csstype@^3.0.2, csstype@^3.0.6: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +csstype@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + csvtojson@^2.0.10: version "2.0.10" resolved "https://registry.yarnpkg.com/csvtojson/-/csvtojson-2.0.10.tgz#11e7242cc630da54efce7958a45f443210357574" @@ -4193,6 +4504,14 @@ dom-helpers@^3.4.0: dependencies: "@babel/runtime" "^7.1.2" +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@^1.0.1: version "1.4.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" @@ -4869,6 +5188,11 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -5287,7 +5611,7 @@ hexoid@^1.0.0: resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== -hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6291,6 +6615,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -6313,6 +6642,11 @@ json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonpointer@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" + integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== + jsonwebtoken@^8.5.1: version "8.5.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" @@ -6406,7 +6740,7 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -leven@^3.1.0: +leven@^3.1.0, "leven@^3.1.0 < 4": version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== @@ -7905,6 +8239,16 @@ react-transition-group@2.9.0: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-universal-interface@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b" @@ -8093,6 +8437,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -8141,6 +8490,15 @@ resolve@^1.1.7, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^1.19.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^2.0.0-next.4: version "2.0.0-next.4" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660" @@ -8413,6 +8771,11 @@ source-map@0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA== +source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -8646,6 +9009,11 @@ styled-jsx@5.1.1: dependencies: client-only "0.0.1" +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + stylis@^4.0.6: version "4.3.0" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c" From 716f75cb912438cd0501b96a9af3ecc079276363 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:56:50 +0100 Subject: [PATCH 03/21] commit first working barebones version w MUI --- src/pages/import.tsx | 168 +++++++++++++++++++ src/pages/page.tsx | 165 +++++++++++++++++++ src/pages/schema.json | 372 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 705 insertions(+) create mode 100644 src/pages/import.tsx create mode 100644 src/pages/page.tsx create mode 100644 src/pages/schema.json diff --git a/src/pages/import.tsx b/src/pages/import.tsx new file mode 100644 index 000000000..aa9c0db66 --- /dev/null +++ b/src/pages/import.tsx @@ -0,0 +1,168 @@ +//'use client' +import { + Accordion, + AccordionDetails, + AccordionSummary, + Alert, + Box, + Button, + Container, + Snackbar, + Typography +} from "@mui/material"; +import Ajv from "ajv"; +import React, { useState } from "react"; +import Layout from '../components/layout'; +import schema from "./schema.json"; +const addFormats = require("ajv-formats"); +const betterAjvErrors = require("better-ajv-errors").default + +function FileUploadClientComponent() { + const [alertMessage, setAlertMessage] = useState("") + const [validationErrors, setValidationErrors] = useState([]) + + const handleValidationErrors = (rawJSON: object) => { + try { + const ajv = new Ajv({ allErrors: true, verbose: true }); + addFormats(ajv); + const compiledSchema = ajv.compile(schema); + const validate = compiledSchema(rawJSON); + if (!validate) { + const betterErrors = betterAjvErrors(schema, rawJSON, compiledSchema.errors, {format: 'js'}) + console.log(betterErrors) + setValidationErrors(betterErrors); + setAlertMessage( + betterErrors.length > 0 + ? "Error: Schema validation errors found." + : "Error: Schema validation failed, but no errors were provided." + ); + } else { + setAlertMessage( + "JSON file and schema successfully validated. 🎊 Proceed to database upload." + ); + setValidationErrors([]); + } + } catch (error) { + console.error("Error during validation:", error); + setAlertMessage( + "Error: An unexpected error occurred during validation. See browser console log for more details." + ); + } + }; + function safelyParseJSON(json: string) { + try { + return JSON.parse(json); + } catch (e) { + setAlertMessage(`Error: Could not parse JSON file. ${e}`); + return null; + } + } + + const handleFileSelect = (event: React.ChangeEvent) => { + const file = event.target.files ? event.target.files[0] : null; + + if (file) { + const reader = new FileReader(); + + reader.onload = (event) => { + const file = event.target?.result; + if (typeof file === "string") { + const isValidJSON = safelyParseJSON(file); + if (isValidJSON) { + handleValidationErrors(isValidJSON); + } else { + setAlertMessage("Error: Not a valid JSON file"); + } + } + }; + reader.readAsText(file); + } + }; + + return ( + + handleFileSelect(event)} + /> + + {alertMessage && ( + + + {alertMessage} + + + )} + + Validation Errors + {validationErrors.map((error, index) => ( + + + {error['error']} + + + +
{JSON.stringify(error, null, 2)}
+
+
+
+ ))} +
+
+ ); +} + +const BulkImport = (): JSX.Element => { + return ( + + + + + Bulk Data Import V2 + + + Upload Area, Crag, Climbing Data. + + + + + + + + + + + + + + + + Database Response Log + + + + + ) +} + +export default BulkImport \ No newline at end of file diff --git a/src/pages/page.tsx b/src/pages/page.tsx new file mode 100644 index 000000000..1bb6903ae --- /dev/null +++ b/src/pages/page.tsx @@ -0,0 +1,165 @@ +'use client' +import { + Alert, + Box, + Button, + Container, + Typography, + Accordion, + AccordionDetails, + AccordionSummary, + Snackbar +} from "@mui/material"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import Ajv from "ajv"; +import React, { useState } from "react"; +import schema from "./schema/schema.json"; +const addFormats = require("ajv-formats"); +const betterAjvErrors = require("better-ajv-errors").default + +function FileUploadClientComponent() { + const [alertMessage, setAlertMessage] = useState("") + const [validationErrors, setValidationErrors] = useState([]) + + const handleValidationErrors = (rawJSON: object) => { + try { + const ajv = new Ajv({ allErrors: true, verbose: true, strict: false }); + addFormats(ajv); + const compiledSchema = ajv.compile(schema); + const validate = compiledSchema(rawJSON); + if (!validate) { + const betterErrors = betterAjvErrors(schema, rawJSON, compiledSchema.errors, {format: 'js'}) + console.log(betterErrors) + setValidationErrors(betterErrors); + setAlertMessage( + betterErrors.length > 0 + ? "Error: Schema validation errors found." + : "Error: Schema validation failed, but no errors were provided." + ); + } else { + setAlertMessage( + "JSON file and schema successfully validated. 🎊 Proceed to database upload." + ); + setValidationErrors([]); + } + } catch (error) { + console.error("Error during validation:", error); + setAlertMessage( + "Error: An unexpected error occurred during validation. See browser console log for more details." + ); + } + }; + function safelyParseJSON(json: string) { + try { + return JSON.parse(json); + } catch (e) { + setAlertMessage(`Error: Could not parse JSON file. ${e}`); + return null; + } + } + + const handleFileSelect = (event: React.ChangeEvent) => { + const file = event.target.files ? event.target.files[0] : null; + + if (file) { + const reader = new FileReader(); + + reader.onload = (event) => { + const file = event.target?.result; + if (typeof file === "string") { + const isValidJSON = safelyParseJSON(file); + if (isValidJSON) { + handleValidationErrors(isValidJSON); + } else { + setAlertMessage("Error: Not a valid JSON file"); + } + } + }; + reader.readAsText(file); + } + }; + + return ( + + handleFileSelect(event)} + /> + + {alertMessage && ( + + + {alertMessage} + + + )} + + Validation Errors + {validationErrors.map((error, index) => ( + + } + > + {error['error']} + + + +
{JSON.stringify(error, null, 2)}
+
+
+
+ ))} +
+
+ ); +} + +export default function Home() { + + return ( + + + + Bulk Data Import V2 + + + Upload Area, Crag, Climbing Data. + + + + + + + + + + + + + + + + Database Response Log + + + + ); +} diff --git a/src/pages/schema.json b/src/pages/schema.json new file mode 100644 index 000000000..c82260345 --- /dev/null +++ b/src/pages/schema.json @@ -0,0 +1,372 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "areas": { + "description": "An array of area objects.", + "type": "array", + "items": { + "$ref": "#/definitions/Area" + } + } + }, + "required": [ + "areas" + ], + "definitions": { + "Area": { + "description": "Represents a climbing area with its properties and nested areas or climbs.", + "type": "object", + "properties": { + "id": { + "description": "A unique identifier for the area.", + "type": "string", + "format": "uuid" + }, + "areaName": { + "description": "The name that this area is commonly identified by within the climbing community.", + "type": "string" + }, + "climbs": { + "description": "The climbs that appear within this area. If this area is a leaf node, then these climbs can be understood as appearing physically on - rather than within - this area.", + "type": "array", + "items": { + "$ref": "#/definitions/Climb" + } + }, + "children": { + "description": "Nested areas within the current area. Can be either an array of areas or a single area object.", + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/Area" + } + }, + { + "type": "object", + "$ref": "#/definitions/Area" + } + ] + }, + "gradeContext": { + "description": "The grading system used for climbing/bouldering in the area. Inherits from parent node if current node does not have one. UIAA = UIAA/font, US = yds/vscale, FR = french/font (see https://github.com/OpenBeta/openbeta-graphql/blob/9c517329db079c922fe7f092a78b658cb295e158/src/GradeUtils.ts#L40)", + "type": "string", + "enum": [ + "ALSK", + "AU", + "BRZ", + "FIN", + "FR", + "HK", + "NWG", + "POL", + "SA", + "SWE", + "SX", + "UIAA", + "UK", + "US" + ] + }, + "metadata": { + "description": "Additional metadata about the area.", + "type": "object", + "properties": { + "leaf": { + "description": "Indicates if the area has no child areas.", + "type": "boolean" + }, + "isBoulder": { + "description": "Indicates if the area is primarily for bouldering.", + "type": "boolean" + }, + "lnglat": { + "description": "The longitude and latitude of the area.", + "type": "object", + "properties": { + "type": { + "description": "The type of the geo-coordinate, always 'Point'.", + "type": "string", + "enum": [ + "Point" + ] + }, + "coordinates": { + "description": "The longitude and latitude coordinates.", + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2 + } + } + }, + "bbox": { + "description": "The bounding box of the area.", + "type": "array", + "items": { + "type": "number" + }, + "minItems": 4, + "maxItems": 4 + }, + "leftRightIndex": { + "description": "A numeric index used for sorting climbs from left to right (of a wall).", + "type": "number" + }, + "ext_id": { + "description": "An external identifier for the area.", + "type": "string" + }, + "additionalProperties": true + }, + "additionalProperties": true + }, + "content": { + "description": "Content related to the area, such as descriptions.", + "type": "object", + "properties": { + "description": { + "description": "A textual description of the area.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "anyOf": [ + { + "required": [ + "areaName" + ] + }, + { + "required": [ + "id" + ] + } + ], + "additionalProperties": false + }, + "Climb": { + "description": "Represents a climbing route with its properties.", + "type": "object", + "properties": { + "id": { + "description": "A unique identifier for the climb.", + "type": "string", + "format": "uuid" + }, + "name": { + "description": "The name that this climb is commonly identified by.", + "type": "string" + }, + "grade": { + "description": "The difficulty grade of the climb. Must be coherent with gradeContext. I. e. gradeContext = 'US' requires denomination in yds/vscale (climbing/bouldering), so '5.11'/'V14', 'FR' would be french/font '9c+'/'9a', 'UIIA' would be uiaa/font '9+'/'9a'. (see https://github.com/OpenBeta/sandbag)", + "type": "string" + }, + "gradeContext": { + "description": "The grading system used for climbing/bouldering in the area. Normally inherited from parent area. UIAA = UIAA/font, US = yds/vscale, FR = french/font (climbing/bouldering) (see https://github.com/OpenBeta/openbeta-graphql/blob/9c517329db079c922fe7f092a78b658cb295e158/src/GradeUtils.ts#L40)", + "type": "string", + "enum": [ + "ALSK", + "AU", + "BRZ", + "FIN", + "FR", + "HK", + "NWG", + "POL", + "SA", + "SWE", + "SX", + "UIAA", + "UK", + "US" + ] + }, + "fa": { + "description": "The first ascent information of the climb. Usually formatted as: name(s) (year).", + "type": "string" + }, + "type": { + "description": "What sort of climb is this? Climbs can combine these fields, which is why this is not an enumeration.", + "type": "object", + "properties": { + "trad": { + "type": "boolean" + }, + "sport": { + "type": "boolean" + }, + "bouldering": { + "type": "boolean" + }, + "deepwatersolo": { + "type": "boolean" + }, + "alpine": { + "type": "boolean" + }, + "ice": { + "type": "boolean" + }, + "mixed": { + "type": "boolean" + }, + "aid": { + "type": "boolean" + }, + "tr": { + "description": "Top Rope", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "safety": { + "description": "The safety rating of a climb based on US movie ratings. See https://github.com/OpenBeta/openbeta-graphql/blob/9c517329db079c922fe7f092a78b658cb295e158/src/graphql/schema/Climb.gql#L177.", + "type": "string", + "enum": [ + "UNSPECIFIED", + "PG", + "PG13", + "R", + "X" + ] + }, + "boltsCount": { + "description": "The number of bolts (fixed anchors) on the climb.", + "type": "number" + }, + "length": { + "description": "Total length in meters if known (-1 otherwise)", + "type": "number" + }, + "pitches": { + "description": "List of Pitch objects representing individual pitches of a multi-pitch climb", + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "description": "A unique identifier for the pitch.", + "type": "string", + "format": "uuid" + }, + "parentId": { + "description": "The unique identifier of the parent climb.", + "type": "string", + "format": "uuid" + }, + "pitchNumber": { + "description": "The number of the pitch in the sequence.", + "type": "number" + }, + "grade": { + "description": "The difficulty grade of the pitch (see Climb.grade)", + "type": "string" + }, + "disciplines": { + "description": "The climbing disciplines applicable to the pitch. (see Climb.grade)", + "type": "object" + }, + "length": { + "description": "The length of the pitch in meters", + "type": "number" + }, + "boltsCount": { + "description": "The number of bolts (fixed anchors) on the pitch.", + "type": "number" + }, + "description": { + "description": "A textual description of the pitch.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "metadata": { + "description": "Additional metadata about the climb.", + "type": "object", + "properties": { + "lnglat": { + "description": "The longitude and latitude of the climb.", + "type": "object", + "properties": { + "type": { + "description": "The type of the geo-coordinate, always 'Point'.", + "type": "string", + "enum": [ + "Point" + ] + }, + "coordinates": { + "description": "The longitude and latitude coordinates.", + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2 + } + } + }, + "leftRightIndex": { + "description": "A numeric index used for sorting or positioning.", + "type": "number" + }, + "mp_id": { + "description": "An identifier used in Mountain Project's database.", + "type": "string" + }, + "mp_crag_id": { + "description": "The identifier of the crag in Mountain Project's database.", + "type": "string" + }, + "areaRef": { + "description": "A reference to the area containing the climb.", + "type": "object" + } + }, + "additionalProperties": false + }, + "content": { + "description": "Composable attributes for this climb, these are the bread and butter guidebook-like data that make up the bulk of the text beta for this climb.", + "type": "object", + "properties": { + "description": { + "description": "The description of this climb, this is the main text field for this climb. This contains beta, visual descriptors, and any other information useful to identifying and attempting the climb", + "type": "string" + }, + "protection": { + "description": "What do climbers need to know about making a safe attempt of this climb? What gear do they need, what are the hazards, etc.", + "type": "string" + }, + "location": { + "description": "Information regarding Approach and other location context for this climb. Could also include information about the situation of this specific climb.", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "anyOf": [ + { + "required": [ + "name" + ] + }, + { + "required": [ + "id" + ] + } + ], + "additionalProperties": false + } + } +} \ No newline at end of file From 27a7de8ac4e227063722e2b0ddf7e70802ed45aa Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:42:08 +0100 Subject: [PATCH 04/21] fix file upload btn --- src/pages/import.tsx | 116 +++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 65 deletions(-) diff --git a/src/pages/import.tsx b/src/pages/import.tsx index aa9c0db66..f1160b0d0 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -1,25 +1,15 @@ //'use client' -import { - Accordion, - AccordionDetails, - AccordionSummary, - Alert, - Box, - Button, - Container, - Snackbar, - Typography -} from "@mui/material"; import Ajv from "ajv"; -import React, { useState } from "react"; +import React, { useState, useRef } from "react"; // Import useRef +import { toast } from 'react-toastify'; import Layout from '../components/layout'; import schema from "./schema.json"; const addFormats = require("ajv-formats"); const betterAjvErrors = require("better-ajv-errors").default function FileUploadClientComponent() { - const [alertMessage, setAlertMessage] = useState("") const [validationErrors, setValidationErrors] = useState([]) + const fileInputRef = useRef(null); // Create a ref for the file input const handleValidationErrors = (rawJSON: object) => { try { @@ -31,20 +21,20 @@ function FileUploadClientComponent() { const betterErrors = betterAjvErrors(schema, rawJSON, compiledSchema.errors, {format: 'js'}) console.log(betterErrors) setValidationErrors(betterErrors); - setAlertMessage( + toast.error( betterErrors.length > 0 ? "Error: Schema validation errors found." : "Error: Schema validation failed, but no errors were provided." ); } else { - setAlertMessage( + toast.success( "JSON file and schema successfully validated. 🎊 Proceed to database upload." ); setValidationErrors([]); } } catch (error) { console.error("Error during validation:", error); - setAlertMessage( + toast.error( "Error: An unexpected error occurred during validation. See browser console log for more details." ); } @@ -53,7 +43,7 @@ function FileUploadClientComponent() { try { return JSON.parse(json); } catch (e) { - setAlertMessage(`Error: Could not parse JSON file. ${e}`); + toast.error(`Error: Could not parse JSON file. ${e}`); return null; } } @@ -71,7 +61,7 @@ function FileUploadClientComponent() { if (isValidJSON) { handleValidationErrors(isValidJSON); } else { - setAlertMessage("Error: Not a valid JSON file"); + toast.error("Error: Not a valid JSON file"); } } }; @@ -79,61 +69,58 @@ function FileUploadClientComponent() { } }; + // Function to trigger file input click + const handleButtonClick = () => { + fileInputRef.current?.click(); + }; + return ( - +
handleFileSelect(event)} + ref={fileInputRef} // Attach the ref to the file input /> - {alertMessage && ( - - - {alertMessage} - - - )} - - Validation Errors +
+
Validation Errors
{validationErrors.map((error, index) => ( - - - {error['error']} - - - -
{JSON.stringify(error, null, 2)}
-
-
-
+
+
+
{error['error']}
+
+
+
{JSON.stringify(error, null, 2)}
+
+
))} - - +
+
); } const BulkImport = (): JSX.Element => { return ( - - - +
+

Bulk Data Import V2 - - +

+
Upload Area, Crag, Climbing Data. - - - +
+
+
  • - Allows for adding or changing large amounts of data at once.
  • - Utilizes JSON schema to ensure correct data structure.
  • @@ -144,23 +131,22 @@ const BulkImport = (): JSX.Element => {
  • - Note: OpenBeta’s route database is licensed as CC-BY-SA 4.0.
  • - Special permissions required (ask on Discord).
- - +
+
- +
- +
- - - - + +
+
Database Response Log - - - +
+
) } From 2b2cc72f462efd126948aca83a5a2c931a1cb083 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:50:05 +0100 Subject: [PATCH 05/21] fix ajv --- package.json | 1 + yarn.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index db534959b..b2ee02fbe 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@turf/line-to-polygon": "^6.5.0", "@udecode/zustood": "^1.1.3", "@vercel/edge": "^1.1.1", + "ajv": "^8.12.0", "ajv-formats": "^2.1.1", "auth0": "^2.42.0", "awesome-debounce-promise": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index 848ef3bb9..06296275c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3277,7 +3277,7 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0: +ajv@^8.0.0, ajv@^8.12.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== From b8df83268861a7172b901e9e8bd3481af6488995 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:24:48 +0100 Subject: [PATCH 06/21] snapshot before restyling --- src/pages/import.tsx | 7 +- src/pages/page.tsx | 165 ------------------------------------------- 2 files changed, 4 insertions(+), 168 deletions(-) delete mode 100644 src/pages/page.tsx diff --git a/src/pages/import.tsx b/src/pages/import.tsx index f1160b0d0..dca72c663 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -6,6 +6,7 @@ import Layout from '../components/layout'; import schema from "./schema.json"; const addFormats = require("ajv-formats"); const betterAjvErrors = require("better-ajv-errors").default + function FileUploadClientComponent() { const [validationErrors, setValidationErrors] = useState([]) @@ -112,8 +113,8 @@ function FileUploadClientComponent() { const BulkImport = (): JSX.Element => { return ( -
-

+
+

Bulk Data Import V2

@@ -145,7 +146,7 @@ const BulkImport = (): JSX.Element => {

Database Response Log -
+
) diff --git a/src/pages/page.tsx b/src/pages/page.tsx deleted file mode 100644 index 1bb6903ae..000000000 --- a/src/pages/page.tsx +++ /dev/null @@ -1,165 +0,0 @@ -'use client' -import { - Alert, - Box, - Button, - Container, - Typography, - Accordion, - AccordionDetails, - AccordionSummary, - Snackbar -} from "@mui/material"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import Ajv from "ajv"; -import React, { useState } from "react"; -import schema from "./schema/schema.json"; -const addFormats = require("ajv-formats"); -const betterAjvErrors = require("better-ajv-errors").default - -function FileUploadClientComponent() { - const [alertMessage, setAlertMessage] = useState("") - const [validationErrors, setValidationErrors] = useState([]) - - const handleValidationErrors = (rawJSON: object) => { - try { - const ajv = new Ajv({ allErrors: true, verbose: true, strict: false }); - addFormats(ajv); - const compiledSchema = ajv.compile(schema); - const validate = compiledSchema(rawJSON); - if (!validate) { - const betterErrors = betterAjvErrors(schema, rawJSON, compiledSchema.errors, {format: 'js'}) - console.log(betterErrors) - setValidationErrors(betterErrors); - setAlertMessage( - betterErrors.length > 0 - ? "Error: Schema validation errors found." - : "Error: Schema validation failed, but no errors were provided." - ); - } else { - setAlertMessage( - "JSON file and schema successfully validated. 🎊 Proceed to database upload." - ); - setValidationErrors([]); - } - } catch (error) { - console.error("Error during validation:", error); - setAlertMessage( - "Error: An unexpected error occurred during validation. See browser console log for more details." - ); - } - }; - function safelyParseJSON(json: string) { - try { - return JSON.parse(json); - } catch (e) { - setAlertMessage(`Error: Could not parse JSON file. ${e}`); - return null; - } - } - - const handleFileSelect = (event: React.ChangeEvent) => { - const file = event.target.files ? event.target.files[0] : null; - - if (file) { - const reader = new FileReader(); - - reader.onload = (event) => { - const file = event.target?.result; - if (typeof file === "string") { - const isValidJSON = safelyParseJSON(file); - if (isValidJSON) { - handleValidationErrors(isValidJSON); - } else { - setAlertMessage("Error: Not a valid JSON file"); - } - } - }; - reader.readAsText(file); - } - }; - - return ( - - handleFileSelect(event)} - /> - - {alertMessage && ( - - - {alertMessage} - - - )} - - Validation Errors - {validationErrors.map((error, index) => ( - - } - > - {error['error']} - - - -
{JSON.stringify(error, null, 2)}
-
-
-
- ))} -
-
- ); -} - -export default function Home() { - - return ( - - - - Bulk Data Import V2 - - - Upload Area, Crag, Climbing Data. - - - - - - - - - - - - - - - - Database Response Log - - - - ); -} From c5e3739fd498b6246a4f05d2b8782bacc8815e96 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Fri, 16 Feb 2024 11:57:44 +0100 Subject: [PATCH 07/21] improve styling --- .vscode/settings.json | 2 +- src/pages/import.tsx | 135 +++++++++++++++++++++++++----------------- 2 files changed, 82 insertions(+), 55 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ecb2334dd..bc4464afc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ "[typescriptreact]": { "editor.formatOnType": true, "editor.formatOnSave": true, - "editor.defaultFormatter": "standard.vscode-standard" + "editor.defaultFormatter": "vscode.typescript-language-features" }, "[typescript]": { "editor.formatOnSave": true, diff --git a/src/pages/import.tsx b/src/pages/import.tsx index dca72c663..e353351b2 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -4,13 +4,16 @@ import React, { useState, useRef } from "react"; // Import useRef import { toast } from 'react-toastify'; import Layout from '../components/layout'; import schema from "./schema.json"; +import { File, Lightbulb, Detective } from "@phosphor-icons/react"; const addFormats = require("ajv-formats"); const betterAjvErrors = require("better-ajv-errors").default - + function FileUploadClientComponent() { + const fileInputRef = useRef(null) + const [fileName, setFileName] = useState(null); const [validationErrors, setValidationErrors] = useState([]) - const fileInputRef = useRef(null); // Create a ref for the file input + const [isValidationSuccessful, setIsValidationSuccessful] = useState(false); const handleValidationErrors = (rawJSON: object) => { try { @@ -19,7 +22,7 @@ function FileUploadClientComponent() { const compiledSchema = ajv.compile(schema); const validate = compiledSchema(rawJSON); if (!validate) { - const betterErrors = betterAjvErrors(schema, rawJSON, compiledSchema.errors, {format: 'js'}) + const betterErrors = betterAjvErrors(schema, rawJSON, compiledSchema.errors, { format: 'js' }) console.log(betterErrors) setValidationErrors(betterErrors); toast.error( @@ -31,7 +34,8 @@ function FileUploadClientComponent() { toast.success( "JSON file and schema successfully validated. 🎊 Proceed to database upload." ); - setValidationErrors([]); + setValidationErrors([]) + setIsValidationSuccessful(true) } } catch (error) { console.error("Error during validation:", error); @@ -50,6 +54,10 @@ function FileUploadClientComponent() { } const handleFileSelect = (event: React.ChangeEvent) => { + toast.dismiss() + setValidationErrors([]) + setIsValidationSuccessful(false) + const file = event.target.files ? event.target.files[0] : null; if (file) { @@ -76,80 +84,99 @@ function FileUploadClientComponent() { }; return ( -
+ <>
+ + +

Validation Result

+ handleFileSelect(event)} - ref={fileInputRef} // Attach the ref to the file input - /> + onChange={(event) => { + handleFileSelect(event); + if (event.target.files && event.target.files[0]) { + setFileName(event.target.files[0].name); // Assuming setFileName is a state setter function defined to hold the file name + } + }} + ref={fileInputRef} /> + -
-
Validation Errors
- {validationErrors.map((error, index) => ( -
-
-
{error['error']}
-
-
-
{JSON.stringify(error, null, 2)}
-
-
- ))} -
+

{fileName ? `Selected file: ${fileName}` : "No file selected."}

{/* Display the selected file name or indicate no file selected */} + +
+
+ + +
+ +

Validation Result

+ {validationErrors.length === 0 ? ( +

No errors found.

+ ) : ( + validationErrors.map((error, index) => ( +
+
+
{error['error']}
+
+
+
{JSON.stringify(error, null, 2)}
+
+
+ )) + )} +
+
+

+
+
+ +
+
+ ); } + const BulkImport = (): JSX.Element => { return ( -
-

+
+

Bulk Data Import V2 -

-
- Upload Area, Crag, Climbing Data. -
-
-
-

+
+ +
+
    +
  • Allows for adding or changing large amounts of data at once.
  • +
  • Utilizes JSON schema to ensure correct data structure.
  • +
  • Example Uploads and detailed instructions are available in the README.
  • +
  • Note: OpenBeta’s route database is licensed as CC-BY-SA 4.0.
  • +
  • Special permissions required (ask on Discord).
- -
- +
+ Loading...
}> + +
-
- -
-
- Database Response Log -
- ) -} + ); +}; -export default BulkImport \ No newline at end of file +export default BulkImport; \ No newline at end of file From 800a892b37147900d68b811647cf14eb98a5b9ba Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Fri, 16 Feb 2024 13:26:08 +0100 Subject: [PATCH 08/21] polish styling --- src/pages/import.tsx | 72 +++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/src/pages/import.tsx b/src/pages/import.tsx index e353351b2..4046558d9 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -9,7 +9,7 @@ const addFormats = require("ajv-formats"); const betterAjvErrors = require("better-ajv-errors").default -function FileUploadClientComponent() { +function FileUploadAndValidationClientComponent() { const fileInputRef = useRef(null) const [fileName, setFileName] = useState(null); const [validationErrors, setValidationErrors] = useState([]) @@ -84,46 +84,45 @@ function FileUploadClientComponent() { }; return ( - <>
- - -

Validation Result

+ <>
+
+

File Upload

+
+ +
{ - handleFileSelect(event); + handleFileSelect(event) if (event.target.files && event.target.files[0]) { - setFileName(event.target.files[0].name); // Assuming setFileName is a state setter function defined to hold the file name + setFileName(event.target.files[0].name) } }} ref={fileInputRef} /> - -

{fileName ? `Selected file: ${fileName}` : "No file selected."}

{/* Display the selected file name or indicate no file selected */} - -
-
- + +
+

{fileName ? `Selected file: ${fileName}` : "No file selected."}

-
-

Validation Result

- {validationErrors.length === 0 ? ( -

No errors found.

- ) : ( - validationErrors.map((error, index) => ( +
+ {validationErrors.length > 0 && ( +
+
+

Validation Errors

+ {validationErrors.map((error, index) => (
{error['error']}
@@ -132,14 +131,14 @@ function FileUploadClientComponent() {
{JSON.stringify(error, null, 2)}
- )) - )} + ))} +
-
+ )}

-
@@ -154,24 +153,23 @@ const BulkImport = (): JSX.Element => {

- Bulk Data Import V2 + Bulk Data Import

  • Allows for adding or changing large amounts of data at once.
  • -
  • Utilizes JSON schema to ensure correct data structure.
  • -
  • Example Uploads and detailed instructions are available in the README.
  • +
  • Utilizes this JSON schema to ensure correct data structure.
  • +
  • Example upload files and detailed instructions are available in this README.
  • +

  • Note: OpenBeta’s route database is licensed as CC-BY-SA 4.0.
  • -
  • Special permissions required (ask on Discord).
  • +
  • Need help? Find us on Discord.
- Loading...
}> - - +
From 318a3a35bd49f48bfe286dcc5a002e820e0ba1cd Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:38:07 +0100 Subject: [PATCH 09/21] rem unused deps, rework folder-file structure, update readme --- package.json | 3 - public/bulk-import/README.md | 72 ++++ .../bulk-import/bulk-import-schema.json | 0 .../add-areas-with-climbs.json | 128 +++++++ .../example-uploads/update-areas.json | 87 +++++ .../example-uploads/update-climbs.json | 36 ++ src/pages/import.tsx | 10 +- yarn.lock | 311 +----------------- 8 files changed, 332 insertions(+), 315 deletions(-) create mode 100644 public/bulk-import/README.md rename src/pages/schema.json => public/bulk-import/bulk-import-schema.json (100%) create mode 100644 public/bulk-import/example-uploads/add-areas-with-climbs.json create mode 100644 public/bulk-import/example-uploads/update-areas.json create mode 100644 public/bulk-import/example-uploads/update-climbs.json diff --git a/package.json b/package.json index b2ee02fbe..4c5a5dd15 100644 --- a/package.json +++ b/package.json @@ -10,15 +10,12 @@ "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", - "@emotion/react": "^11.11.3", - "@emotion/styled": "^11.11.0", "@google-cloud/storage": "^6.11.0", "@headlessui/react": "^1.7.15", "@heroicons/react": "2.0.13", "@hookform/resolvers": "^3.1.1", "@lexical/react": "^0.7.5", "@math.gl/web-mercator": "3.6.2", - "@mui/material": "^5.15.10", "@openbeta/sandbag": "^0.0.51", "@phosphor-icons/react": "^2.0.14", "@radix-ui/react-alert-dialog": "^1.0.0", diff --git a/public/bulk-import/README.md b/public/bulk-import/README.md new file mode 100644 index 000000000..6fcb23d2f --- /dev/null +++ b/public/bulk-import/README.md @@ -0,0 +1,72 @@ +# Introduction +This readme is supplementary information to the bulk import feature of OpenBeta.io (https://openbeta.io/import). + +# Data Validation +## Client-Side Schema Validation +All uploaded data is validated against a JSON schema in the user's browser beforehand. The schema represents a simplified version of the actual database schema, specifically tailored to end-users in order to make it comfortable to mass-contribute climbing data to OpenBeta without needing to dive deep into technicalities. (For a full specification of the database, please refer to https://github.com/OpenBeta/openbeta-graphql). + +## Server-Side Schema Validation +Each upload is tested against the database as an uncommitted transaction, only if each line has passed the validation against the database the whole file will be written. + +# Example Files +This document will refer to the following example files: +* The schema file used for client-side validation: `https://openbeta.io/bulk-import/bulk-import-schema.json` +* Example files that guide you how to structure your JSON files for mass upload + * (1) `https://openbeta.io/bulk-import/example-uploads/add-areas-with-climbs.json` + * (2) `https://openbeta.io/bulk-import/example-uploads/update-areas-with-climbs.json` + * (3) `https://openbeta.io/bulk-import/example-uploads/update-climbs-with-pitches.json` + +# Schema +## General Data Hierarchy +This is how OpenBeta's data is structured. Generally speaking, the database schema distinguishes between `Area`s and `Climb`s: + +> - Country (`Area`) +> - Region (`Area`) +> - Sub-Region (`Area`) +> - Sub-Sub-Region (`Area`) +> - Crag 1 (`Area`) +> - Sector 1 (`Area`) +> - Sector 2 (`Area`) +> - Sector N (`Area`) +> - Climb 1 (`Climb`) +> - Climb 2 (`Climb`) +> - Climb N (`Climb`) +> - Pitch 1 (`Climb.Pitch`) +> - Pitch 2 (`Climb.Pitch`) +> - Crag 2 (`Area`) +> - Crag N (`Area`) + + +## Example +Let's look at the example `add-areas-with-climbs.json` for an illustration: + +> - "USA" (Country, Type: `Area`) +> - "Utah" (State, Type: `Area`) +> - "Southeast Utah" (Region, Type: `Area`) +> - "Indian Creek" (Crag, Type: `Area`) +> - "Supercrack Buttress" (Sector, Type: `Area`) +> - "The Key Flake" (Route, Type: `Climb`) +> - "Incredible Hand Crack" (Route, Type: `Climb`) +> - "Pitch 1" (Pitch, Type: `Climb.Pitch`) +> - "Pitch 2" (Pitch, Type: `Climb.Pitch`) + +### A note on `Area`s +* an `Area` may be a country, state, region, crag or sector (and may be infinetely nested) +* it is only the leaf area node that determines its type, which is automatically determined server-side based on certain criteria: + * an area with *climbs* => a *sector* (then, metadata property `isLeaf` is set to `TRUE`) + * an area with *sectors* => a *crag* (not a leaf) + * if an area has only boulders as children => *boulder field* (metadata property `isBoulder` is set to `TRUE`) + +# How to Update Data +* On the use of `uuid`s for `Area`, `Climb`, or `Climb.Pitch`: + * The schema requires EITHER + *`name` (for areas: `areaName`) + * OR `uuid` (for pitches: `id`) + * omitting an uuid => **creates** new entries + * providing an uuid => **updates** existing entries + +* Deleting climbs is not supported (join our Discord for help: https://discord.gg/a6vuuTQxS8) + +# Nightly Database Dump +* Find all current database data here: https://github.com/OpenBeta/openbeta-export/tree/production (updated nightly) + diff --git a/src/pages/schema.json b/public/bulk-import/bulk-import-schema.json similarity index 100% rename from src/pages/schema.json rename to public/bulk-import/bulk-import-schema.json diff --git a/public/bulk-import/example-uploads/add-areas-with-climbs.json b/public/bulk-import/example-uploads/add-areas-with-climbs.json new file mode 100644 index 000000000..d24742afa --- /dev/null +++ b/public/bulk-import/example-uploads/add-areas-with-climbs.json @@ -0,0 +1,128 @@ +{ + "areas": [ + { + "areaName": "Utah", + "children": [ + { + "areaName": "Southeast Utah", + "children": [ + { + "areaName": "Indian Creek", + "children": [ + { + "areaName": "Supercrack Buttress", + "climbs": [ + { + "name": "The Key Flake", + "grade": "5.10", + "fa": "unknown", + "type": { + "trad": true + }, + "safety": "UNSPECIFIED", + "metadata": { + "lnglat": { + "type": "Point", + "coordinates": [ + -109.54552, + 38.03635 + ] + }, + "leftRightIndex": 1 + }, + "content": { + "description": "Cool off-width that requires off-width and face skills.", + "protection": "Anchors hidden up top. Need 80m to make it all the way down.", + "location": "Opposite keyhole flake. Obvious right leaning offwidth that starts atop 20 ft boulder." + } + }, + { + "name": "Incredible Hand Crack", + "grade": "5.10", + "fa": "Rich Perch, John Bragg, Doug Snively, and Anne Tarver, 1978", + "type": { + "trad": true + }, + "safety": "UNSPECIFIED", + "metadata": { + "lnglat": { + "type": "Point", + "coordinates": [ + -109.54552, + 38.03635 + ] + }, + "leftRightIndex": 2 + }, + "content": { + "description": "Route starts at the top of the trail from the parking lot to Supercrack Buttress.", + "protection": "Cams from 2-2.5\". Heavy on 2.5\" (#2 Camalot)", + "location": "" + }, + "pitches": [ + { + "pitchNumber": 1, + "grade": "5.10", + "disciplines": { + "trad": true + }, + "length": 100, + "boltsCount": 0, + "description": "A classic hand crack that widens slightly towards the top. Requires a range of cam sizes. Sustained and excellent quality." + }, + { + "pitchNumber": 2, + "grade": "5.9", + "disciplines": { + "trad": true + }, + "length": 30, + "boltsCount": 0, + "description": "Easier climbing with good protection. Features a mix of crack sizes. Shorter than the first pitch but equally enjoyable." + } + ] + } + ], + "gradeContext": "US", + "metadata": { + "isBoulder": false, + "isDestination": false, + "leaf": true, + "lnglat": { + "type": "Point", + "coordinates": [ + -109.54552, + 38.03635 + ] + }, + "bbox": [ + -109.54609091005857, + 38.03590033981814, + -109.54494908994141, + 38.03679966018186 + ] + }, + "content": { + "description": "" + } + } + ], + "metadata": { + "lnglat": { + "type": "Point", + "coordinates": [ + -109.5724044642857, + 38.069429035714286 + ] + } + }, + "content": { + "description": "Indian Creek is a crack climbing mecca in the southeastern region of Utah, USA. Located within the [Bears Ears National Monument](https://en.wikipedia.org/wiki/Bears_Ears_National_Monument)." + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/public/bulk-import/example-uploads/update-areas.json b/public/bulk-import/example-uploads/update-areas.json new file mode 100644 index 000000000..1244817a8 --- /dev/null +++ b/public/bulk-import/example-uploads/update-areas.json @@ -0,0 +1,87 @@ +{ + "areas": [ + { + "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "name": "Area with updated name, coordinates, description", + "metadata": { + "lnglat": { + "type": "Point", + "coordinates": [ + -99.99, + 11.11 + ] + } + }, + "content": { + "description": "Some new description." + } + }, + { + "id": "a3f159cb-2f10-4e3a-8bce-6689a70b76b8", + "name": "Existing area, where new children areas are added", + "children": + [ + { + "id": "b1a6492f-8d58-4d5b-8b1a-51b75f51e990", + "areaName": "New Area 1", + "metadata": { + "isDestination": true, + "leaf": false, + "isBoulder": false, + "lnglat": { + "type": "Point", + "coordinates": [ + -100.00, + 20.00 + ] + }, + "leftRightIndex": 1 + }, + "content": { + "description": "Description of New Area 1" + } + }, + { + "id": "c2d3e1f4-41e4-4dce-9bce-5b6a6395d570", + "areaName": "New Area 2", + "metadata": { + "isDestination": false, + "leaf": true, + "isBoulder": true, + "lnglat": { + "type": "Point", + "coordinates": [ + -101.00, + 21.00 + ] + }, + "leftRightIndex": 2 + }, + "content": { + "description": "Description of New Area 2" + } + }, + { + "id": "d4e5f6a7-8b9c-4d0e-9abc-7d8e9fa6b7c8", + "areaName": "New Area 3", + "metadata": { + "isDestination": true, + "leaf": false, + "isBoulder": false, + "lnglat": { + "type": "Point", + "coordinates": [ + -102.00, + 22.00 + ] + }, + "leftRightIndex": 3 + }, + "content": { + "description": "Description of New Area 3" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/public/bulk-import/example-uploads/update-climbs.json b/public/bulk-import/example-uploads/update-climbs.json new file mode 100644 index 000000000..735c6ac54 --- /dev/null +++ b/public/bulk-import/example-uploads/update-climbs.json @@ -0,0 +1,36 @@ +{ + "areas": [ + { + "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "climbs": [ + { + "name": "The Key Flake (updated)", + "grade": "5.11", + "fa": "Bobby Updated" + }, + { + "name": "Incredible Hand Crack (updated)", + "grade": "5.12", + "fa": "Someone Updated", + "type": { + "trad": false + }, + "content": { + "description": "I have updated this" + }, + "pitches": [ + { + "pitchNumber": 1, + "length": 110, + "boltsCount": 0 + }, + { + "pitchNumber": 2, + "description": "Updated: Easier climbing with good protection. Features a mix of crack sizes. Shorter than the first pitch but equally enjoyable." + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/pages/import.tsx b/src/pages/import.tsx index 4046558d9..05ab4b606 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -1,9 +1,9 @@ //'use client' import Ajv from "ajv"; -import React, { useState, useRef } from "react"; // Import useRef +import React, { useState, useRef } from "react" import { toast } from 'react-toastify'; import Layout from '../components/layout'; -import schema from "./schema.json"; +import schema from "../../public/bulk-import/bulk-import-schema.json" import { File, Lightbulb, Detective } from "@phosphor-icons/react"; const addFormats = require("ajv-formats"); const betterAjvErrors = require("better-ajv-errors").default @@ -160,9 +160,9 @@ const BulkImport = (): JSX.Element => {
  • Allows for adding or changing large amounts of data at once.
  • -
  • Utilizes this JSON schema to ensure correct data structure.
  • -
  • Example upload files and detailed instructions are available in this README.
  • -

    + +
  • Utilizes this JSON schema to ensure correct data structure.
  • +
  • Example upload files and detailed instructions are available in this README.
  • Note: OpenBeta’s route database is licensed as CC-BY-SA 4.0.
  • Need help? Find us on Discord.
diff --git a/yarn.lock b/yarn.lock index 06296275c..92ff42657 100644 --- a/yarn.lock +++ b/yarn.lock @@ -218,7 +218,7 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5": +"@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== @@ -1080,13 +1080,6 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": - version "7.23.9" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" - integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/template@^7.22.15", "@babel/template@^7.22.5", "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -1157,113 +1150,6 @@ dependencies: tslib "^2.0.0" -"@emotion/babel-plugin@^11.11.0": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" - integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== - dependencies: - "@babel/helper-module-imports" "^7.16.7" - "@babel/runtime" "^7.18.3" - "@emotion/hash" "^0.9.1" - "@emotion/memoize" "^0.8.1" - "@emotion/serialize" "^1.1.2" - babel-plugin-macros "^3.1.0" - convert-source-map "^1.5.0" - escape-string-regexp "^4.0.0" - find-root "^1.1.0" - source-map "^0.5.7" - stylis "4.2.0" - -"@emotion/cache@^11.11.0": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" - integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== - dependencies: - "@emotion/memoize" "^0.8.1" - "@emotion/sheet" "^1.2.2" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" - stylis "4.2.0" - -"@emotion/hash@^0.9.1": - version "0.9.1" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" - integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== - -"@emotion/is-prop-valid@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" - integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== - dependencies: - "@emotion/memoize" "^0.8.1" - -"@emotion/memoize@^0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" - integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== - -"@emotion/react@^11.11.3": - version "11.11.3" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.3.tgz#96b855dc40a2a55f52a72f518a41db4f69c31a25" - integrity sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA== - dependencies: - "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.11.0" - "@emotion/cache" "^11.11.0" - "@emotion/serialize" "^1.1.3" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" - hoist-non-react-statics "^3.3.1" - -"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.3.tgz#84b77bfcfe3b7bb47d326602f640ccfcacd5ffb0" - integrity sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA== - dependencies: - "@emotion/hash" "^0.9.1" - "@emotion/memoize" "^0.8.1" - "@emotion/unitless" "^0.8.1" - "@emotion/utils" "^1.2.1" - csstype "^3.0.2" - -"@emotion/sheet@^1.2.2": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" - integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== - -"@emotion/styled@^11.11.0": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.0.tgz#26b75e1b5a1b7a629d7c0a8b708fbf5a9cdce346" - integrity sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng== - dependencies: - "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.11.0" - "@emotion/is-prop-valid" "^1.2.1" - "@emotion/serialize" "^1.1.2" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.2.1" - -"@emotion/unitless@^0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" - integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== - -"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" - integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== - -"@emotion/utils@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" - integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== - -"@emotion/weak-memoize@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" - integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== - "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -1296,13 +1182,6 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.49.0.tgz#86f79756004a97fa4df866835093f1df3d03c333" integrity sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w== -"@floating-ui/core@^1.0.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" - integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== - dependencies: - "@floating-ui/utils" "^0.2.1" - "@floating-ui/core@^1.4.2": version "1.5.0" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.0.tgz#5c05c60d5ae2d05101c3021c1a2a350ddc027f8c" @@ -1318,14 +1197,6 @@ "@floating-ui/core" "^1.4.2" "@floating-ui/utils" "^0.1.3" -"@floating-ui/dom@^1.6.1": - version "1.6.3" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef" - integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw== - dependencies: - "@floating-ui/core" "^1.0.0" - "@floating-ui/utils" "^0.2.0" - "@floating-ui/react-dom@^2.0.0": version "2.0.2" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.2.tgz#fab244d64db08e6bed7be4b5fcce65315ef44d20" @@ -1333,23 +1204,11 @@ dependencies: "@floating-ui/dom" "^1.5.1" -"@floating-ui/react-dom@^2.0.8": - version "2.0.8" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" - integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== - dependencies: - "@floating-ui/dom" "^1.6.1" - "@floating-ui/utils@^0.1.3": version "0.1.4" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.4.tgz#19654d1026cc410975d46445180e70a5089b3e7d" integrity sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA== -"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" - integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== - "@google-cloud/paginator@^3.0.7": version "3.0.7" resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-3.0.7.tgz#fb6f8e24ec841f99defaebf62c75c2e744dd419b" @@ -1897,90 +1756,6 @@ "@babel/runtime" "^7.12.0" gl-matrix "^3.4.0" -"@mui/base@5.0.0-beta.36": - version "5.0.0-beta.36" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.36.tgz#29ca2de9d387f6d3943b6f18a84415c43e5f206c" - integrity sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ== - dependencies: - "@babel/runtime" "^7.23.9" - "@floating-ui/react-dom" "^2.0.8" - "@mui/types" "^7.2.13" - "@mui/utils" "^5.15.9" - "@popperjs/core" "^2.11.8" - clsx "^2.1.0" - prop-types "^15.8.1" - -"@mui/core-downloads-tracker@^5.15.10": - version "5.15.10" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.10.tgz#616bfb54e3860268d56ff59cd187d47044d954f3" - integrity sha512-qPv7B+LeMatYuzRjB3hlZUHqinHx/fX4YFBiaS19oC02A1e9JFuDKDvlyRQQ5oRSbJJt0QlaLTlr0IcauVcJRQ== - -"@mui/material@^5.15.10": - version "5.15.10" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.10.tgz#6533ba53edbd0790dbc5bb7e9e173f6069ffd7e6" - integrity sha512-YJJGHjwDOucecjDEV5l9ISTCo+l9YeWrho623UajzoHRYxuKUmwrGVYOW4PKwGvCx9SU9oklZnbbi2Clc5XZHw== - dependencies: - "@babel/runtime" "^7.23.9" - "@mui/base" "5.0.0-beta.36" - "@mui/core-downloads-tracker" "^5.15.10" - "@mui/system" "^5.15.9" - "@mui/types" "^7.2.13" - "@mui/utils" "^5.15.9" - "@types/react-transition-group" "^4.4.10" - clsx "^2.1.0" - csstype "^3.1.3" - prop-types "^15.8.1" - react-is "^18.2.0" - react-transition-group "^4.4.5" - -"@mui/private-theming@^5.15.9": - version "5.15.9" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.9.tgz#3ea3514ed2f6bf68541dbe9206665a82cd89cb01" - integrity sha512-/aMJlDOxOTAXyp4F2rIukW1O0anodAMCkv1DfBh/z9vaKHY3bd5fFf42wmP+0GRmwMinC5aWPpNfHXOED1fEtg== - dependencies: - "@babel/runtime" "^7.23.9" - "@mui/utils" "^5.15.9" - prop-types "^15.8.1" - -"@mui/styled-engine@^5.15.9": - version "5.15.9" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.9.tgz#444605039ec3fe456bdd5d5cb94330183be62b91" - integrity sha512-NRKtYkL5PZDH7dEmaLEIiipd3mxNnQSO+Yo8rFNBNptY8wzQnQ+VjayTq39qH7Sast5cwHKYFusUrQyD+SS4Og== - dependencies: - "@babel/runtime" "^7.23.9" - "@emotion/cache" "^11.11.0" - csstype "^3.1.3" - prop-types "^15.8.1" - -"@mui/system@^5.15.9": - version "5.15.9" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.9.tgz#8a34ac0ab133af2550cc7ab980a35174142fd265" - integrity sha512-SxkaaZ8jsnIJ77bBXttfG//LUf6nTfOcaOuIgItqfHv60ZCQy/Hu7moaob35kBb+guxVJnoSZ+7vQJrA/E7pKg== - dependencies: - "@babel/runtime" "^7.23.9" - "@mui/private-theming" "^5.15.9" - "@mui/styled-engine" "^5.15.9" - "@mui/types" "^7.2.13" - "@mui/utils" "^5.15.9" - clsx "^2.1.0" - csstype "^3.1.3" - prop-types "^15.8.1" - -"@mui/types@^7.2.13": - version "7.2.13" - resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.13.tgz#d1584912942f9dc042441ecc2d1452be39c666b8" - integrity sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g== - -"@mui/utils@^5.15.9": - version "5.15.9" - resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.9.tgz#2bdf925e274d87cbe90c14eb52d0835318205e86" - integrity sha512-yDYfr61bCYUz1QtwvpqYy/3687Z8/nS4zv7lv/ih/6ZFGMl1iolEvxRmR84v2lOYxlds+kq1IVYbXxDKh8Z9sg== - dependencies: - "@babel/runtime" "^7.23.9" - "@types/prop-types" "^15.7.11" - prop-types "^15.8.1" - react-is "^18.2.0" - "@next/env@13.5.6": version "13.5.6" resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.6.tgz#c1148e2e1aa166614f05161ee8f77ded467062bc" @@ -2067,11 +1842,6 @@ resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.0.14.tgz#3c8977cc81cc376d0c6afda46882eb5dc9b8b54d" integrity sha512-VaZ7/JEQ7dW+Up23l7t6lqJ3dPJupM03916Pat+ZOLX1vex9OeX9t8RZLJWt0oVrdc/GcrAyRD5FESDeP+M4tQ== -"@popperjs/core@^2.11.8": - version "2.11.8" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" - integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== - "@radix-ui/primitive@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" @@ -2981,11 +2751,6 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.6.tgz#bbf819813d6be21011b8f5801058498bec555572" integrity sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg== -"@types/prop-types@^15.7.11": - version "15.7.11" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" - integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== - "@types/qs@*": version "6.9.8" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.8.tgz#f2a7de3c107b89b441e071d5472e6b726b4adf45" @@ -3010,13 +2775,6 @@ dependencies: "@types/react" "*" -"@types/react-transition-group@^4.4.10": - version "4.4.10" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac" - integrity sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q== - dependencies: - "@types/react" "*" - "@types/react@*", "@types/react@^18.0.9": version "18.2.22" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.22.tgz#abe778a1c95a07fa70df40a52d7300a40b949ccb" @@ -3623,15 +3381,6 @@ babel-plugin-jest-hoist@^29.6.3: "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" -babel-plugin-macros@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" - integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== - dependencies: - "@babel/runtime" "^7.12.5" - cosmiconfig "^7.0.0" - resolve "^1.19.0" - babel-plugin-polyfill-corejs2@^0.4.5: version "0.4.5" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz#8097b4cb4af5b64a1d11332b6fb72ef5e64a054c" @@ -3955,11 +3704,6 @@ clsx@^1.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== -clsx@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" - integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -4046,7 +3790,7 @@ constant-case@^1.1.0: snake-case "^1.1.0" upper-case "^1.1.1" -convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== @@ -4080,7 +3824,7 @@ core-js-compat@^3.31.0: dependencies: browserslist "^4.21.10" -cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: +cosmiconfig@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== @@ -4206,11 +3950,6 @@ csstype@^3.0.2, csstype@^3.0.6: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== -csstype@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== - csvtojson@^2.0.10: version "2.0.10" resolved "https://registry.yarnpkg.com/csvtojson/-/csvtojson-2.0.10.tgz#11e7242cc630da54efce7958a45f443210357574" @@ -4504,14 +4243,6 @@ dom-helpers@^3.4.0: dependencies: "@babel/runtime" "^7.1.2" -dom-helpers@^5.0.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" - integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" - dom-serializer@^1.0.1: version "1.4.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" @@ -5188,11 +4919,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -find-root@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" - integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== - find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -5611,7 +5337,7 @@ hexoid@^1.0.0: resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== -hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -8239,16 +7965,6 @@ react-transition-group@2.9.0: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" -react-transition-group@^4.4.5: - version "4.4.5" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" - integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== - dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" - react-universal-interface@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b" @@ -8490,15 +8206,6 @@ resolve@^1.1.7, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.19.0: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - resolve@^2.0.0-next.4: version "2.0.0-next.4" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660" @@ -8771,11 +8478,6 @@ source-map@0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA== -source-map@^0.5.7: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -9009,11 +8711,6 @@ styled-jsx@5.1.1: dependencies: client-only "0.0.1" -stylis@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" - integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== - stylis@^4.0.6: version "4.3.0" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c" From 4a78d4a4a0d4da988ba056a6ec8a50b615a6ec79 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:28:41 +0100 Subject: [PATCH 10/21] adapt schema to match backend --- public/bulk-import/README.md | 6 +- public/bulk-import/bulk-import-schema.json | 201 +++++++-------------- src/pages/import.tsx | 4 +- 3 files changed, 66 insertions(+), 145 deletions(-) diff --git a/public/bulk-import/README.md b/public/bulk-import/README.md index 6fcb23d2f..80df110ed 100644 --- a/public/bulk-import/README.md +++ b/public/bulk-import/README.md @@ -55,15 +55,15 @@ Let's look at the example `add-areas-with-climbs.json` for an illustration: * it is only the leaf area node that determines its type, which is automatically determined server-side based on certain criteria: * an area with *climbs* => a *sector* (then, metadata property `isLeaf` is set to `TRUE`) * an area with *sectors* => a *crag* (not a leaf) - * if an area has only boulders as children => *boulder field* (metadata property `isBoulder` is set to `TRUE`) + * an area with *only boulder routes* as children => *boulder field* (metadata property `isBoulder` may be set to `TRUE`) # How to Update Data * On the use of `uuid`s for `Area`, `Climb`, or `Climb.Pitch`: * The schema requires EITHER *`name` (for areas: `areaName`) * OR `uuid` (for pitches: `id`) - * omitting an uuid => **creates** new entries - * providing an uuid => **updates** existing entries + * omitting a uuid => **creates** new entries + * providing a uuid => **updates** existing entries * Deleting climbs is not supported (join our Discord for help: https://discord.gg/a6vuuTQxS8) diff --git a/public/bulk-import/bulk-import-schema.json b/public/bulk-import/bulk-import-schema.json index c82260345..a18b7622c 100644 --- a/public/bulk-import/bulk-import-schema.json +++ b/public/bulk-import/bulk-import-schema.json @@ -27,6 +27,10 @@ "description": "The name that this area is commonly identified by within the climbing community.", "type": "string" }, + "countryCode": { + "description": "Only relevant when adding NEW countries. Database already contains most countries. Must be ISO 3166-1 Alpha-3 country code (e. g. `USA`).", + "type": "string" + }, "climbs": { "description": "The climbs that appear within this area. If this area is a leaf node, then these climbs can be understood as appearing physically on - rather than within - this area.", "type": "array", @@ -69,71 +73,26 @@ "US" ] }, - "metadata": { - "description": "Additional metadata about the area.", - "type": "object", - "properties": { - "leaf": { - "description": "Indicates if the area has no child areas.", - "type": "boolean" - }, - "isBoulder": { - "description": "Indicates if the area is primarily for bouldering.", - "type": "boolean" - }, - "lnglat": { - "description": "The longitude and latitude of the area.", - "type": "object", - "properties": { - "type": { - "description": "The type of the geo-coordinate, always 'Point'.", - "type": "string", - "enum": [ - "Point" - ] - }, - "coordinates": { - "description": "The longitude and latitude coordinates.", - "type": "array", - "items": { - "type": "number" - }, - "minItems": 2, - "maxItems": 2 - } - } - }, - "bbox": { - "description": "The bounding box of the area.", - "type": "array", - "items": { - "type": "number" - }, - "minItems": 4, - "maxItems": 4 - }, - "leftRightIndex": { - "description": "A numeric index used for sorting climbs from left to right (of a wall).", - "type": "number" - }, - "ext_id": { - "description": "An external identifier for the area.", - "type": "string" - }, - "additionalProperties": true - }, - "additionalProperties": true + "description": { + "description": "Everything you need to know about the area (which may be a sector, crag or region). Directions, approach, landmarks, need-to-knows, vicinity, etc.", + "type": "string" }, - "content": { - "description": "Content related to the area, such as descriptions.", - "type": "object", - "properties": { - "description": { - "description": "A textual description of the area.", - "type": "string" - } + "lng": { + "description": "Longitude coordinate of the area, using the WGS 84 reference system.", + "type": "number" + }, + "lat": { + "description": "Latitude coordinate of the area, using the WGS 84 reference system.", + "type": "number" + }, + "bbox": { + "description": "The bounding box of the area.", + "type": "array", + "items": { + "type": "number" }, - "additionalProperties": false + "minItems": 4, + "maxItems": 4 } }, "anyOf": [ @@ -163,6 +122,18 @@ "description": "The name that this climb is commonly identified by.", "type": "string" }, + "description": { + "description": "The description of this climb, this is the main text field for this climb. This contains beta, visual descriptors, and any other information useful to identifying and attempting the climb.", + "type": "string" + }, + "protection": { + "description": "What do climbers need to know about making a safe attempt of this climb? What gear do they need, what are the hazards, etc.", + "type": "string" + }, + "location": { + "description": "Information regarding approach and other location context for this climb. Could also include information about the situation of this specific climb.", + "type": "string" + }, "grade": { "description": "The difficulty grade of the climb. Must be coherent with gradeContext. I. e. gradeContext = 'US' requires denomination in yds/vscale (climbing/bouldering), so '5.11'/'V14', 'FR' would be french/font '9c+'/'9a', 'UIIA' would be uiaa/font '9+'/'9a'. (see https://github.com/OpenBeta/sandbag)", "type": "string" @@ -187,11 +158,8 @@ "US" ] }, - "fa": { - "description": "The first ascent information of the climb. Usually formatted as: name(s) (year).", - "type": "string" - }, - "type": { + + "disciplines": { "description": "What sort of climb is this? Climbs can combine these fields, which is why this is not an enumeration.", "type": "object", "properties": { @@ -226,6 +194,31 @@ }, "additionalProperties": false }, + + "boltsCount": { + "description": "The number of bolts (fixed anchors) on the climb.", + "type": "number" + }, + "length": { + "description": "Total length in meters if known (-1 otherwise)", + "type": "number" + }, + "lng": { + "description": "Longitude coordinate of the area, using the WGS 84 reference system.", + "type": "number" + }, + "lat": { + "description": "Latitude coordinate of the area, using the WGS 84 reference system.", + "type": "number" + }, + "leftRightIndex": { + "description": "A numeric index used for sorting climbs from left to right (of a wall).", + "type": "number" + }, + "fa": { + "description": "The first ascent information of the climb. Usually formatted as: name(s) (year).", + "type": "string" + }, "safety": { "description": "The safety rating of a climb based on US movie ratings. See https://github.com/OpenBeta/openbeta-graphql/blob/9c517329db079c922fe7f092a78b658cb295e158/src/graphql/schema/Climb.gql#L177.", "type": "string", @@ -237,14 +230,6 @@ "X" ] }, - "boltsCount": { - "description": "The number of bolts (fixed anchors) on the climb.", - "type": "number" - }, - "length": { - "description": "Total length in meters if known (-1 otherwise)", - "type": "number" - }, "pitches": { "description": "List of Pitch objects representing individual pitches of a multi-pitch climb", "type": "array", @@ -288,70 +273,6 @@ }, "additionalProperties": false } - }, - "metadata": { - "description": "Additional metadata about the climb.", - "type": "object", - "properties": { - "lnglat": { - "description": "The longitude and latitude of the climb.", - "type": "object", - "properties": { - "type": { - "description": "The type of the geo-coordinate, always 'Point'.", - "type": "string", - "enum": [ - "Point" - ] - }, - "coordinates": { - "description": "The longitude and latitude coordinates.", - "type": "array", - "items": { - "type": "number" - }, - "minItems": 2, - "maxItems": 2 - } - } - }, - "leftRightIndex": { - "description": "A numeric index used for sorting or positioning.", - "type": "number" - }, - "mp_id": { - "description": "An identifier used in Mountain Project's database.", - "type": "string" - }, - "mp_crag_id": { - "description": "The identifier of the crag in Mountain Project's database.", - "type": "string" - }, - "areaRef": { - "description": "A reference to the area containing the climb.", - "type": "object" - } - }, - "additionalProperties": false - }, - "content": { - "description": "Composable attributes for this climb, these are the bread and butter guidebook-like data that make up the bulk of the text beta for this climb.", - "type": "object", - "properties": { - "description": { - "description": "The description of this climb, this is the main text field for this climb. This contains beta, visual descriptors, and any other information useful to identifying and attempting the climb", - "type": "string" - }, - "protection": { - "description": "What do climbers need to know about making a safe attempt of this climb? What gear do they need, what are the hazards, etc.", - "type": "string" - }, - "location": { - "description": "Information regarding Approach and other location context for this climb. Could also include information about the situation of this specific climb.", - "type": "string" - } - }, - "additionalProperties": false } }, "anyOf": [ diff --git a/src/pages/import.tsx b/src/pages/import.tsx index 05ab4b606..53aafa8d0 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -161,8 +161,8 @@ const BulkImport = (): JSX.Element => {
  • Allows for adding or changing large amounts of data at once.
  • -
  • Utilizes this JSON schema to ensure correct data structure.
  • -
  • Example upload files and detailed instructions are available in this README.
  • +
  • Used a JSON schema to validate your upload's data structure before uploading
  • +
  • See this README.md for example upload files, schema, and detailed instructions.
  • Note: OpenBeta’s route database is licensed as CC-BY-SA 4.0.
  • Need help? Find us on Discord.
From 46edc9216ae66ee84a28a57e17fe8ac809e9aed3 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Sun, 18 Feb 2024 11:01:45 +0100 Subject: [PATCH 11/21] fix example uploads to match new schema --- public/bulk-import/bulk-import-schema.json | 2 +- .../add-areas-with-climbs.json | 101 +++++------------- .../example-uploads/update-areas.json | 95 ++++------------ .../example-uploads/update-climbs.json | 6 +- 4 files changed, 52 insertions(+), 152 deletions(-) diff --git a/public/bulk-import/bulk-import-schema.json b/public/bulk-import/bulk-import-schema.json index a18b7622c..a83eb2bd3 100644 --- a/public/bulk-import/bulk-import-schema.json +++ b/public/bulk-import/bulk-import-schema.json @@ -28,7 +28,7 @@ "type": "string" }, "countryCode": { - "description": "Only relevant when adding NEW countries. Database already contains most countries. Must be ISO 3166-1 Alpha-3 country code (e. g. `USA`).", + "description": "Relevant for adding areas directly below a country (E. g. USA -> Utah). ISO 3166-1 Alpha-2 country code (e. g. `us`). Children areas inherit the countryCode", "type": "string" }, "climbs": { diff --git a/public/bulk-import/example-uploads/add-areas-with-climbs.json b/public/bulk-import/example-uploads/add-areas-with-climbs.json index d24742afa..bf7fec947 100644 --- a/public/bulk-import/example-uploads/add-areas-with-climbs.json +++ b/public/bulk-import/example-uploads/add-areas-with-climbs.json @@ -2,70 +2,59 @@ "areas": [ { "areaName": "Utah", + "countryCode": "us", "children": [ { "areaName": "Southeast Utah", "children": [ { "areaName": "Indian Creek", + "description": "Indian Creek is a crack climbing mecca in the southeastern region of Utah, USA. Located within the [Bears Ears National Monument](https://en.wikipedia.org/wiki/Bears_Ears_National_Monument).", + "lng": -109.5724044642857, + "lat": 38.069429035714286, "children": [ { "areaName": "Supercrack Buttress", + "gradeContext": "US", + "description": "", + "lng": -109.54552, + "lat": 38.03635, + "bbox": [ + -109.54609091005857, + 38.03590033981814, + -109.54494908994141, + 38.03679966018186 + ], "climbs": [ { "name": "The Key Flake", "grade": "5.10", "fa": "unknown", - "type": { + "disciplines": { "trad": true }, "safety": "UNSPECIFIED", - "metadata": { - "lnglat": { - "type": "Point", - "coordinates": [ - -109.54552, - 38.03635 - ] - }, - "leftRightIndex": 1 - }, - "content": { - "description": "Cool off-width that requires off-width and face skills.", - "protection": "Anchors hidden up top. Need 80m to make it all the way down.", - "location": "Opposite keyhole flake. Obvious right leaning offwidth that starts atop 20 ft boulder." - } + "lng": -109.54552, + "lat": 38.03635, + "leftRightIndex": 1, + "description": "Cool off-width that requires off-width and face skills.", + "protection": "Anchors hidden up top. Need 80m to make it all the way down.", + "location": "Opposite keyhole flake. Obvious right leaning offwidth that starts atop 20 ft boulder." }, { "name": "Incredible Hand Crack", "grade": "5.10", "fa": "Rich Perch, John Bragg, Doug Snively, and Anne Tarver, 1978", - "type": { + "disciplines": { "trad": true }, - "safety": "UNSPECIFIED", - "metadata": { - "lnglat": { - "type": "Point", - "coordinates": [ - -109.54552, - 38.03635 - ] - }, - "leftRightIndex": 2 - }, - "content": { - "description": "Route starts at the top of the trail from the parking lot to Supercrack Buttress.", - "protection": "Cams from 2-2.5\". Heavy on 2.5\" (#2 Camalot)", - "location": "" - }, + "leftRightIndex": 2, + "description": "Route starts at the top of the trail from the parking lot to Supercrack Buttress.", + "protection": "Cams from 2-2.5\". Heavy on 2.5\" (#2 Camalot)", "pitches": [ { "pitchNumber": 1, "grade": "5.10", - "disciplines": { - "trad": true - }, "length": 100, "boltsCount": 0, "description": "A classic hand crack that widens slightly towards the top. Requires a range of cam sizes. Sustained and excellent quality." @@ -73,52 +62,14 @@ { "pitchNumber": 2, "grade": "5.9", - "disciplines": { - "trad": true - }, "length": 30, - "boltsCount": 0, "description": "Easier climbing with good protection. Features a mix of crack sizes. Shorter than the first pitch but equally enjoyable." } ] } - ], - "gradeContext": "US", - "metadata": { - "isBoulder": false, - "isDestination": false, - "leaf": true, - "lnglat": { - "type": "Point", - "coordinates": [ - -109.54552, - 38.03635 - ] - }, - "bbox": [ - -109.54609091005857, - 38.03590033981814, - -109.54494908994141, - 38.03679966018186 - ] - }, - "content": { - "description": "" - } - } - ], - "metadata": { - "lnglat": { - "type": "Point", - "coordinates": [ - -109.5724044642857, - 38.069429035714286 ] } - }, - "content": { - "description": "Indian Creek is a crack climbing mecca in the southeastern region of Utah, USA. Located within the [Bears Ears National Monument](https://en.wikipedia.org/wiki/Bears_Ears_National_Monument)." - } + ] } ] } diff --git a/public/bulk-import/example-uploads/update-areas.json b/public/bulk-import/example-uploads/update-areas.json index 1244817a8..89d188688 100644 --- a/public/bulk-import/example-uploads/update-areas.json +++ b/public/bulk-import/example-uploads/update-areas.json @@ -2,86 +2,37 @@ "areas": [ { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", - "name": "Area with updated name, coordinates, description", - "metadata": { - "lnglat": { - "type": "Point", - "coordinates": [ - -99.99, - 11.11 - ] - } - }, - "content": { - "description": "Some new description." - } + "areaName": "Area with updated name, coordinates, description", + "lng": -99.99, + "lat": 11.11, + "description": "Some new description." }, { "id": "a3f159cb-2f10-4e3a-8bce-6689a70b76b8", - "name": "Existing area, where new children areas are added", - "children": - [ - { - "id": "b1a6492f-8d58-4d5b-8b1a-51b75f51e990", - "areaName": "New Area 1", - "metadata": { - "isDestination": true, - "leaf": false, - "isBoulder": false, - "lnglat": { - "type": "Point", - "coordinates": [ - -100.00, - 20.00 - ] - }, - "leftRightIndex": 1 - }, - "content": { + "areaName": "Existing area, where new children areas are added", + "children": [ + { + "id": "b1a6492f-8d58-4d5b-8b1a-51b75f51e990", + "areaName": "New Area 1", + "lng": -100.00, + "lat": 20.00, "description": "Description of New Area 1" - } - }, - { - "id": "c2d3e1f4-41e4-4dce-9bce-5b6a6395d570", - "areaName": "New Area 2", - "metadata": { - "isDestination": false, - "leaf": true, - "isBoulder": true, - "lnglat": { - "type": "Point", - "coordinates": [ - -101.00, - 21.00 - ] - }, - "leftRightIndex": 2 }, - "content": { + { + "id": "c2d3e1f4-41e4-4dce-9bce-5b6a6395d570", + "areaName": "New Area 2", + "lng": -101.00, + "lat": 21.00, "description": "Description of New Area 2" - } - }, - { - "id": "d4e5f6a7-8b9c-4d0e-9abc-7d8e9fa6b7c8", - "areaName": "New Area 3", - "metadata": { - "isDestination": true, - "leaf": false, - "isBoulder": false, - "lnglat": { - "type": "Point", - "coordinates": [ - -102.00, - 22.00 - ] - }, - "leftRightIndex": 3 }, - "content": { + { + "id": "d4e5f6a7-8b9c-4d0e-9abc-7d8e9fa6b7c8", + "areaName": "New Area 3", + "lng": -102.00, + "lat": 22.00, "description": "Description of New Area 3" } - } - ] + ] } ] -} \ No newline at end of file +} diff --git a/public/bulk-import/example-uploads/update-climbs.json b/public/bulk-import/example-uploads/update-climbs.json index 735c6ac54..37ef22bee 100644 --- a/public/bulk-import/example-uploads/update-climbs.json +++ b/public/bulk-import/example-uploads/update-climbs.json @@ -12,12 +12,10 @@ "name": "Incredible Hand Crack (updated)", "grade": "5.12", "fa": "Someone Updated", - "type": { + "disciplines": { "trad": false }, - "content": { - "description": "I have updated this" - }, + "description": "I have updated this", "pitches": [ { "pitchNumber": 1, From 34f40de0712b4a43ba4ac972f92b573352113c5e Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Sun, 18 Feb 2024 11:05:30 +0100 Subject: [PATCH 12/21] simplify README --- public/bulk-import/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/bulk-import/README.md b/public/bulk-import/README.md index 80df110ed..f9ec6fd47 100644 --- a/public/bulk-import/README.md +++ b/public/bulk-import/README.md @@ -51,11 +51,11 @@ Let's look at the example `add-areas-with-climbs.json` for an illustration: > - "Pitch 2" (Pitch, Type: `Climb.Pitch`) ### A note on `Area`s -* an `Area` may be a country, state, region, crag or sector (and may be infinetely nested) -* it is only the leaf area node that determines its type, which is automatically determined server-side based on certain criteria: - * an area with *climbs* => a *sector* (then, metadata property `isLeaf` is set to `TRUE`) - * an area with *sectors* => a *crag* (not a leaf) - * an area with *only boulder routes* as children => *boulder field* (metadata property `isBoulder` may be set to `TRUE`) +* An `Area` may be a country, state, region, crag or sector (and may be infinetely nested) +* It is only the leaf area node that determines its type, which is (moslty) automatically determined server-side based on certain criteria: + * an area with *climbs* => a *sector* + * an area with *sectors* => a *crag* + * an area with *only boulder routes* as children => *boulder field* # How to Update Data * On the use of `uuid`s for `Area`, `Climb`, or `Climb.Pitch`: From 4f6058d36060344ce4a67c201f9304dbee913135 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Sun, 18 Feb 2024 11:34:42 +0100 Subject: [PATCH 13/21] fix: replace id with uuid in examples and schema --- public/bulk-import/bulk-import-schema.json | 6 +++--- public/bulk-import/example-uploads/update-areas.json | 10 +++++----- public/bulk-import/example-uploads/update-climbs.json | 2 +- src/pages/import.tsx | 3 ++- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/public/bulk-import/bulk-import-schema.json b/public/bulk-import/bulk-import-schema.json index a83eb2bd3..72611858b 100644 --- a/public/bulk-import/bulk-import-schema.json +++ b/public/bulk-import/bulk-import-schema.json @@ -18,7 +18,7 @@ "description": "Represents a climbing area with its properties and nested areas or climbs.", "type": "object", "properties": { - "id": { + "uuid": { "description": "A unique identifier for the area.", "type": "string", "format": "uuid" @@ -103,7 +103,7 @@ }, { "required": [ - "id" + "uuid" ] } ], @@ -113,7 +113,7 @@ "description": "Represents a climbing route with its properties.", "type": "object", "properties": { - "id": { + "uuid": { "description": "A unique identifier for the climb.", "type": "string", "format": "uuid" diff --git a/public/bulk-import/example-uploads/update-areas.json b/public/bulk-import/example-uploads/update-areas.json index 89d188688..01e8861f5 100644 --- a/public/bulk-import/example-uploads/update-areas.json +++ b/public/bulk-import/example-uploads/update-areas.json @@ -1,32 +1,32 @@ { "areas": [ { - "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "areaName": "Area with updated name, coordinates, description", "lng": -99.99, "lat": 11.11, "description": "Some new description." }, { - "id": "a3f159cb-2f10-4e3a-8bce-6689a70b76b8", + "uuid": "a3f159cb-2f10-4e3a-8bce-6689a70b76b8", "areaName": "Existing area, where new children areas are added", "children": [ { - "id": "b1a6492f-8d58-4d5b-8b1a-51b75f51e990", + "uuid": "b1a6492f-8d58-4d5b-8b1a-51b75f51e990", "areaName": "New Area 1", "lng": -100.00, "lat": 20.00, "description": "Description of New Area 1" }, { - "id": "c2d3e1f4-41e4-4dce-9bce-5b6a6395d570", + "uuid": "c2d3e1f4-41e4-4dce-9bce-5b6a6395d570", "areaName": "New Area 2", "lng": -101.00, "lat": 21.00, "description": "Description of New Area 2" }, { - "id": "d4e5f6a7-8b9c-4d0e-9abc-7d8e9fa6b7c8", + "uuid": "d4e5f6a7-8b9c-4d0e-9abc-7d8e9fa6b7c8", "areaName": "New Area 3", "lng": -102.00, "lat": 22.00, diff --git a/public/bulk-import/example-uploads/update-climbs.json b/public/bulk-import/example-uploads/update-climbs.json index 37ef22bee..89ef7d418 100644 --- a/public/bulk-import/example-uploads/update-climbs.json +++ b/public/bulk-import/example-uploads/update-climbs.json @@ -1,7 +1,7 @@ { "areas": [ { - "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "climbs": [ { "name": "The Key Flake (updated)", diff --git a/src/pages/import.tsx b/src/pages/import.tsx index 53aafa8d0..e7ffb0339 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -4,9 +4,10 @@ import React, { useState, useRef } from "react" import { toast } from 'react-toastify'; import Layout from '../components/layout'; import schema from "../../public/bulk-import/bulk-import-schema.json" -import { File, Lightbulb, Detective } from "@phosphor-icons/react"; +import { Lightbulb } from "@phosphor-icons/react"; const addFormats = require("ajv-formats"); const betterAjvErrors = require("better-ajv-errors").default +import { jsonToGraphQLQuery } from 'json-to-graphql-query' function FileUploadAndValidationClientComponent() { From 4524961e10de6fe8f5b28385bfe2e7ea42cd1841 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Sun, 18 Feb 2024 13:37:01 +0100 Subject: [PATCH 14/21] add json to gql skeleton (inital draft) --- src/pages/import.tsx | 108 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 91 insertions(+), 17 deletions(-) diff --git a/src/pages/import.tsx b/src/pages/import.tsx index e7ffb0339..ac311cde0 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -7,7 +7,7 @@ import schema from "../../public/bulk-import/bulk-import-schema.json" import { Lightbulb } from "@phosphor-icons/react"; const addFormats = require("ajv-formats"); const betterAjvErrors = require("better-ajv-errors").default -import { jsonToGraphQLQuery } from 'json-to-graphql-query' +import { jsonToGraphQLQuery, VariableType } from 'json-to-graphql-query' function FileUploadAndValidationClientComponent() { @@ -89,21 +89,21 @@ function FileUploadAndValidationClientComponent() {

File Upload

- +
- { - handleFileSelect(event) - if (event.target.files && event.target.files[0]) { - setFileName(event.target.files[0].name) - } - }} - ref={fileInputRef} /> + { + handleFileSelect(event) + if (event.target.files && event.target.files[0]) { + setFileName(event.target.files[0].name) + } + }} + ref={fileInputRef} /> -
-

{fileName ? `Selected file: ${fileName}` : "No file selected."}

+
+

{fileName ? `Selected file: ${fileName}` : "No file selected."}

@@ -149,6 +149,80 @@ function FileUploadAndValidationClientComponent() { } +function createMutationAndUploadToDB(parsedJsonData: string) { + + const bulkImportMutation = { + mutation: { + __name: "BulkImport", + __variables: { + input: "BulkImportInputType!" + }, + bulkImport: { + __args: { + input: parsedJsonData + }, + addedAreas: { + uuid: true, + areaName: true, + description: true, + countryCode: true, + gradeContext: true, + leftRightIndex: true, + lng: true, + lat: true, + bbox: true, + children: { + uuid: true, + areaName: true, + description: true, + countryCode: true, + gradeContext: true, + leftRightIndex: true, + lng: true, + lat: true, + bbox: true, + // TODO: recursion is handled appropriately + }, + climbs: { + uuid: true, + name: true, + grade: true, + disciplines: true, + safety: true, + lng: true, + lat: true, + leftRightIndex: true, + description: true, + location: true, + protection: true, + fa: true, + length: true, + boltsCount: true, + experimentalAuthor: { + displayName: true, + url: true + }, + pitches: { + id: true, + pitchNumber: true, + grade: true, + disciplines: true, + description: true, + length: true, + boltsCount: true + } + } + } + } + } + }; + + const graphqlMutation = jsonToGraphQLQuery(bulkImportMutation, { pretty: true }); + + console.log(graphqlMutation +} + + const BulkImport = (): JSX.Element => { return ( @@ -161,7 +235,7 @@ const BulkImport = (): JSX.Element => {
  • Allows for adding or changing large amounts of data at once.
  • - +
  • Used a JSON schema to validate your upload's data structure before uploading
  • See this README.md for example upload files, schema, and detailed instructions.
  • Note: OpenBeta’s route database is licensed as CC-BY-SA 4.0.
  • From 51d97a0181955311926f7d2bcb8339e800cd6b72 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:38:58 +0100 Subject: [PATCH 15/21] add (still broken) db call functionality --- package.json | 1 + .../example-uploads/test_update-climbs.json | 33 ++++ src/pages/import.tsx | 164 ++++++++++-------- yarn.lock | 5 + 4 files changed, 126 insertions(+), 77 deletions(-) create mode 100644 public/bulk-import/example-uploads/test_update-climbs.json diff --git a/package.json b/package.json index 4c5a5dd15..4e3e4f638 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "graphql": "^16.2.0", "i18n-iso-countries": "^7.5.0", "immer": "^10.0.2", + "json-to-graphql-query": "^2.2.5", "lexical": "^0.7.5", "mapbox-gl": "^2.7.0", "nanoid": "^4.0.0", diff --git a/public/bulk-import/example-uploads/test_update-climbs.json b/public/bulk-import/example-uploads/test_update-climbs.json new file mode 100644 index 000000000..a927e6546 --- /dev/null +++ b/public/bulk-import/example-uploads/test_update-climbs.json @@ -0,0 +1,33 @@ +{ + "areas": [ + { + "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "climbs": [ + { + "name": "The Key Flake (test)", + "grade": "5.11", + "fa": "Bobby Updated" + }, + { + "name": "Incredible Hand Crack (test)", + "grade": "5.12", + "disciplines": { + "trad": false + }, + "description": "I have updated this (test)", + "pitches": [ + { + "pitchNumber": 1, + "length": 110, + "boltsCount": 0 + }, + { + "pitchNumber": 2, + "description": " (test) Updated: Easier climbing with good protection. Features a mix of crack sizes. Shorter than the first pitch but equally enjoyable." + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/pages/import.tsx b/src/pages/import.tsx index ac311cde0..324d01d45 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -1,13 +1,15 @@ //'use client' import Ajv from "ajv"; -import React, { useState, useRef } from "react" +import React, { useState, useRef, useCallback } from "react" import { toast } from 'react-toastify'; import Layout from '../components/layout'; import schema from "../../public/bulk-import/bulk-import-schema.json" import { Lightbulb } from "@phosphor-icons/react"; const addFormats = require("ajv-formats"); const betterAjvErrors = require("better-ajv-errors").default -import { jsonToGraphQLQuery, VariableType } from 'json-to-graphql-query' +import { jsonToGraphQLQuery } from 'json-to-graphql-query' +import { useMutation, gql } from '@apollo/client' +import { graphqlClient } from '../js/graphql/Client' function FileUploadAndValidationClientComponent() { @@ -15,15 +17,16 @@ function FileUploadAndValidationClientComponent() { const [fileName, setFileName] = useState(null); const [validationErrors, setValidationErrors] = useState([]) const [isValidationSuccessful, setIsValidationSuccessful] = useState(false); + const [parsedJSON, setParsedJSON] = useState(null); - const handleValidationErrors = (rawJSON: object) => { + const handleValidationErrors = (parsedJSON: object) => { try { const ajv = new Ajv({ allErrors: true, verbose: true }); addFormats(ajv); const compiledSchema = ajv.compile(schema); - const validate = compiledSchema(rawJSON); + const validate = compiledSchema(parsedJSON); if (!validate) { - const betterErrors = betterAjvErrors(schema, rawJSON, compiledSchema.errors, { format: 'js' }) + const betterErrors = betterAjvErrors(schema, parsedJSON, compiledSchema.errors, { format: 'js' }) console.log(betterErrors) setValidationErrors(betterErrors); toast.error( @@ -37,6 +40,7 @@ function FileUploadAndValidationClientComponent() { ); setValidationErrors([]) setIsValidationSuccessful(true) + createMutationAndUploadToDB(parsedJSON) } } catch (error) { console.error("Error during validation:", error); @@ -59,7 +63,7 @@ function FileUploadAndValidationClientComponent() { setValidationErrors([]) setIsValidationSuccessful(false) - const file = event.target.files ? event.target.files[0] : null; + const file = event.target.files ? event.target.files[0] : null if (file) { const reader = new FileReader(); @@ -67,9 +71,10 @@ function FileUploadAndValidationClientComponent() { reader.onload = (event) => { const file = event.target?.result; if (typeof file === "string") { - const isValidJSON = safelyParseJSON(file); - if (isValidJSON) { - handleValidationErrors(isValidJSON); + const parsedJSON = safelyParseJSON(file); + if (parsedJSON) { + setParsedJSON(parsedJSON); // Update the state + handleValidationErrors(parsedJSON); } else { toast.error("Error: Not a valid JSON file"); } @@ -136,90 +141,95 @@ function FileUploadAndValidationClientComponent() { )} -

    -
    -
    - -
    -
    + + + ); } +interface UploadToDBProps { + parsedJsonData: object | null // TODO: remove nullableness? + isValidationSuccessful: boolean +} + +// Assuming `graphqlClient` is an instance of ApolloClient +const UploadToDBComponent: React.FC = ({ parsedJsonData, isValidationSuccessful }) => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const handleUpload = useCallback(async () => { + if (!isValidationSuccessful) { + toast.error("JSON schema validation had not been successful. Fix the errors and try again."); + return; + } + + setLoading(true); + setError(null); + + const bulkImportMutation = { + mutation: { + bulkImportAreas: { + __args: { + input: parsedJsonData, + }, + }, + }, + }; + + const generatedGraphqlMutation = jsonToGraphQLQuery(bulkImportMutation, { pretty: true }); + + try { + const { data } = await graphqlClient.mutate({ + mutation: gql`${generatedGraphqlMutation}`, + }); + console.log("Sending mutation to the database..."); + if (data) { + console.log("Data successfully uploaded to the database:", data); + toast.success("Data successfully uploaded to the database."); + } + } catch (e) { + const error = e as Error; + console.error("Error uploading to DB:", error); + setError(error); + toast.error("Error uploading data to the database. See console log for more details."); + } finally { + setLoading(false); + } + }, [parsedJsonData, isValidationSuccessful]); + + if (loading) return

    Uploading...

    ; + if (error) return

    An error occurred while uploading to the database. See console log for more details.

    ; + + return ( + + ); +}; + -function createMutationAndUploadToDB(parsedJsonData: string) { +function createMutationAndUploadToDB(parsedJsonData: object) { const bulkImportMutation = { mutation: { - __name: "BulkImport", - __variables: { - input: "BulkImportInputType!" - }, - bulkImport: { + bulkImportAreas: { __args: { input: parsedJsonData - }, - addedAreas: { - uuid: true, - areaName: true, - description: true, - countryCode: true, - gradeContext: true, - leftRightIndex: true, - lng: true, - lat: true, - bbox: true, - children: { - uuid: true, - areaName: true, - description: true, - countryCode: true, - gradeContext: true, - leftRightIndex: true, - lng: true, - lat: true, - bbox: true, - // TODO: recursion is handled appropriately - }, - climbs: { - uuid: true, - name: true, - grade: true, - disciplines: true, - safety: true, - lng: true, - lat: true, - leftRightIndex: true, - description: true, - location: true, - protection: true, - fa: true, - length: true, - boltsCount: true, - experimentalAuthor: { - displayName: true, - url: true - }, - pitches: { - id: true, - pitchNumber: true, - grade: true, - disciplines: true, - description: true, - length: true, - boltsCount: true - } - } } } } }; - const graphqlMutation = jsonToGraphQLQuery(bulkImportMutation, { pretty: true }); + const generatedGraphqlMutation = jsonToGraphQLQuery(bulkImportMutation, { pretty: true }); + + console.log(generatedGraphqlMutation) - console.log(graphqlMutation + return generatedGraphqlMutation } diff --git a/yarn.lock b/yarn.lock index 92ff42657..1ceb45252 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6356,6 +6356,11 @@ json-stringify-pretty-compact@^3.0.0: resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz#f71ef9d82ef16483a407869556588e91b681d9ab" integrity sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA== +json-to-graphql-query@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/json-to-graphql-query/-/json-to-graphql-query-2.2.5.tgz#56b072a693b50fd4dc981367b60d52e3dc78f426" + integrity sha512-5Nom9inkIMrtY992LMBBG1Zaekrc10JaRhyZgprwHBVMDtRgllTvzl0oBbg13wJsVZoSoFNNMaeIVQs0P04vsA== + json5@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" From 560e6033209b5efed2425da9bc9d042639a47a83 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:39:31 +0100 Subject: [PATCH 16/21] snapshot --- src/pages/import.tsx | 118 ++++++++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 46 deletions(-) diff --git a/src/pages/import.tsx b/src/pages/import.tsx index 324d01d45..e499fefbf 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -1,6 +1,6 @@ //'use client' import Ajv from "ajv"; -import React, { useState, useRef, useCallback } from "react" +import React, { useState, useRef, useCallback, useEffect } from "react" import { toast } from 'react-toastify'; import Layout from '../components/layout'; import schema from "../../public/bulk-import/bulk-import-schema.json" @@ -14,10 +14,11 @@ import { graphqlClient } from '../js/graphql/Client' function FileUploadAndValidationClientComponent() { const fileInputRef = useRef(null) - const [fileName, setFileName] = useState(null); + const [fileName, setFileName] = useState(null) const [validationErrors, setValidationErrors] = useState([]) - const [isValidationSuccessful, setIsValidationSuccessful] = useState(false); - const [parsedJSON, setParsedJSON] = useState(null); + const [isValidationSuccessful, setIsValidationSuccessful] = useState(false) + const [resetDbUploadErrorTrigger, setResetDbUploadErrorTrigger] = useState(false) + const [parsedJSON, setParsedJSON] = useState(null) const handleValidationErrors = (parsedJSON: object) => { try { @@ -40,7 +41,6 @@ function FileUploadAndValidationClientComponent() { ); setValidationErrors([]) setIsValidationSuccessful(true) - createMutationAndUploadToDB(parsedJSON) } } catch (error) { console.error("Error during validation:", error); @@ -62,6 +62,7 @@ function FileUploadAndValidationClientComponent() { toast.dismiss() setValidationErrors([]) setIsValidationSuccessful(false) + setResetDbUploadErrorTrigger(true) const file = event.target.files ? event.target.files[0] : null @@ -84,7 +85,6 @@ function FileUploadAndValidationClientComponent() { } }; - // Function to trigger file input click const handleButtonClick = () => { fileInputRef.current?.click(); }; @@ -142,7 +142,7 @@ function FileUploadAndValidationClientComponent() { )} - + ); @@ -151,12 +151,17 @@ function FileUploadAndValidationClientComponent() { interface UploadToDBProps { parsedJsonData: object | null // TODO: remove nullableness? isValidationSuccessful: boolean + resetDbUploadErrorTrigger: boolean } // Assuming `graphqlClient` is an instance of ApolloClient -const UploadToDBComponent: React.FC = ({ parsedJsonData, isValidationSuccessful }) => { +const UploadToDBComponent: React.FC = ({ parsedJsonData, isValidationSuccessful, resetDbUploadErrorTrigger }) => { const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); + const [dbUploadError, setDbUploadError] = useState(null); + + useEffect(() => { + setDbUploadError(null); + }, [resetDbUploadErrorTrigger]); const handleUpload = useCallback(async () => { if (!isValidationSuccessful) { @@ -164,8 +169,8 @@ const UploadToDBComponent: React.FC = ({ parsedJsonData, isVali return; } - setLoading(true); - setError(null); + setLoading(true) + setDbUploadError(null) const bulkImportMutation = { mutation: { @@ -173,11 +178,51 @@ const UploadToDBComponent: React.FC = ({ parsedJsonData, isVali __args: { input: parsedJsonData, }, - }, - }, + addedAreas: { + id: true, + uuid: true, + area_name: true, + areaName: true, + climbs: { + id: true, + uuid: true, + name: true, + fa: true, + length: true, + boltsCount: true, + gradeContext: true, + pitches: { + id: true, + parentId: true, + pitchNumber: true, + length: true, + boltsCount: true, + description: true, + } + } + }, + updatedAreas: { + id: true, + uuid: true, + area_name: true, + areaName: true, + climbs: { + id: true, + uuid: true, + name: true, + fa: true, + length: true, + boltsCount: true, + gradeContext: true, + } + } + } + } }; - const generatedGraphqlMutation = jsonToGraphQLQuery(bulkImportMutation, { pretty: true }); + const generatedGraphqlMutation = jsonToGraphQLQuery(bulkImportMutation, { pretty: true }) + + console.log('generatedGraphqlMutation:', generatedGraphqlMutation) try { const { data } = await graphqlClient.mutate({ @@ -191,45 +236,26 @@ const UploadToDBComponent: React.FC = ({ parsedJsonData, isVali } catch (e) { const error = e as Error; console.error("Error uploading to DB:", error); - setError(error); + setDbUploadError(error); toast.error("Error uploading data to the database. See console log for more details."); } finally { setLoading(false); } }, [parsedJsonData, isValidationSuccessful]); - if (loading) return

    Uploading...

    ; - if (error) return

    An error occurred while uploading to the database. See console log for more details.

    ; - return ( - - ); -}; - - -function createMutationAndUploadToDB(parsedJsonData: object) { - - const bulkImportMutation = { - mutation: { - bulkImportAreas: { - __args: { - input: parsedJsonData - } - } - } - }; - - const generatedGraphqlMutation = jsonToGraphQLQuery(bulkImportMutation, { pretty: true }); - - console.log(generatedGraphqlMutation) - - return generatedGraphqlMutation + <> + {loading &&

    Uploading...

    } + {dbUploadError &&

    An error occurred while uploading to the database. See console log for more details.

    } + + + ) } From 8f0870a524b1af7bc66a3a058d234fe0c1e1eaf9 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:17:25 +0100 Subject: [PATCH 17/21] enhance upload button logic --- .../add-areas-with-climbs.json | 1 - src/pages/import.tsx | 131 +++++++++--------- 2 files changed, 69 insertions(+), 63 deletions(-) diff --git a/public/bulk-import/example-uploads/add-areas-with-climbs.json b/public/bulk-import/example-uploads/add-areas-with-climbs.json index bf7fec947..75d01be97 100644 --- a/public/bulk-import/example-uploads/add-areas-with-climbs.json +++ b/public/bulk-import/example-uploads/add-areas-with-climbs.json @@ -33,7 +33,6 @@ "disciplines": { "trad": true }, - "safety": "UNSPECIFIED", "lng": -109.54552, "lat": 38.03635, "leftRightIndex": 1, diff --git a/src/pages/import.tsx b/src/pages/import.tsx index e499fefbf..8f02ba726 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -17,7 +17,7 @@ function FileUploadAndValidationClientComponent() { const [fileName, setFileName] = useState(null) const [validationErrors, setValidationErrors] = useState([]) const [isValidationSuccessful, setIsValidationSuccessful] = useState(false) - const [resetDbUploadErrorTrigger, setResetDbUploadErrorTrigger] = useState(false) + const [encounteredDbUploadError, setEncounteredDbUploadError] = useState(false) const [parsedJSON, setParsedJSON] = useState(null) const handleValidationErrors = (parsedJSON: object) => { @@ -62,7 +62,7 @@ function FileUploadAndValidationClientComponent() { toast.dismiss() setValidationErrors([]) setIsValidationSuccessful(false) - setResetDbUploadErrorTrigger(true) + setEncounteredDbUploadError(false) const file = event.target.files ? event.target.files[0] : null @@ -142,89 +142,96 @@ function FileUploadAndValidationClientComponent() { )} - + ); } interface UploadToDBProps { - parsedJsonData: object | null // TODO: remove nullableness? + parsedJsonData: object | null isValidationSuccessful: boolean - resetDbUploadErrorTrigger: boolean + encounteredDbUploadError: boolean; + setEncounteredDbUploadError: (error: boolean) => void; } -// Assuming `graphqlClient` is an instance of ApolloClient -const UploadToDBComponent: React.FC = ({ parsedJsonData, isValidationSuccessful, resetDbUploadErrorTrigger }) => { +const UploadToDBComponent: React.FC = ({ + parsedJsonData, + isValidationSuccessful, + encounteredDbUploadError, + setEncounteredDbUploadError, +}) => { const [loading, setLoading] = useState(false); - const [dbUploadError, setDbUploadError] = useState(null); useEffect(() => { - setDbUploadError(null); - }, [resetDbUploadErrorTrigger]); + if (encounteredDbUploadError) { + setEncounteredDbUploadError(false); + } + }, [parsedJsonData, setEncounteredDbUploadError]); const handleUpload = useCallback(async () => { - if (!isValidationSuccessful) { - toast.error("JSON schema validation had not been successful. Fix the errors and try again."); + if (!isValidationSuccessful || !parsedJsonData) { + toast.error("JSON schema validation unsuccessful. Fix the errors and try again."); return; } - setLoading(true) - setDbUploadError(null) - - const bulkImportMutation = { - mutation: { - bulkImportAreas: { - __args: { - input: parsedJsonData, - }, - addedAreas: { - id: true, - uuid: true, - area_name: true, - areaName: true, - climbs: { + setLoading(true); + try { + const bulkImportMutation = { + mutation: { + bulkImportAreas: { + __args: { + input: parsedJsonData, + }, + addedAreas: { id: true, uuid: true, - name: true, - fa: true, - length: true, - boltsCount: true, - gradeContext: true, - pitches: { + area_name: true, + areaName: true, + climbs: { id: true, - parentId: true, - pitchNumber: true, + uuid: true, + name: true, + fa: true, length: true, boltsCount: true, - description: true, + gradeContext: true, + pitches: { + id: true, + parentId: true, + pitchNumber: true, + length: true, + boltsCount: true, + description: true, + } } - } - }, - updatedAreas: { - id: true, - uuid: true, - area_name: true, - areaName: true, - climbs: { + }, + updatedAreas: { id: true, uuid: true, - name: true, - fa: true, - length: true, - boltsCount: true, - gradeContext: true, + area_name: true, + areaName: true, + climbs: { + id: true, + uuid: true, + name: true, + fa: true, + length: true, + boltsCount: true, + gradeContext: true, + } } } } - } - }; + }; - const generatedGraphqlMutation = jsonToGraphQLQuery(bulkImportMutation, { pretty: true }) + const generatedGraphqlMutation = jsonToGraphQLQuery(bulkImportMutation, { pretty: true }); + console.log('generatedGraphqlMutation:', generatedGraphqlMutation) - console.log('generatedGraphqlMutation:', generatedGraphqlMutation) + await graphqlClient.mutate({ + mutation: gql`${generatedGraphqlMutation}`, + }); - try { const { data } = await graphqlClient.mutate({ mutation: gql`${generatedGraphqlMutation}`, }); @@ -232,31 +239,31 @@ const UploadToDBComponent: React.FC = ({ parsedJsonData, isVali if (data) { console.log("Data successfully uploaded to the database:", data); toast.success("Data successfully uploaded to the database."); + setEncounteredDbUploadError(false) } - } catch (e) { - const error = e as Error; + } catch (error) { console.error("Error uploading to DB:", error); - setDbUploadError(error); toast.error("Error uploading data to the database. See console log for more details."); + setEncounteredDbUploadError(true) } finally { setLoading(false); } - }, [parsedJsonData, isValidationSuccessful]); + }, [parsedJsonData, isValidationSuccessful, setEncounteredDbUploadError]); return ( <> {loading &&

    Uploading...

    } - {dbUploadError &&

    An error occurred while uploading to the database. See console log for more details.

    } + {encounteredDbUploadError &&

    An error occurred while uploading to the database. See console log for more details.

    } - ) -} + ); +}; const BulkImport = (): JSX.Element => { From e47651c18d13812314d5581b422c9bc15194ab89 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:56:11 +0100 Subject: [PATCH 18/21] enhance upload button logic --- src/pages/import.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/import.tsx b/src/pages/import.tsx index 8f02ba726..40f841765 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -8,7 +8,7 @@ import { Lightbulb } from "@phosphor-icons/react"; const addFormats = require("ajv-formats"); const betterAjvErrors = require("better-ajv-errors").default import { jsonToGraphQLQuery } from 'json-to-graphql-query' -import { useMutation, gql } from '@apollo/client' +import { gql } from '@apollo/client' import { graphqlClient } from '../js/graphql/Client' From 179dc4edb8684b0dff65d7b535055d03e151036f Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Sun, 25 Feb 2024 19:34:22 +0100 Subject: [PATCH 19/21] first working e2e version --- .../example-uploads/update-single-area.json | 8 +++ src/pages/import.tsx | 56 +++++++------------ 2 files changed, 29 insertions(+), 35 deletions(-) create mode 100644 public/bulk-import/example-uploads/update-single-area.json diff --git a/public/bulk-import/example-uploads/update-single-area.json b/public/bulk-import/example-uploads/update-single-area.json new file mode 100644 index 000000000..07a85c7e0 --- /dev/null +++ b/public/bulk-import/example-uploads/update-single-area.json @@ -0,0 +1,8 @@ +{ + "areas": [ + { + "uuid": "78026106-8e5a-5b20-aeef-dfdaa18407e7", + "areaName": "Spring break" + } + ] +} \ No newline at end of file diff --git a/src/pages/import.tsx b/src/pages/import.tsx index 40f841765..70915322b 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -10,6 +10,7 @@ const betterAjvErrors = require("better-ajv-errors").default import { jsonToGraphQLQuery } from 'json-to-graphql-query' import { gql } from '@apollo/client' import { graphqlClient } from '../js/graphql/Client' +import { useSession } from 'next-auth/react'; function FileUploadAndValidationClientComponent() { @@ -37,7 +38,7 @@ function FileUploadAndValidationClientComponent() { ); } else { toast.success( - "JSON file and schema successfully validated. 🎊 Proceed to database upload." + "JSON file and schema successfully validated. ➤ Proceed to database upload." ); setValidationErrors([]) setIsValidationSuccessful(true) @@ -161,6 +162,8 @@ const UploadToDBComponent: React.FC = ({ encounteredDbUploadError, setEncounteredDbUploadError, }) => { + const { data: sessionData } = useSession(); + const accessToken = sessionData?.accessToken ?? ''; const [loading, setLoading] = useState(false); useEffect(() => { @@ -188,38 +191,17 @@ const UploadToDBComponent: React.FC = ({ uuid: true, area_name: true, areaName: true, - climbs: { - id: true, - uuid: true, - name: true, - fa: true, - length: true, - boltsCount: true, - gradeContext: true, - pitches: { - id: true, - parentId: true, - pitchNumber: true, - length: true, - boltsCount: true, - description: true, - } - } }, updatedAreas: { id: true, uuid: true, area_name: true, areaName: true, - climbs: { - id: true, - uuid: true, - name: true, - fa: true, - length: true, - boltsCount: true, - gradeContext: true, - } + }, + addedOrUpdatedClimbs: { + id: true, + uuid: true, + name: true, } } } @@ -228,17 +210,21 @@ const UploadToDBComponent: React.FC = ({ const generatedGraphqlMutation = jsonToGraphQLQuery(bulkImportMutation, { pretty: true }); console.log('generatedGraphqlMutation:', generatedGraphqlMutation) - await graphqlClient.mutate({ - mutation: gql`${generatedGraphqlMutation}`, - }); - const { data } = await graphqlClient.mutate({ mutation: gql`${generatedGraphqlMutation}`, - }); + context: { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + }) + console.log("Sending mutation to the database..."); if (data) { - console.log("Data successfully uploaded to the database:", data); - toast.success("Data successfully uploaded to the database."); + const updatedCount = data.bulkImportAreas.addedAreas.length + data.bulkImportAreas.updatedAreas.length + data.bulkImportAreas.addedOrUpdatedClimbs.length + + console.log("Added areas: ${addedAreasCount}, Updated areas: ${updatedAreasCount}, Added/Updated climbs: ${addedOrUpdatedClimbsCount}.", data); + toast.success(`Successfully updated ${updatedCount} database items. See console log for more details.`) setEncounteredDbUploadError(false) } } catch (error) { @@ -248,7 +234,7 @@ const UploadToDBComponent: React.FC = ({ } finally { setLoading(false); } - }, [parsedJsonData, isValidationSuccessful, setEncounteredDbUploadError]); + }, [parsedJsonData, isValidationSuccessful, setEncounteredDbUploadError, accessToken]); return ( <> From f0d2b5b2d1e313aaa0cda1ee3a0e7f1af3b718ea Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Sun, 25 Feb 2024 22:10:34 +0100 Subject: [PATCH 20/21] pimp example files --- .env | 49 ------------------- .../add-areas-with-climbs.json | 14 +++--- .../example-uploads/test_update-climbs.json | 33 ------------- .../update-climb-add-pitches.json | 36 ++++++++++++++ .../example-uploads/update-climbs.json | 29 +++++------ src/pages/import.tsx | 45 +++++++++-------- 6 files changed, 80 insertions(+), 126 deletions(-) delete mode 100644 .env delete mode 100644 public/bulk-import/example-uploads/test_update-climbs.json create mode 100644 public/bulk-import/example-uploads/update-climb-add-pitches.json diff --git a/.env b/.env deleted file mode 100644 index c4e7c1e96..000000000 --- a/.env +++ /dev/null @@ -1,49 +0,0 @@ -######### Server-side vars ########### -PAGE_REVALIDATE_TOKEN=8b&o4t!xUqAN3Y#9 - -# Auth0 authentication -AUTH0_DOMAIN=https://dev-fmjy7n5n.us.auth0.com -AUTH0_CLIENT_ID=3qGOaKjDAL3LmZmoUR7nm6uWY69aI27z -AUTH0_CLIENT_SECRET=send request to hello at openbeta.io -# generate with `openssl rand -base64 32` -NEXTAUTH_SECRET=vQyFR7gskaqxehN0cI/53r+duWc5Et0ktdoz6KozTCo= - -# Auth0 Management API -AUTH0_MGMT_CLIENT_ID=seD3dNxnZ4jXik1dzdQEgPSNSvXGBsqA -AUTH0_MGMT_CLIENT_SECRET=send request to hello at openbeta.io - -######### Client-side vars ############ -# Must prefix with NEXT_PUBLIC_in order to expose vars to the browser -# See https://nextjs.org/docs/basic-features/environment-variables - -NEXT_PUBLIC_CDN_URL=https://stg-media.openbeta.io - -NEXT_PUBLIC_API_SERVER=https://stg-api.openbeta.io - -# Disable telemetry -NEXT_TELEMETRY_DISABLED=1 - -# Typesense nodes (handle a single node for now) -NEXT_PUBLIC_TYPESENSE_NODES=lj2nqsybtrcxp5kmp-1.a1.typesense.net - -# Typesense RO API key -NEXT_PUBLIC_TYPESENSE_API_KEY=nBvjNVojLn792SX9sOLhLlpQ7BQJo5ok - -# Mapbox Geocoder token -NEXT_PUBLIC_MAPBOX_API_KEY=pk.eyJ1IjoibWFwcGFuZGFzIiwiYSI6ImNsZG1wcnBhZTA5eXozb3FnZTY1bHg4bHcifQ.7LJGRLFeLUbntAt5twiDiw - -# Discord invite -NEXT_PUBLIC_DISCORD_INVITE=https://discord.gg/a6vuuTQxS8 - -# Open Collective API URL -OPEN_COLLECTIVE_API_URI=https://api.opencollective.com/graphql/v2 - -# A comma-separate-list of profiles to pre-build -PREBUILD_PROFILES= - -# Google cloud storage bucket name -GC_BUCKET_NAME=openbeta-staging - -# A comma-separated-list of test area IDs -# to exclude from the feed -NEXT_PUBLIC_TEST_AREA_IDS=18c5dd5c-8186-50b6-8a60-ae2948c548d1 diff --git a/public/bulk-import/example-uploads/add-areas-with-climbs.json b/public/bulk-import/example-uploads/add-areas-with-climbs.json index 75d01be97..e2d663218 100644 --- a/public/bulk-import/example-uploads/add-areas-with-climbs.json +++ b/public/bulk-import/example-uploads/add-areas-with-climbs.json @@ -1,20 +1,20 @@ { "areas": [ { - "areaName": "Utah", + "areaName": "Utah (upload-test 2)", "countryCode": "us", "children": [ { - "areaName": "Southeast Utah", + "areaName": "Southeast Utah (upload-test)", "children": [ { - "areaName": "Indian Creek", + "areaName": "Indian Creek (upload-test)", "description": "Indian Creek is a crack climbing mecca in the southeastern region of Utah, USA. Located within the [Bears Ears National Monument](https://en.wikipedia.org/wiki/Bears_Ears_National_Monument).", "lng": -109.5724044642857, "lat": 38.069429035714286, "children": [ { - "areaName": "Supercrack Buttress", + "areaName": "Supercrack Buttress (upload-test)", "gradeContext": "US", "description": "", "lng": -109.54552, @@ -27,7 +27,7 @@ ], "climbs": [ { - "name": "The Key Flake", + "name": "The Key Flake (upload-test)", "grade": "5.10", "fa": "unknown", "disciplines": { @@ -56,13 +56,13 @@ "grade": "5.10", "length": 100, "boltsCount": 0, - "description": "A classic hand crack that widens slightly towards the top. Requires a range of cam sizes. Sustained and excellent quality." + "description": "(upload-test) A classic hand crack that widens slightly towards the top. Requires a range of cam sizes. Sustained and excellent quality." }, { "pitchNumber": 2, "grade": "5.9", "length": 30, - "description": "Easier climbing with good protection. Features a mix of crack sizes. Shorter than the first pitch but equally enjoyable." + "description": "(upload-test) Easier climbing with good protection. Features a mix of crack sizes. Shorter than the first pitch but equally enjoyable." } ] } diff --git a/public/bulk-import/example-uploads/test_update-climbs.json b/public/bulk-import/example-uploads/test_update-climbs.json deleted file mode 100644 index a927e6546..000000000 --- a/public/bulk-import/example-uploads/test_update-climbs.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "areas": [ - { - "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", - "climbs": [ - { - "name": "The Key Flake (test)", - "grade": "5.11", - "fa": "Bobby Updated" - }, - { - "name": "Incredible Hand Crack (test)", - "grade": "5.12", - "disciplines": { - "trad": false - }, - "description": "I have updated this (test)", - "pitches": [ - { - "pitchNumber": 1, - "length": 110, - "boltsCount": 0 - }, - { - "pitchNumber": 2, - "description": " (test) Updated: Easier climbing with good protection. Features a mix of crack sizes. Shorter than the first pitch but equally enjoyable." - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/public/bulk-import/example-uploads/update-climb-add-pitches.json b/public/bulk-import/example-uploads/update-climb-add-pitches.json new file mode 100644 index 000000000..f73f81f8f --- /dev/null +++ b/public/bulk-import/example-uploads/update-climb-add-pitches.json @@ -0,0 +1,36 @@ +{ + "areas": [ + { + "uuid": "642a1da2-4e3c-5fc7-a49b-4594be53e1f3", + "climbs": [ + { + "uuid": "973b3434-191d-4d00-be0e-217e66aa8b9c", + "name": "The Key Flake (updated)", + "grade": "5.11", + "fa": "Bobby Updated", + "disciplines": { + "trad": false + }, + "description": "Now I've got pitches, too!", + "pitches": [ + { + "pitchNumber": 1, + "grade": "5.11", + "description": "Test description for pitch number 1" + }, + { + "pitchNumber": 2, + "grade": "5.12", + "description": "Test description for pitch number 2" + }, + { + "pitchNumber": 3, + "grade": "5.13", + "description": "Test description for pitch number 3" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/public/bulk-import/example-uploads/update-climbs.json b/public/bulk-import/example-uploads/update-climbs.json index 89ef7d418..456daa2c1 100644 --- a/public/bulk-import/example-uploads/update-climbs.json +++ b/public/bulk-import/example-uploads/update-climbs.json @@ -1,21 +1,13 @@ { "areas": [ { - "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "uuid": "73da6733-68f7-5b73-8fbf-79cec45d9691", "climbs": [ { - "name": "The Key Flake (updated)", - "grade": "5.11", - "fa": "Bobby Updated" - }, - { - "name": "Incredible Hand Crack (updated)", - "grade": "5.12", - "fa": "Someone Updated", - "disciplines": { - "trad": false - }, - "description": "I have updated this", + "uuid": "524963d9-a8d0-43da-a3b6-6f4576cf4ab7", + "name": "Credible Hand Crack (updated)", + "grade": "5.6", + "fa": "Bobby Updated", "pitches": [ { "pitchNumber": 1, @@ -24,9 +16,18 @@ }, { "pitchNumber": 2, - "description": "Updated: Easier climbing with good protection. Features a mix of crack sizes. Shorter than the first pitch but equally enjoyable." + "description": "(update test) Updated: Easier climbing with good protection. Features a mix of crack sizes. Shorter than the first pitch but equally enjoyable." } ] + }, + { + "uuid": "973b3434-191d-4d00-be0e-217e66aa8b9c", + "name": "Key Fake (updated)", + "grade": "5.3", + "disciplines": { + "trad": false + }, + "description": "I have updated this (test)" } ] } diff --git a/src/pages/import.tsx b/src/pages/import.tsx index 70915322b..34a720cf2 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -12,13 +12,12 @@ import { gql } from '@apollo/client' import { graphqlClient } from '../js/graphql/Client' import { useSession } from 'next-auth/react'; - function FileUploadAndValidationClientComponent() { const fileInputRef = useRef(null) const [fileName, setFileName] = useState(null) const [validationErrors, setValidationErrors] = useState([]) const [isValidationSuccessful, setIsValidationSuccessful] = useState(false) - const [encounteredDbUploadError, setEncounteredDbUploadError] = useState(false) + const [hasEncounteredDbUploadError, setEncounteredDbUploadError] = useState(false) const [parsedJSON, setParsedJSON] = useState(null) const handleValidationErrors = (parsedJSON: object) => { @@ -143,7 +142,7 @@ function FileUploadAndValidationClientComponent() { )} - + ); @@ -152,14 +151,14 @@ function FileUploadAndValidationClientComponent() { interface UploadToDBProps { parsedJsonData: object | null isValidationSuccessful: boolean - encounteredDbUploadError: boolean; + hasEncounteredDbUploadError: boolean; setEncounteredDbUploadError: (error: boolean) => void; } const UploadToDBComponent: React.FC = ({ parsedJsonData, isValidationSuccessful, - encounteredDbUploadError, + hasEncounteredDbUploadError, setEncounteredDbUploadError, }) => { const { data: sessionData } = useSession(); @@ -167,7 +166,7 @@ const UploadToDBComponent: React.FC = ({ const [loading, setLoading] = useState(false); useEffect(() => { - if (encounteredDbUploadError) { + if (hasEncounteredDbUploadError) { setEncounteredDbUploadError(false); } }, [parsedJsonData, setEncounteredDbUploadError]); @@ -218,13 +217,14 @@ const UploadToDBComponent: React.FC = ({ }, }, }) - + console.log("Sending mutation to the database..."); if (data) { - const updatedCount = data.bulkImportAreas.addedAreas.length + data.bulkImportAreas.updatedAreas.length + data.bulkImportAreas.addedOrUpdatedClimbs.length - - console.log("Added areas: ${addedAreasCount}, Updated areas: ${updatedAreasCount}, Added/Updated climbs: ${addedOrUpdatedClimbsCount}.", data); - toast.success(`Successfully updated ${updatedCount} database items. See console log for more details.`) + const updatedCountAreas = data.bulkImportAreas.addedAreas.length + data.bulkImportAreas.updatedAreas.length + data.bulkImportAreas.addedOrUpdatedClimbs.length + const updatedCountClimbs = data.bulkImportAreas.addedOrUpdatedClimbs.length + + console.log("Detailed database update log:", data); + toast.success(`Successfully created/updated ${updatedCountAreas} database items. See console log for more details.`) setEncounteredDbUploadError(false) } } catch (error) { @@ -238,15 +238,15 @@ const UploadToDBComponent: React.FC = ({ return ( <> - {loading &&

    Uploading...

    } - {encounteredDbUploadError &&

    An error occurred while uploading to the database. See console log for more details.

    } - +
    + +
    ); }; @@ -264,8 +264,7 @@ const BulkImport = (): JSX.Element => {
    • Allows for adding or changing large amounts of data at once.
    • - -
    • Used a JSON schema to validate your upload's data structure before uploading
    • +
    • Uses a JSON schema to validate your upload's data structure before uploading
    • See this README.md for example upload files, schema, and detailed instructions.
    • Note: OpenBeta’s route database is licensed as CC-BY-SA 4.0.
    • Need help? Find us on Discord.
    • @@ -281,4 +280,4 @@ const BulkImport = (): JSX.Element => { ); }; -export default BulkImport; \ No newline at end of file +export default BulkImport \ No newline at end of file From 5388e6a6ebc9a985cb035616ba4b37c6602f9fc7 Mon Sep 17 00:00:00 2001 From: l4u532 <88317742+l4u532@users.noreply.github.com> Date: Mon, 26 Feb 2024 21:32:08 +0100 Subject: [PATCH 21/21] move example upload files to openbeta-graphql, tidy up --- public/bulk-import/README.md | 10 +- .../add-areas-with-climbs.json | 78 ------------- .../example-uploads/update-areas.json | 38 ------ .../update-climb-add-pitches.json | 36 ------ .../example-uploads/update-climbs.json | 35 ------ .../example-uploads/update-single-area.json | 8 -- .../{bulk-import-schema.json => schema.json} | 0 src/pages/import.tsx | 108 +++++++++--------- 8 files changed, 59 insertions(+), 254 deletions(-) delete mode 100644 public/bulk-import/example-uploads/add-areas-with-climbs.json delete mode 100644 public/bulk-import/example-uploads/update-areas.json delete mode 100644 public/bulk-import/example-uploads/update-climb-add-pitches.json delete mode 100644 public/bulk-import/example-uploads/update-climbs.json delete mode 100644 public/bulk-import/example-uploads/update-single-area.json rename public/bulk-import/{bulk-import-schema.json => schema.json} (100%) diff --git a/public/bulk-import/README.md b/public/bulk-import/README.md index f9ec6fd47..f03cffc74 100644 --- a/public/bulk-import/README.md +++ b/public/bulk-import/README.md @@ -10,11 +10,8 @@ Each upload is tested against the database as an uncommitted transaction, only i # Example Files This document will refer to the following example files: -* The schema file used for client-side validation: `https://openbeta.io/bulk-import/bulk-import-schema.json` -* Example files that guide you how to structure your JSON files for mass upload - * (1) `https://openbeta.io/bulk-import/example-uploads/add-areas-with-climbs.json` - * (2) `https://openbeta.io/bulk-import/example-uploads/update-areas-with-climbs.json` - * (3) `https://openbeta.io/bulk-import/example-uploads/update-climbs-with-pitches.json` +* The schema file used for client-side validation: `https://openbeta.io/bulk-import/schema.json` (use the schema locally by adding `"$schema": "schema.json"` to the first line of your JSON file to be uploaded) +* Example files that guide you how to structure your JSON files for mass upload are located at TODO: Link to GitHub dir # Schema ## General Data Hierarchy @@ -50,6 +47,9 @@ Let's look at the example `add-areas-with-climbs.json` for an illustration: > - "Pitch 1" (Pitch, Type: `Climb.Pitch`) > - "Pitch 2" (Pitch, Type: `Climb.Pitch`) +## Using the schema.json + + ### A note on `Area`s * An `Area` may be a country, state, region, crag or sector (and may be infinetely nested) * It is only the leaf area node that determines its type, which is (moslty) automatically determined server-side based on certain criteria: diff --git a/public/bulk-import/example-uploads/add-areas-with-climbs.json b/public/bulk-import/example-uploads/add-areas-with-climbs.json deleted file mode 100644 index e2d663218..000000000 --- a/public/bulk-import/example-uploads/add-areas-with-climbs.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "areas": [ - { - "areaName": "Utah (upload-test 2)", - "countryCode": "us", - "children": [ - { - "areaName": "Southeast Utah (upload-test)", - "children": [ - { - "areaName": "Indian Creek (upload-test)", - "description": "Indian Creek is a crack climbing mecca in the southeastern region of Utah, USA. Located within the [Bears Ears National Monument](https://en.wikipedia.org/wiki/Bears_Ears_National_Monument).", - "lng": -109.5724044642857, - "lat": 38.069429035714286, - "children": [ - { - "areaName": "Supercrack Buttress (upload-test)", - "gradeContext": "US", - "description": "", - "lng": -109.54552, - "lat": 38.03635, - "bbox": [ - -109.54609091005857, - 38.03590033981814, - -109.54494908994141, - 38.03679966018186 - ], - "climbs": [ - { - "name": "The Key Flake (upload-test)", - "grade": "5.10", - "fa": "unknown", - "disciplines": { - "trad": true - }, - "lng": -109.54552, - "lat": 38.03635, - "leftRightIndex": 1, - "description": "Cool off-width that requires off-width and face skills.", - "protection": "Anchors hidden up top. Need 80m to make it all the way down.", - "location": "Opposite keyhole flake. Obvious right leaning offwidth that starts atop 20 ft boulder." - }, - { - "name": "Incredible Hand Crack", - "grade": "5.10", - "fa": "Rich Perch, John Bragg, Doug Snively, and Anne Tarver, 1978", - "disciplines": { - "trad": true - }, - "leftRightIndex": 2, - "description": "Route starts at the top of the trail from the parking lot to Supercrack Buttress.", - "protection": "Cams from 2-2.5\". Heavy on 2.5\" (#2 Camalot)", - "pitches": [ - { - "pitchNumber": 1, - "grade": "5.10", - "length": 100, - "boltsCount": 0, - "description": "(upload-test) A classic hand crack that widens slightly towards the top. Requires a range of cam sizes. Sustained and excellent quality." - }, - { - "pitchNumber": 2, - "grade": "5.9", - "length": 30, - "description": "(upload-test) Easier climbing with good protection. Features a mix of crack sizes. Shorter than the first pitch but equally enjoyable." - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/public/bulk-import/example-uploads/update-areas.json b/public/bulk-import/example-uploads/update-areas.json deleted file mode 100644 index 01e8861f5..000000000 --- a/public/bulk-import/example-uploads/update-areas.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "areas": [ - { - "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", - "areaName": "Area with updated name, coordinates, description", - "lng": -99.99, - "lat": 11.11, - "description": "Some new description." - }, - { - "uuid": "a3f159cb-2f10-4e3a-8bce-6689a70b76b8", - "areaName": "Existing area, where new children areas are added", - "children": [ - { - "uuid": "b1a6492f-8d58-4d5b-8b1a-51b75f51e990", - "areaName": "New Area 1", - "lng": -100.00, - "lat": 20.00, - "description": "Description of New Area 1" - }, - { - "uuid": "c2d3e1f4-41e4-4dce-9bce-5b6a6395d570", - "areaName": "New Area 2", - "lng": -101.00, - "lat": 21.00, - "description": "Description of New Area 2" - }, - { - "uuid": "d4e5f6a7-8b9c-4d0e-9abc-7d8e9fa6b7c8", - "areaName": "New Area 3", - "lng": -102.00, - "lat": 22.00, - "description": "Description of New Area 3" - } - ] - } - ] -} diff --git a/public/bulk-import/example-uploads/update-climb-add-pitches.json b/public/bulk-import/example-uploads/update-climb-add-pitches.json deleted file mode 100644 index f73f81f8f..000000000 --- a/public/bulk-import/example-uploads/update-climb-add-pitches.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "areas": [ - { - "uuid": "642a1da2-4e3c-5fc7-a49b-4594be53e1f3", - "climbs": [ - { - "uuid": "973b3434-191d-4d00-be0e-217e66aa8b9c", - "name": "The Key Flake (updated)", - "grade": "5.11", - "fa": "Bobby Updated", - "disciplines": { - "trad": false - }, - "description": "Now I've got pitches, too!", - "pitches": [ - { - "pitchNumber": 1, - "grade": "5.11", - "description": "Test description for pitch number 1" - }, - { - "pitchNumber": 2, - "grade": "5.12", - "description": "Test description for pitch number 2" - }, - { - "pitchNumber": 3, - "grade": "5.13", - "description": "Test description for pitch number 3" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/public/bulk-import/example-uploads/update-climbs.json b/public/bulk-import/example-uploads/update-climbs.json deleted file mode 100644 index 456daa2c1..000000000 --- a/public/bulk-import/example-uploads/update-climbs.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "areas": [ - { - "uuid": "73da6733-68f7-5b73-8fbf-79cec45d9691", - "climbs": [ - { - "uuid": "524963d9-a8d0-43da-a3b6-6f4576cf4ab7", - "name": "Credible Hand Crack (updated)", - "grade": "5.6", - "fa": "Bobby Updated", - "pitches": [ - { - "pitchNumber": 1, - "length": 110, - "boltsCount": 0 - }, - { - "pitchNumber": 2, - "description": "(update test) Updated: Easier climbing with good protection. Features a mix of crack sizes. Shorter than the first pitch but equally enjoyable." - } - ] - }, - { - "uuid": "973b3434-191d-4d00-be0e-217e66aa8b9c", - "name": "Key Fake (updated)", - "grade": "5.3", - "disciplines": { - "trad": false - }, - "description": "I have updated this (test)" - } - ] - } - ] -} \ No newline at end of file diff --git a/public/bulk-import/example-uploads/update-single-area.json b/public/bulk-import/example-uploads/update-single-area.json deleted file mode 100644 index 07a85c7e0..000000000 --- a/public/bulk-import/example-uploads/update-single-area.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "areas": [ - { - "uuid": "78026106-8e5a-5b20-aeef-dfdaa18407e7", - "areaName": "Spring break" - } - ] -} \ No newline at end of file diff --git a/public/bulk-import/bulk-import-schema.json b/public/bulk-import/schema.json similarity index 100% rename from public/bulk-import/bulk-import-schema.json rename to public/bulk-import/schema.json diff --git a/src/pages/import.tsx b/src/pages/import.tsx index 34a720cf2..2bb158aa0 100644 --- a/src/pages/import.tsx +++ b/src/pages/import.tsx @@ -1,16 +1,16 @@ //'use client' -import Ajv from "ajv"; +import Ajv from "ajv" import React, { useState, useRef, useCallback, useEffect } from "react" -import { toast } from 'react-toastify'; -import Layout from '../components/layout'; -import schema from "../../public/bulk-import/bulk-import-schema.json" -import { Lightbulb } from "@phosphor-icons/react"; -const addFormats = require("ajv-formats"); +import { toast } from 'react-toastify' +import Layout from '../components/layout' +import schema from "../../public/bulk-import/schema.json" +import { Lightbulb } from "@phosphor-icons/react" +const addFormats = require("ajv-formats") const betterAjvErrors = require("better-ajv-errors").default import { jsonToGraphQLQuery } from 'json-to-graphql-query' import { gql } from '@apollo/client' import { graphqlClient } from '../js/graphql/Client' -import { useSession } from 'next-auth/react'; +import { useSession } from 'next-auth/react' function FileUploadAndValidationClientComponent() { const fileInputRef = useRef(null) @@ -22,39 +22,39 @@ function FileUploadAndValidationClientComponent() { const handleValidationErrors = (parsedJSON: object) => { try { - const ajv = new Ajv({ allErrors: true, verbose: true }); - addFormats(ajv); - const compiledSchema = ajv.compile(schema); - const validate = compiledSchema(parsedJSON); + const ajv = new Ajv({ allErrors: true, verbose: true }) + addFormats(ajv) + const compiledSchema = ajv.compile(schema) + const validate = compiledSchema(parsedJSON) if (!validate) { const betterErrors = betterAjvErrors(schema, parsedJSON, compiledSchema.errors, { format: 'js' }) console.log(betterErrors) - setValidationErrors(betterErrors); + setValidationErrors(betterErrors) toast.error( betterErrors.length > 0 ? "Error: Schema validation errors found." : "Error: Schema validation failed, but no errors were provided." - ); + ) } else { toast.success( "JSON file and schema successfully validated. ➤ Proceed to database upload." - ); + ) setValidationErrors([]) setIsValidationSuccessful(true) } } catch (error) { - console.error("Error during validation:", error); + console.error("Error during validation:", error) toast.error( "Error: An unexpected error occurred during validation. See browser console log for more details." - ); + ) } - }; + } function safelyParseJSON(json: string) { try { - return JSON.parse(json); + return JSON.parse(json) } catch (e) { - toast.error(`Error: Could not parse JSON file. ${e}`); - return null; + toast.error(`Error: Could not parse JSON file. ${e}`) + return null } } @@ -67,27 +67,27 @@ function FileUploadAndValidationClientComponent() { const file = event.target.files ? event.target.files[0] : null if (file) { - const reader = new FileReader(); + const reader = new FileReader() reader.onload = (event) => { - const file = event.target?.result; + const file = event.target?.result if (typeof file === "string") { - const parsedJSON = safelyParseJSON(file); + const parsedJSON = safelyParseJSON(file) if (parsedJSON) { - setParsedJSON(parsedJSON); // Update the state - handleValidationErrors(parsedJSON); + setParsedJSON(parsedJSON) + handleValidationErrors(parsedJSON) } else { - toast.error("Error: Not a valid JSON file"); + toast.error("Error: Not a valid JSON file") } } - }; - reader.readAsText(file); + } + reader.readAsText(file) } - }; + } const handleButtonClick = () => { - fileInputRef.current?.click(); - }; + fileInputRef.current?.click() + } return ( <>
      @@ -145,14 +145,14 @@ function FileUploadAndValidationClientComponent() { - ); + ) } interface UploadToDBProps { parsedJsonData: object | null isValidationSuccessful: boolean - hasEncounteredDbUploadError: boolean; - setEncounteredDbUploadError: (error: boolean) => void; + hasEncounteredDbUploadError: boolean + setEncounteredDbUploadError: (error: boolean) => void } const UploadToDBComponent: React.FC = ({ @@ -161,23 +161,23 @@ const UploadToDBComponent: React.FC = ({ hasEncounteredDbUploadError, setEncounteredDbUploadError, }) => { - const { data: sessionData } = useSession(); - const accessToken = sessionData?.accessToken ?? ''; - const [loading, setLoading] = useState(false); + const { data: sessionData } = useSession() + const accessToken = sessionData?.accessToken ?? '' + const [loading, setLoading] = useState(false) useEffect(() => { if (hasEncounteredDbUploadError) { - setEncounteredDbUploadError(false); + setEncounteredDbUploadError(false) } - }, [parsedJsonData, setEncounteredDbUploadError]); + }, [parsedJsonData, setEncounteredDbUploadError]) const handleUpload = useCallback(async () => { if (!isValidationSuccessful || !parsedJsonData) { - toast.error("JSON schema validation unsuccessful. Fix the errors and try again."); - return; + toast.error("JSON schema validation unsuccessful. Fix the errors and try again.") + return } - setLoading(true); + setLoading(true) try { const bulkImportMutation = { mutation: { @@ -204,9 +204,9 @@ const UploadToDBComponent: React.FC = ({ } } } - }; + } - const generatedGraphqlMutation = jsonToGraphQLQuery(bulkImportMutation, { pretty: true }); + const generatedGraphqlMutation = jsonToGraphQLQuery(bulkImportMutation, { pretty: true }) console.log('generatedGraphqlMutation:', generatedGraphqlMutation) const { data } = await graphqlClient.mutate({ @@ -218,23 +218,23 @@ const UploadToDBComponent: React.FC = ({ }, }) - console.log("Sending mutation to the database..."); + console.log("Sending mutation to the database...") if (data) { const updatedCountAreas = data.bulkImportAreas.addedAreas.length + data.bulkImportAreas.updatedAreas.length + data.bulkImportAreas.addedOrUpdatedClimbs.length const updatedCountClimbs = data.bulkImportAreas.addedOrUpdatedClimbs.length - console.log("Detailed database update log:", data); + console.log("Detailed database update log:", data) toast.success(`Successfully created/updated ${updatedCountAreas} database items. See console log for more details.`) setEncounteredDbUploadError(false) } } catch (error) { - console.error("Error uploading to DB:", error); - toast.error("Error uploading data to the database. See console log for more details."); + console.error("Error uploading to DB:", error) + toast.error("Error uploading data to the database. See console log for more details.") setEncounteredDbUploadError(true) } finally { - setLoading(false); + setLoading(false) } - }, [parsedJsonData, isValidationSuccessful, setEncounteredDbUploadError, accessToken]); + }, [parsedJsonData, isValidationSuccessful, setEncounteredDbUploadError, accessToken]) return ( <> @@ -248,8 +248,8 @@ const UploadToDBComponent: React.FC = ({
      - ); -}; + ) +} const BulkImport = (): JSX.Element => { @@ -277,7 +277,7 @@ const BulkImport = (): JSX.Element => {
    - ); -}; + ) +} export default BulkImport \ No newline at end of file