diff --git a/next.config.js b/next.config.js index 9077fbb2..43026471 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,7 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, + scrollRestoration: true, // swcMinify: false, } diff --git a/package-lock.json b/package-lock.json index fbb03b08..8b522bf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.7.0", "dependencies": { "@headlessui/react": "1.7.8", + "@tailwindcss/container-queries": "0.1.1", "date-fns": "2.29.3", "maplibre-gl": "2.4.0", "next": "13.4.12", @@ -16,6 +17,7 @@ "react-dom": "18.2.0", "sanitize-html": "2.7.3", "swiper": "8.4.5", + "swr": "2.2.0", "use-debounce": "9.0.2" }, "devDependencies": { @@ -75,7 +77,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "engines": { "node": ">=10" }, @@ -601,7 +602,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -613,7 +614,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -2097,7 +2098,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -2106,7 +2106,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -2114,14 +2113,12 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.17", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -2336,7 +2333,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -2349,7 +2345,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -2358,7 +2353,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -2799,6 +2793,14 @@ "tslib": "^2.4.0" } }, + "node_modules/@tailwindcss/container-queries": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/container-queries/-/container-queries-0.1.1.tgz", + "integrity": "sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==", + "peerDependencies": { + "tailwindcss": ">=3.2.0" + } + }, "node_modules/@tailwindcss/line-clamp": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/@tailwindcss/line-clamp/-/line-clamp-0.4.4.tgz", @@ -7380,25 +7382,25 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node16": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true + "devOptional": true }, "node_modules/@types/aria-query": { "version": "5.0.1", @@ -7587,7 +7589,7 @@ "version": "18.17.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==", - "dev": true + "devOptional": true }, "node_modules/@types/node-fetch": { "version": "2.6.4", @@ -8034,7 +8036,7 @@ "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, + "devOptional": true, "bin": { "acorn": "bin/acorn" }, @@ -8168,14 +8170,12 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -8187,8 +8187,7 @@ "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, "node_modules/argparse": { "version": "2.0.1", @@ -8630,8 +8629,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/before-after-hook": { "version": "2.2.3", @@ -8652,7 +8650,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } @@ -8685,7 +8682,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8695,7 +8691,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -8813,7 +8808,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "engines": { "node": ">= 6" } @@ -8894,7 +8888,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "funding": [ { "type": "individual", @@ -8921,7 +8914,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -9049,8 +9041,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/config-chain": { "version": "1.1.13", @@ -9163,7 +9154,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "devOptional": true }, "node_modules/cross-fetch": { "version": "3.1.5", @@ -9295,7 +9286,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "bin": { "cssesc": "bin/cssesc" }, @@ -9744,14 +9734,13 @@ "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.3.1" } @@ -9780,8 +9769,7 @@ "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, "node_modules/doctrine": { "version": "3.0.0", @@ -10902,7 +10890,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -10918,7 +10905,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -10942,7 +10928,6 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -10987,7 +10972,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -11128,14 +11112,12 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -11148,8 +11130,7 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -11326,7 +11307,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -11464,7 +11444,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -11743,7 +11722,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -11752,8 +11730,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "1.3.8", @@ -11842,7 +11819,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -11882,7 +11858,6 @@ "version": "2.12.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -11924,7 +11899,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -11951,7 +11925,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -12002,7 +11975,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -15063,7 +15035,6 @@ "version": "1.18.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", - "dev": true, "bin": { "jiti": "bin/jiti.js" } @@ -15312,7 +15283,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, "engines": { "node": ">=10" } @@ -15320,8 +15290,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/load-json-file": { "version": "4.0.0", @@ -15486,7 +15455,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "devOptional": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -15665,7 +15634,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -15674,7 +15642,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -15738,7 +15705,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -15792,7 +15758,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -16010,7 +15975,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -18636,7 +18600,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -18645,7 +18608,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "engines": { "node": ">= 6" } @@ -18779,7 +18741,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -18991,7 +18952,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -19008,8 +18968,7 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-type": { "version": "4.0.0", @@ -19041,7 +19000,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -19062,7 +19020,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, "engines": { "node": ">= 6" } @@ -19242,7 +19199,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -19259,7 +19215,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "dependencies": { "camelcase-css": "^2.0.1" }, @@ -19278,7 +19233,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", - "dev": true, "dependencies": { "lilconfig": "^2.0.5", "yaml": "^2.1.1" @@ -19307,7 +19261,6 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", - "dev": true, "engines": { "node": ">= 14" } @@ -19316,7 +19269,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.11" }, @@ -19335,7 +19287,6 @@ "version": "6.0.13", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", - "dev": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -19360,8 +19311,7 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/potpack": { "version": "1.0.2", @@ -19518,7 +19468,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -19605,7 +19554,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "dependencies": { "pify": "^2.3.0" } @@ -19614,7 +19562,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -19773,7 +19720,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -19857,7 +19803,6 @@ "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, "dependencies": { "is-core-module": "^2.11.0", "path-parse": "^1.0.7", @@ -19939,7 +19884,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -19979,7 +19923,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -20639,7 +20582,6 @@ "version": "3.32.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", @@ -20661,7 +20603,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -20675,7 +20616,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "engines": { "node": ">= 6" } @@ -20684,7 +20624,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -20758,7 +20697,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -20810,6 +20748,17 @@ "node": ">= 4.7.0" } }, + "node_modules/swr": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz", + "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==", + "dependencies": { + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -20836,7 +20785,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", - "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -20873,7 +20821,6 @@ "version": "6.0.13", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", - "dev": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -20980,7 +20927,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "dependencies": { "any-promise": "^1.0.0" } @@ -20989,7 +20935,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -21062,7 +21007,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -21127,14 +21071,13 @@ "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, + "devOptional": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -21177,7 +21120,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.4.0" } @@ -21186,7 +21129,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "devOptional": true }, "node_modules/tsconfig-paths": { "version": "3.14.2", @@ -21430,7 +21373,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -21565,17 +21508,24 @@ "react": ">=16.8.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "devOptional": true }, "node_modules/v8-to-istanbul": { "version": "9.0.1", @@ -21830,8 +21780,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", @@ -21955,7 +21904,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -21997,8 +21946,7 @@ "@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==" }, "@ampproject/remapping": { "version": "2.2.0", @@ -22387,7 +22335,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -22396,7 +22344,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -23527,26 +23475,22 @@ "@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, "@jridgewell/set-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" }, "@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { "version": "0.3.17", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, "requires": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -23671,7 +23615,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -23680,14 +23623,12 @@ "@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" }, "@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -24032,6 +23973,12 @@ "tslib": "^2.4.0" } }, + "@tailwindcss/container-queries": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/container-queries/-/container-queries-0.1.1.tgz", + "integrity": "sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==", + "requires": {} + }, "@tailwindcss/line-clamp": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/@tailwindcss/line-clamp/-/line-clamp-0.4.4.tgz", @@ -27136,25 +27083,25 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "devOptional": true }, "@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "devOptional": true }, "@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "devOptional": true }, "@tsconfig/node16": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true + "devOptional": true }, "@types/aria-query": { "version": "5.0.1", @@ -27333,7 +27280,7 @@ "version": "18.17.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.1.tgz", "integrity": "sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==", - "dev": true + "devOptional": true }, "@types/node-fetch": { "version": "2.6.4", @@ -27656,7 +27603,7 @@ "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true + "devOptional": true }, "acorn-globals": { "version": "6.0.0", @@ -27753,14 +27700,12 @@ "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -27769,8 +27714,7 @@ "arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, "argparse": { "version": "2.0.1", @@ -28095,8 +28039,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "before-after-hook": { "version": "2.2.3", @@ -28113,8 +28056,7 @@ "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "boolbase": { "version": "1.0.0", @@ -28141,7 +28083,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -28151,7 +28092,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -28231,8 +28171,7 @@ "camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, "camelcase-keys": { "version": "6.2.2", @@ -28281,7 +28220,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -28297,7 +28235,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -28403,8 +28340,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "config-chain": { "version": "1.1.13", @@ -28496,7 +28432,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "devOptional": true }, "cross-fetch": { "version": "3.1.5", @@ -28600,8 +28536,7 @@ "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" }, "csso": { "version": "4.2.0", @@ -28919,14 +28854,13 @@ "didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true + "devOptional": true }, "diff-sequences": { "version": "29.2.0", @@ -28946,8 +28880,7 @@ "dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, "doctrine": { "version": "3.0.0", @@ -29780,7 +29713,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -29793,7 +29725,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -29816,7 +29747,6 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, "requires": { "reusify": "^1.0.4" } @@ -29852,7 +29782,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -29948,21 +29877,18 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "optional": true }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "function.prototype.name": { "version": "1.1.5", @@ -30099,7 +30025,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "requires": { "is-glob": "^4.0.3" } @@ -30201,7 +30126,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -30378,7 +30302,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -30387,8 +30310,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", @@ -30456,7 +30378,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -30481,7 +30402,6 @@ "version": "2.12.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dev": true, "requires": { "has": "^1.0.3" } @@ -30504,8 +30424,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -30523,7 +30442,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -30552,8 +30470,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-number-object": { "version": "1.0.7", @@ -32849,8 +32766,7 @@ "jiti": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", - "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", - "dev": true + "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==" }, "js-tokens": { "version": "4.0.0", @@ -33042,14 +32958,12 @@ "lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==" }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "load-json-file": { "version": "4.0.0", @@ -33186,7 +33100,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "devOptional": true }, "makeerror": { "version": "1.0.12", @@ -33319,14 +33233,12 @@ "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "requires": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -33369,7 +33281,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -33411,7 +33322,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "requires": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -33563,8 +33473,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-range": { "version": "0.1.2", @@ -35394,14 +35303,12 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-hash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" }, "object-inspect": { "version": "1.12.3", @@ -35496,7 +35403,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -35644,8 +35550,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", @@ -35656,8 +35561,7 @@ "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-type": { "version": "4.0.0", @@ -35682,8 +35586,7 @@ "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pify": { "version": "3.0.0", @@ -35694,8 +35597,7 @@ "pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==" }, "pkg-conf": { "version": "2.1.0", @@ -35820,7 +35722,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "requires": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -35831,7 +35732,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "requires": { "camelcase-css": "^2.0.1" } @@ -35840,7 +35740,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", - "dev": true, "requires": { "lilconfig": "^2.0.5", "yaml": "^2.1.1" @@ -35849,8 +35748,7 @@ "yaml": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", - "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", - "dev": true + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==" } } }, @@ -35858,7 +35756,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, "requires": { "postcss-selector-parser": "^6.0.11" }, @@ -35867,7 +35764,6 @@ "version": "6.0.13", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", - "dev": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -35888,8 +35784,7 @@ "postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "potpack": { "version": "1.0.2", @@ -36015,8 +35910,7 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "quick-lru": { "version": "4.0.1", @@ -36076,7 +35970,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "requires": { "pify": "^2.3.0" }, @@ -36084,8 +35977,7 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==" } } }, @@ -36216,7 +36108,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -36282,7 +36173,6 @@ "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, "requires": { "is-core-module": "^2.11.0", "path-parse": "^1.0.7", @@ -36341,8 +36231,7 @@ "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, "rimraf": { "version": "3.0.2", @@ -36366,7 +36255,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -36883,7 +36771,6 @@ "version": "3.32.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", - "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", @@ -36898,7 +36785,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, "requires": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -36908,14 +36794,12 @@ "commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -36974,8 +36858,7 @@ "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, "svgo": { "version": "2.8.0", @@ -37001,6 +36884,14 @@ "ssr-window": "^4.0.2" } }, + "swr": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz", + "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==", + "requires": { + "use-sync-external-store": "^1.2.0" + } + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -37021,7 +36912,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", - "dev": true, "requires": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -37051,7 +36941,6 @@ "version": "6.0.13", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", - "dev": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -37129,7 +37018,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "requires": { "any-promise": "^1.0.0" } @@ -37138,7 +37026,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "requires": { "thenify": ">= 3.1.0 < 4" } @@ -37198,7 +37085,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -37247,14 +37133,13 @@ "ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, + "devOptional": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -37275,13 +37160,13 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true + "devOptional": true }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "devOptional": true } } }, @@ -37470,7 +37355,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true + "devOptional": true }, "uglify-js": { "version": "3.17.4", @@ -37559,17 +37444,22 @@ "integrity": "sha512-QLyB0sxt9F5AisGDrUybCRJSLE60bTQR0yXc+IebNGUu1GCXwii1zsZl82mPGdWqDVQy7+1FKMLHQUixxf5Nbw==", "requires": {} }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "devOptional": true }, "v8-to-istanbul": { "version": "9.0.1", @@ -37768,8 +37658,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "write-file-atomic": { "version": "4.0.2", @@ -37857,7 +37746,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true + "devOptional": true }, "yocto-queue": { "version": "0.1.0", diff --git a/package.json b/package.json index 405e4a82..cb8a2850 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "@headlessui/react": "1.7.8", + "@tailwindcss/container-queries": "0.1.1", "date-fns": "2.29.3", "maplibre-gl": "2.4.0", "next": "13.4.12", @@ -23,6 +24,7 @@ "react-dom": "18.2.0", "sanitize-html": "2.7.3", "swiper": "8.4.5", + "swr": "2.2.0", "use-debounce": "9.0.2" }, "devDependencies": { @@ -62,4 +64,4 @@ "tsconfig-paths-webpack-plugin": "3.5.2", "typescript": "4.9.5" } -} \ No newline at end of file +} diff --git a/pages/api/search.ts b/pages/api/search.ts new file mode 100644 index 00000000..ada4ef71 --- /dev/null +++ b/pages/api/search.ts @@ -0,0 +1,100 @@ +import { TableRowType } from '@common/types/gristData' +import { NextApiRequest, NextApiResponse } from 'next' +import path from 'path' +import fs from 'fs/promises' +import { existsSync } from 'fs' + +const searchData = ({ + data, + query, + filters, +}: { + data: TableRowType[] + query: string + filters: string[] +}): TableRowType[] => { + if (filters.length === 0) return [] + if (!query && filters.length === 5) return data + return data.filter((item) => { + // check if item.fields.Typ is in the filters array + if (filters.length > 0 && !filters.includes(item.fields.Typ)) { + return false + } + + // Return true if there is no query + if (!query) return true + + // Convert each field value into a string and join them together. + // const str = Object.values(item.fields) + // .map((value) => String(value)) + // .join(' ') + // only filter specific fields + const str = [ + item.fields.Einrichtung, + item.fields.Schlagworte.join(' '), + item.fields.Zielgruppen, + item.fields.Trager, + item.fields.Kategorie, + item.fields.Sprachen, + item.fields.Uber_uns, + item.fields.Strasse, + item.fields.Hausnummer, + item.fields.Zusatz, + item.fields.PLZ, + item.fields.Bezirk, + item.fields.Stadtteil, + item.fields.Telefonnummer, + item.fields.EMail, + item.fields.Typ, + ].join(' ') + + // Return true if the keyword exists in str. + return str.toLowerCase().includes(query.toLowerCase()) + }) +} +const handler = async ( + req: NextApiRequest, + res: NextApiResponse +): Promise => { + const url = new URL(req.url ?? '', 'http://localhost') + + // Get all query parameters as an object + const params = Object.fromEntries(url.searchParams.entries()) + // check if the q parameter exists + + if (typeof params.query !== 'string') { + return res + .status(400) + .json({ result: null, error: 'Missing query parameter "query"' }) + } + + // the paramters filters is a comma sparated list. Create an an array from it + // If there is no filter applied we still get an string of 0 length. Which gets split into [""] an array with one empty string + // to prevent our filter function from failing we replace this with an empty array + const filters = params.filters.length === 0 ? [] : params.filters.split(',') + + const filePath = path.resolve(process.cwd(), './data/records.json') + // check if the file ath te path exists + if (!existsSync(filePath)) { + return res + .status(500) + .json({ result: null, error: 'data/records.json File not found' }) + } + + try { + const content = await fs.readFile(filePath, 'utf8') + const data = JSON.parse(content) as TableRowType[] + const result = searchData({ data, query: params.query, filters }) + return res.status(200).json({ params, result, total: data.length }) + } catch (error: unknown) { + if (error instanceof Error) { + return res.status(500).json({ result: null, error: error.message }) + } else { + return res + .status(500) + .json({ result: null, error: new Error('unknown error') }) + } + } +} + +export default handler diff --git a/pages/index.tsx b/pages/index.tsx index 65b20dfe..6c97a5f1 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -4,19 +4,21 @@ import classNames from '@lib/classNames' import { WelcomeScreen } from '@components/WelcomeScreen' import { WelcomeFilters } from '@components/WelcomeFilters' import { useEffect, useState } from 'react' -import { GristLabelType, TableRowType } from '@common/types/gristData' +import { GristLabelType } from '@common/types/gristData' import { useIsMobile } from '@lib/hooks/useIsMobile' import { LegalFooter } from '@components/LegalFooter' import { Page } from '@common/types/nextPage' import { LabelsProvider } from '@lib/LabelsContext' import { loadData } from '@lib/loadData' import { Footer } from '@components/Footer' +import { RecordsWithOnlyLabelsType } from '@lib/hooks/useFilteredFacilitiesCount' export const getStaticProps: GetStaticProps = async () => { const { texts, labels, records } = await loadData() - const recordsWithOnlyLabels = records.map( - (records) => records.fields.Schlagworte - ) + const recordsWithOnlyLabels = records.map((record) => [ + record.id, + record.fields.Schlagworte, + ]) return { props: { texts, recordsWithOnlyLabels, labels }, revalidate: 120, @@ -24,7 +26,7 @@ export const getStaticProps: GetStaticProps = async () => { } interface HomePropsType { - recordsWithOnlyLabels: TableRowType['fields']['Schlagworte'][] + recordsWithOnlyLabels: RecordsWithOnlyLabelsType[] labels: GristLabelType[] } diff --git a/pages/info.tsx b/pages/info.tsx index 971dfeb0..42ea85c5 100644 --- a/pages/info.tsx +++ b/pages/info.tsx @@ -20,14 +20,21 @@ export const getStaticProps: GetStaticProps = async () => { } const Info: Page = () => { - const { back } = useRouter() + const { query } = useRouter() + + const { back, ...restQuery } = query return (
Info - HILF-MIR Berlin
- void back()} /> +
diff --git a/pages/map.tsx b/pages/map.tsx index 1c5933b6..b5c1bbfc 100644 --- a/pages/map.tsx +++ b/pages/map.tsx @@ -13,8 +13,9 @@ import { useDistanceToUser } from '@lib/hooks/useDistanceToUser' import { useUserGeolocation } from '@lib/hooks/useUserGeolocation' import { useFiltersWithActiveProp } from '@lib/hooks/useFiltersWithActiveProp' import { getFilteredFacilities } from '@lib/facilityFilterUtil' -import { Button } from '@components/Button' import { FacilityListItem } from '@components/FacilityListItem' +import { useActiveIdsBySearchTerm } from '@lib/hooks/useActiveIdsBySearchTerm' +import ActiveFiltersList from '@components/ActiveFiltersList' export const getStaticProps: GetStaticProps = async () => { const { texts, labels, records } = await loadData() @@ -41,12 +42,13 @@ interface MapProps { const MapPage: Page = ({ records: originalRecords }) => { const [urlState] = useUrlState() const texts = useTexts() - const { isFallback } = useRouter() + const { query, isFallback } = useRouter() const { getDistanceToUser } = useDistanceToUser() const { useGeolocation } = useUserGeolocation() const labels = useFiltersWithActiveProp() const [filteredRecords, setFilteredRecords] = useState(originalRecords) + const activeIdsBySearchTerm = useActiveIdsBySearchTerm() const sortByTagsCount = useCallback( (a: MinimalRecordType, b: MinimalRecordType) => { @@ -101,13 +103,27 @@ const MapPage: Page = ({ records: originalRecords }) => { [getDistanceToUser, useGeolocation, defaultSort, sortByTagsCount] ) + const tagsKey = urlState.tags?.join('-') || '' useEffect(() => { const filteredRecords = getFilteredFacilities({ facilities: originalRecords, labels, + activeIdsBySearchTerm: activeIdsBySearchTerm.ids, }) return setFilteredRecords(sortFacilities(filteredRecords)) - }, [urlState.tags?.join('-')]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tagsKey, activeIdsBySearchTerm.key, activeIdsBySearchTerm.isLoading]) + + useEffect(() => { + if (!query.back || typeof query.back !== 'string') return + const prevItemId = query.back.replace('/', '') + if (!prevItemId) return + const listEl = document.getElementById(`facility-${prevItemId}`) + if (!listEl) return + listEl.scrollIntoView({ behavior: 'instant' as ScrollBehavior }) + listEl?.focus() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) return ( <> @@ -118,35 +134,23 @@ const MapPage: Page = ({ records: originalRecords }) => { : `${texts.resultPageTitle} – ${texts.siteTitle}`} - {labels.filter((label) => label.isActive).length > 0 && ( + + + {filteredRecords.length === 0 && (
-

{texts.resultPageIntro}

-
    - {labels - .filter((label) => label.isActive) - .map((label) => ( - - ))} -
+

{texts.noResults}

)} -
    - {filteredRecords.map((record) => ( -
  • - -
  • - ))} -
+ {filteredRecords.length > 0 && ( +
    + {filteredRecords.map((record) => ( +
  • + +
  • + ))} +
+ )} ) } diff --git a/pages/sofortige-hilfe.tsx b/pages/sofortige-hilfe.tsx index d08b2df7..8aa613be 100644 --- a/pages/sofortige-hilfe.tsx +++ b/pages/sofortige-hilfe.tsx @@ -26,7 +26,7 @@ const Home: NextPage = () => { const [selectedNeighborhood, setSelectedNeighborhood] = useState( neigborhoodKeys[0] ) - const { back } = useRouter() + const { query } = useRouter() const isEmergencyTime = useIsEmergencyTime() @@ -51,7 +51,12 @@ const Home: NextPage = () => {
- void back()} /> +

{texts.directHelpButtonText} diff --git a/scripts/createFakeGristData.ts b/scripts/createFakeGristData.ts index 377855c6..dc1bcf02 100644 --- a/scripts/createFakeGristData.ts +++ b/scripts/createFakeGristData.ts @@ -83,6 +83,8 @@ const fakeData: Omit[] = Array.from(Array(300)).map(() => { Weitere_Offnungszeiten: '', Wichtige_Hinweise: '', Zusatz: '', + Typ: 'Beratung', + Kategorie: 'KJPD', } as TableRowType['fields'], } }) diff --git a/src/common/types/gristData.ts b/src/common/types/gristData.ts index 33d1e110..bb801c0a 100644 --- a/src/common/types/gristData.ts +++ b/src/common/types/gristData.ts @@ -57,5 +57,7 @@ export interface TableRowType extends Record { lat: number /** The facility's longitude */ long: number + Kategorie: string + Typ: 'Beratung' | 'Klinik' | 'Amt' | 'Online' | 'Selbsthilfe' } } diff --git a/src/components/ActiveFiltersList.tsx b/src/components/ActiveFiltersList.tsx new file mode 100644 index 00000000..57cd0a42 --- /dev/null +++ b/src/components/ActiveFiltersList.tsx @@ -0,0 +1,59 @@ +import { useTexts } from '@lib/TextsContext' +import { useFiltersWithActiveProp } from '@lib/hooks/useFiltersWithActiveProp' +import React from 'react' +import { getCategoriesTexts } from './TextSearch' +import { + urlSearchCategoriesToStateSearchCategories, + useUrlState, +} from '@lib/UrlStateContext' +import { GristLabelType } from '@common/types/gristData' +import { Button } from './Button' + +function ActiveFiltersList(): JSX.Element | null { + const texts = useTexts() + const [urlState] = useUrlState() + const labels = useFiltersWithActiveProp() + const categoriesTexts = getCategoriesTexts(texts) + const categories = urlSearchCategoriesToStateSearchCategories( + urlState.qCategories + ) + + const categoryFilters = Object.entries(categories) + .filter(([, isActive]) => isActive) + .map(([key]) => ({ + id: key, + fields: { + text: categoriesTexts[key as keyof typeof categoriesTexts], + }, + })) + + const allFilters = [ + ...(urlState.q ? [{ id: 'search', fields: { text: urlState.q } }] : []), + ...categoryFilters, + ...labels.filter((label) => label.isActive), + ] as GristLabelType[] + + if (allFilters.length === 0) return null + + return ( +
+

{texts.resultPageIntro}

+
    + {allFilters.map((label) => ( + + ))} +
+
+ ) +} + +export default ActiveFiltersList diff --git a/src/components/Button.tsx b/src/components/Button.tsx index ad5b12db..11ff5d8b 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -1,5 +1,7 @@ +import { useUrlState } from '@lib/UrlStateContext' import classNames from '@lib/classNames' import Link from 'next/link' +import { useRouter } from 'next/router' import { ParsedUrlQueryInput } from 'querystring' import { FC, ReactNode } from 'react' @@ -8,7 +10,7 @@ interface ButtonType { scheme?: 'primary' | 'secondary' | 'link' size?: 'large' | 'medium' | 'small' | 'extrasmall' href?: string - query?: string | ParsedUrlQueryInput | null + query?: ParsedUrlQueryInput onClick?: () => void disabled?: boolean icon?: ReactNode @@ -45,7 +47,7 @@ export const Button: FC = ({ scheme = 'secondary', size = 'medium', href, - query, + query: additionalQuery = {}, onClick = () => undefined, className: additionalClassNames = '', disabled = false, @@ -53,6 +55,9 @@ export const Button: FC = ({ tooltip, children, }) => { + const [urlState] = useUrlState() + const { asPath } = useRouter() + const query = { ...urlState, ...additionalQuery, back: asPath.split('?')[0] } const SIZE_CLASSES = getSizeClasses(size) const SCHEME_CLASSES = getSchemeClasses(scheme) const LAYOUT_CLASSES = classNames( @@ -61,7 +66,7 @@ export const Button: FC = ({ ) const SHARED_CLASSES = classNames( - 'text-center', + 'text-center group', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-white', SIZE_CLASSES, SCHEME_CLASSES, @@ -120,9 +125,10 @@ export const Button: FC = ({ {tooltip && ( {tooltip} diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 31d4445a..c1b275b2 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -6,10 +6,12 @@ interface CardPropsType { header?: ReactNode footer?: ReactNode className?: string + id?: string } export const Card: FC = ({ className = '', + id = '', title, header: headerContent, footer, @@ -17,6 +19,7 @@ export const Card: FC = ({ }) => { return (
{ + id?: string + labelText?: string +} + +let idCounter = 0 + +function Checkbox({ + id = `Checkbox-${idCounter++}`, + labelText, + className, + ...props +}: CheckboxProps): JSX.Element { + const checkbox = ( + + ) + if (!labelText) return checkbox + return ( + + ) +} + +export default Checkbox diff --git a/src/components/FacilityInfo/index.tsx b/src/components/FacilityInfo/index.tsx index e4d9192f..6ac42d48 100644 --- a/src/components/FacilityInfo/index.tsx +++ b/src/components/FacilityInfo/index.tsx @@ -124,7 +124,12 @@ export const FacilityInfo: FC = ({ facility }) => { return ( <> - +

diff --git a/src/components/FacilityListItem.tsx b/src/components/FacilityListItem.tsx index 0985b086..083f8f73 100644 --- a/src/components/FacilityListItem.tsx +++ b/src/components/FacilityListItem.tsx @@ -33,6 +33,7 @@ export const FacilityListItem: FC = ({ return ( void }> = ({ recordsWithOnlyLabels, onSubmit = () => undefined }) => { const { push } = useRouter() @@ -35,6 +44,8 @@ export const FiltersList: FC<{ const filteredFacilitiesCount = useFilteredFacilitiesCount( recordsWithOnlyLabels ) + const { isLoading: textSearchLoading, total } = useActiveIdsBySearchTerm() + const fieldsDisabled = textSearchLoading const group1 = labels.filter(({ fields }) => fields.group2 === 'gruppe-1') const group2 = labels.filter(({ fields }) => fields.group2 === 'gruppe-2') @@ -48,21 +59,17 @@ export const FiltersList: FC<{ .map((label) => label.id) const [activeTargetGroupId, setActiveTargetGroupId] = useState( - queryTagIds.find((tagId) => { - return targetGroupIds.includes(tagId) - }) + queryTagIds.find((tagId) => targetGroupIds.includes(tagId)) || null ) + const tagsKey = queryTagIds.join('-') useEffect(() => { - if (queryTagIds.length === 0) return - const currentTargetGroupId = queryTagIds.find((tagId) => targetGroupIds.includes(tagId) ) - if (currentTargetGroupId) { - setActiveTargetGroupId(currentTargetGroupId) - } - }, [queryTagIds, targetGroupIds]) + setActiveTargetGroupId(currentTargetGroupId || null) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tagsKey, targetGroupIds]) const someGroupFiltersActive = labels .filter(({ fields }) => fields.group2 !== 'zielpublikum') @@ -72,35 +79,19 @@ export const FiltersList: FC<{ updateUrlState({ tags: newTags }) } - const getSubmitText = (): string => { - switch (true) { - case queryTagIds.length === 0 || queryTagIds.length === labels.length: - return texts.filtersButtonTextAllFilters - case queryTagIds.length > 0 && filteredFacilitiesCount === 1: - return texts.filtersButtonTextFilteredSingular - case queryTagIds.length > 0 && filteredFacilitiesCount > 1: - return texts.filtersButtonTextFilteredPlural.replace( - '#number', - `${filteredFacilitiesCount}` - ) - case queryTagIds.length > 0 && filteredFacilitiesCount === 0: - return texts.filtersButtonTextFilteredNoResults - default: - return '' - } - } - return ( -
-
+
+
{someGroupFiltersActive && ( )}
-
-
+

{texts.optionalFurtherSearchIntroText}

+
+ + updateUrlState({ + ...urlState, + q: text, + qCategories: + stateSearchCategoriesToUrlSearchCategories(categories), + }) + } + text={urlState.q || ''} + categories={urlSearchCategoriesToStateSearchCategories( + urlState.qCategories + )} + disabled={fieldsDisabled} + /> +
{ if (!a.fields.order) return 1 @@ -158,14 +170,14 @@ export const FiltersList: FC<{ break case targetGroupAlreadyInUrl && !hasValidTargetGroup: updateFilters([...tagsWithoutOldTargetGroup]) - setActiveTargetGroupId(undefined) + setActiveTargetGroupId(null) break case !targetGroupAlreadyInUrl && hasValidTargetGroup: updateFilters([...queryTagIds, selectedValue as number]) break case !targetGroupAlreadyInUrl && !hasValidTargetGroup: updateFilters([...queryTagIds]) - setActiveTargetGroupId(undefined) + setActiveTargetGroupId(null) break default: break @@ -174,46 +186,73 @@ export const FiltersList: FC<{ className="mb-12" />
- - {texts.filtersGeoSearchLabel} - -
+ + + {texts.filtersGeoSearchLabel} + + -
+ ) + } + disabled={filteredFacilitiesCount === 0 || fieldsDisabled} + tooltip={ + filteredFacilitiesCount === 0 && ( + {texts.filtersButtonTextFilteredNoResultsHint} + ) + } + > + {getSubmitText({ + texts, + isLoading: textSearchLoading, + count: filteredFacilitiesCount, + total, + })} +
) } + +function getSubmitText({ + texts, + isLoading = false, + count, + total, +}: { + texts: TextsMapType + isLoading?: boolean + count: number + total: number +}): string { + if (isLoading) return texts.filtersButtonLoading + if (count === 0) return texts.filtersButtonTextFilteredNoResults + if (count === 1) return texts.filtersButtonTextFilteredSingular + if (count === total) return texts.filtersButtonTextAllFilters + return texts.filtersButtonTextFilteredPlural.replace('#number', `${count}`) +} diff --git a/src/components/FiltersTagsList.tsx b/src/components/FiltersTagsList.tsx index 4ebbdb4c..4ec9fe0f 100644 --- a/src/components/FiltersTagsList.tsx +++ b/src/components/FiltersTagsList.tsx @@ -7,12 +7,14 @@ interface FiltersTagsListPropType { onLabelClick?: (activeFilters: number[]) => void filters: FiltersWithActivePropType[] className?: string + disabled?: boolean } export const FiltersTagsList: FC = ({ onLabelClick, filters, className, + disabled = false, }) => { const [urlState] = useUrlState() const activeFilters = urlState.tags || [] @@ -21,6 +23,7 @@ export const FiltersTagsList: FC = ({ <> {filters.map((filter) => (

r.labels)} + recordsWithOnlyLabels={(records || []).map((r) => [ + r.id, + r.labels, + ])} onSubmit={() => setFilterSidebarIsOpened(false)} />
diff --git a/src/components/TextInput.tsx b/src/components/TextInput.tsx new file mode 100644 index 00000000..279a8dd3 --- /dev/null +++ b/src/components/TextInput.tsx @@ -0,0 +1,42 @@ +import classNames from '@lib/classNames' +import React, { forwardRef } from 'react' + +type TextInputPropsType = React.InputHTMLAttributes & { + labelText?: string + id?: string +} + +let idCounter = 0 + +const InputText = forwardRef( + ( + { className, id = `text-input-${idCounter++}`, labelText, ...props }, + ref + ) => ( + <> + {labelText && ( + + )} + + + ) +) +InputText.displayName = 'InputText' + +export default InputText diff --git a/src/components/TextSearch.tsx b/src/components/TextSearch.tsx new file mode 100644 index 00000000..263cc88e --- /dev/null +++ b/src/components/TextSearch.tsx @@ -0,0 +1,159 @@ +import { TextsMapType, useTexts } from '@lib/TextsContext' +import React, { useCallback, useEffect, useRef, useState } from 'react' +import TextInput from './TextInput' +import Checkbox from './Checkbox' +import classNames from '@lib/classNames' +import { Check } from './icons/Check' +import { Cross } from './icons/Cross' + +type CategoriesType = Partial<{ + categorySelfHelp: boolean + categoryAdvising: boolean + categoryClinics: boolean + categoryOnlineOffers: boolean + categoryDistrictOfficeHelp: boolean +}> +interface StateType { + text: string + categories: CategoriesType +} + +interface TextSearchProps extends StateType { + onChange: (state: Partial) => void + disabled?: boolean +} + +type CategoriesTextMapType = Record + +function TextSearch({ + text: initialText, + categories, + onChange, + disabled = false, +}: TextSearchProps): JSX.Element { + const texts = useTexts() + const [text, setText] = useState(initialText || '') + const [hasFocus, setHasFocus] = useState(false) + const inputRef = useRef(null) + const showSubmit = hasFocus && text !== initialText + + const setFocusTrue = useCallback(() => setHasFocus(true), []) + const setFocusFalse = useCallback(() => setHasFocus(false), []) + + useEffect(() => { + if (!inputRef.current) return + const input = inputRef.current + inputRef.current.addEventListener('focus', setFocusTrue) + inputRef.current.addEventListener('blur', setFocusFalse) + return () => { + input.removeEventListener('focus', setFocusTrue) + input.removeEventListener('blur', setFocusFalse) + } + }, [setFocusTrue, setFocusFalse]) + + useEffect(() => { + setText(initialText || '') + }, [initialText]) + + const categoriesTexts = getCategoriesTexts(texts) + const checkboxes = Object.keys(categoriesTexts).map((categoryKey) => ({ + id: categoryKey, + labelText: categoriesTexts[categoryKey as keyof CategoriesTextMapType], + })) + + return ( +
+
+ setText(evt.target.value)} + onBlur={() => onChange({ categories, text })} + onKeyDown={(evt) => { + if (evt.key === 'Enter') { + onChange({ categories, text }) + inputRef.current?.blur() + } + }} + value={text} + disabled={disabled} + /> + + +
+ {checkboxes.map(({ id, labelText }) => ( + { + onChange({ + text, + categories: { + ...categories, + [id]: evt.target.checked, + }, + }) + }} + checked={!!categories[id as keyof CategoriesType]} + /> + ))} +
+ ) +} + +export const getCategoriesTexts = ( + texts: TextsMapType +): CategoriesTextMapType => { + return { + categorySelfHelp: texts.textSearchCategorySelfHelp, + categoryAdvising: texts.textSearchCategoryAdvising, + categoryClinics: texts.textSearchCategoryClinics, + categoryOnlineOffers: texts.textSearchCategoryOnlineOffers, + categoryDistrictOfficeHelp: texts.textSearchCategoryDistrictOfficeHelp, + } +} + +export default TextSearch diff --git a/src/components/WelcomeFilters.tsx b/src/components/WelcomeFilters.tsx index b1419efa..5f4b0fb1 100644 --- a/src/components/WelcomeFilters.tsx +++ b/src/components/WelcomeFilters.tsx @@ -1,17 +1,17 @@ import { useTexts } from '@lib/TextsContext' import { FC } from 'react' import { BackButton } from './BackButton' -import { TableRowType } from '@common/types/gristData' import { FiltersList } from './FiltersList' import { useIsMobile } from '@lib/hooks/useIsMobile' import { LegalFooter } from './LegalFooter' import classNames from '@lib/classNames' import { TextLink } from './TextLink' import { Footer } from './Footer' +import { RecordsWithOnlyLabelsType } from '@lib/hooks/useFilteredFacilitiesCount' export const WelcomeFilters: FC<{ onGoBack: () => void - recordsWithOnlyLabels: TableRowType['fields']['Schlagworte'][] + recordsWithOnlyLabels: RecordsWithOnlyLabelsType[] }> = ({ onGoBack, recordsWithOnlyLabels }) => { const texts = useTexts() const isMobile = useIsMobile() diff --git a/src/components/WelcomeScreen.tsx b/src/components/WelcomeScreen.tsx index af361c28..c30da886 100644 --- a/src/components/WelcomeScreen.tsx +++ b/src/components/WelcomeScreen.tsx @@ -15,7 +15,7 @@ export const WelcomeScreen: FC<{ onShowOffers: () => void }> = ({ onShowOffers }) => { const texts = useTexts() - const { push } = useRouter() + const { push, query } = useRouter() const isMobile = useIsMobile() return (
} - onClick={() => void push(`/sofortige-hilfe`)} + onClick={() => + void push({ + pathname: '/sofortige-hilfe', + query, + }) + } > {texts.directHelpButtonText} diff --git a/src/components/icons/Spinner.tsx b/src/components/icons/Spinner.tsx new file mode 100644 index 00000000..d21deed2 --- /dev/null +++ b/src/components/icons/Spinner.tsx @@ -0,0 +1,27 @@ +import { FC, SVGAttributes } from 'react' + +export const Spinner: FC<{ + className?: SVGAttributes['className'] +}> = ({ className = '' }) => ( + + + + + + + + + + +) diff --git a/src/lib/TextsContext.tsx b/src/lib/TextsContext.tsx index 982ccf7c..3b01f905 100644 --- a/src/lib/TextsContext.tsx +++ b/src/lib/TextsContext.tsx @@ -118,6 +118,15 @@ const defaultValue = { disclaimerContent: '', moreInfos: '', noTargetPreferenceButtonText: '', + textSearchLabel: '', + textSearchPlaceholder: '', + textSearchCategorySelfHelp: '', + textSearchCategoryAdvising: '', + textSearchCategoryClinics: '', + textSearchCategoryOnlineOffers: '', + textSearchCategoryDistrictOfficeHelp: '', + optionalFurtherSearchIntroText: '', + filtersButtonLoading: '', } export type TextsMapType = typeof defaultValue diff --git a/src/lib/UrlStateContext.tsx b/src/lib/UrlStateContext.tsx index 37a03d82..e1b04829 100644 --- a/src/lib/UrlStateContext.tsx +++ b/src/lib/UrlStateContext.tsx @@ -8,77 +8,186 @@ import { useState, } from 'react' import { mapRawQueryToState, PageQueryType } from './mapRawQueryToState' -import { removeNullAndUndefinedFromQuery } from './removeNullAndUndefinedFromQuery' +import { truncateSearchTerm } from './facilityFilterUtil' +import { removeFalsyFromQuery } from './removeFalsyFromQuery' -type UrlStateType = PageQueryType -type SetUrlStateHandlerType = (newState: Partial) => void +type ParsedSearchTermCategoriesType = { + categorySelfHelp: boolean + categoryAdvising: boolean + categoryClinics: boolean + categoryDistrictOfficeHelp: boolean + categoryOnlineOffers: boolean +} + +type SetUrlStateHandlerType = (newState: PageQueryType) => void -const UrlStateContext = createContext<[UrlStateType, SetUrlStateHandlerType]>([ +const UrlStateContext = createContext<[PageQueryType, SetUrlStateHandlerType]>([ {}, () => undefined, ]) const Provider = UrlStateContext.Provider -export const useUrlState = (): [UrlStateType, SetUrlStateHandlerType] => - useContext(UrlStateContext) as [UrlStateType, SetUrlStateHandlerType] +export const useUrlState = (): [PageQueryType, SetUrlStateHandlerType] => + useContext(UrlStateContext) as [PageQueryType, SetUrlStateHandlerType] export const UrlStateProvider: FC = ({ children }) => { const { query, pathname } = useRouter() const mappedQuery = mapRawQueryToState(query) - const [latitude, setLatitude] = useState( - mappedQuery.latitude - ) - const [longitude, setLongitude] = useState( - mappedQuery.longitude - ) - const [zoom, setZoom] = useState(mappedQuery.zoom) - const [tags, setTags] = useState(mappedQuery.tags) + const [latitude, setLatitude] = useState(mappedQuery.latitude) + const [longitude, setLongitude] = useState(mappedQuery.longitude) + const [zoom, setZoom] = useState(mappedQuery.zoom) + const [tags, setTags] = useState(mappedQuery.tags) + const [back, setBack] = useState(mappedQuery.back) + const [q, setQ] = useState(mappedQuery.q) + const [qCategories, setQCategories] = useState(mappedQuery.qCategories) - useEffect(() => { - setLatitude(mappedQuery.latitude) - setLongitude(mappedQuery.longitude) - setZoom(mappedQuery.zoom) - setTags(mappedQuery.tags) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [query]) + // GET A COMBINED STATE OBJECT WITH APPLIED DEFAULTS + const state = getFormattedUrlStateWithDefaults({ + latitude, + longitude, + zoom, + tags, + back, + q, + qCategories, + }) + // UTILITY FUNCTION TO UPDATE ALL REACT STATES + const updateReactUrlState = useCallback( + (state: PageQueryType) => { + setLatitude(state.latitude) + setLongitude(state.longitude) + setZoom(state.zoom) + setTags(state.tags) + setBack(state.back) + setQ(state.q) + setQCategories(state.qCategories) + }, + [setLatitude, setLongitude, setZoom, setTags, setBack, setQ, setQCategories] + ) + + // UTILITY FUNCTION TO UPDATE URL STATE (Passed down the context as a setter) const updateUrlState = useCallback( - (newState: Partial) => { - const paramsString = new URLSearchParams({ - ...removeNullAndUndefinedFromQuery({ - latitude, - longitude, - zoom, - tags, - ...newState, - }), - } as Record).toString() + (newState: PageQueryType) => { const path = typeof query.id === 'string' ? `/${query.id}` : pathname - const as = `${path}?${paramsString}` - window.history.replaceState( - { - ...window.history.state, - ...newState, - as, - url: pathname, - }, - '', - as - ) - if (newState.latitude) setLatitude(newState.latitude) - if (newState.longitude) setLongitude(newState.longitude) - if (newState.zoom) setZoom(newState.zoom) - if (newState.tags) setTags(newState.tags) + + const fullNewState = removeFalsyFromQuery({ + ...state, + ...newState, + }) + fullNewState.q = truncateSearchTerm(fullNewState.q) + + updateStateWindowLocation(path, fullNewState) + updateReactUrlState(fullNewState) }, - [latitude, longitude, pathname, tags, zoom, query.id] + [query.id, pathname, state, updateReactUrlState] + ) + + // UPDATE REACT STATE ON NEXTJS ROUTER QUERY CHANGE + useEffect( + () => updateReactUrlState(mappedQuery), + // eslint-disable-next-line react-hooks/exhaustive-deps + [query] ) - const state = { + // UPDATE URL WITH DEFAULTS ON INITIAL LOAD AND PAGE CHANGE + useEffect(() => { + if (typeof window === 'undefined') return + + const urlStateFromWindowLocation = getUrlStateFromWindowLocation() + const allCatergoriesAreUnselected = + !urlStateFromWindowLocation.qCategories?.length + + if (allCatergoriesAreUnselected) { + updateUrlState({ + ...urlStateFromWindowLocation, + qCategories: stateSearchCategoriesToUrlSearchCategories({ + categorySelfHelp: true, + categoryAdvising: true, + }), + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [query.id, pathname]) + + return {children} +} + +export function urlSearchCategoriesToStateSearchCategories( + qCategories: PageQueryType['qCategories'] +): Partial { + return { + categorySelfHelp: !!qCategories?.includes(1), + categoryAdvising: !!qCategories?.includes(2), + categoryClinics: !!qCategories?.includes(3), + categoryDistrictOfficeHelp: !!qCategories?.includes(4), + categoryOnlineOffers: !!qCategories?.includes(5), + } +} + +export function stateSearchCategoriesToUrlSearchCategories( + searchTermCategories: Partial | undefined +): PageQueryType['qCategories'] { + if (typeof searchTermCategories === 'undefined') return undefined + if (Object.entries(searchTermCategories).length === 0) return [] + return [ + searchTermCategories.categorySelfHelp && 1, + searchTermCategories.categoryAdvising && 2, + searchTermCategories.categoryClinics && 3, + searchTermCategories.categoryDistrictOfficeHelp && 4, + searchTermCategories.categoryOnlineOffers && 5, + ].filter(Boolean) as PageQueryType['qCategories'] +} + +function getUrlStateFromWindowLocation(): PageQueryType { + const urlParams = new URLSearchParams(window.location.search) + return mapRawQueryToState({ + latitude: urlParams.get('latitude') || undefined, + longitude: urlParams.get('longitude') || undefined, + zoom: urlParams.get('zoom') || undefined, + tags: urlParams.getAll('tags')?.join(',') || undefined, + back: urlParams.get('back') || undefined, + q: urlParams.get('q') || undefined, + qCategories: urlParams.getAll('qCategories')?.join(',') || undefined, + }) +} + +function getFormattedUrlStateWithDefaults({ + latitude, + longitude, + zoom, + tags, + back, + q, + qCategories, +}: PageQueryType): PageQueryType { + return { latitude, longitude, zoom, tags: tags || [], + back, + q: q || '', + qCategories: qCategories || [], } - return {children} +} + +function updateStateWindowLocation(path: string, state: PageQueryType): void { + const paramsString = new URLSearchParams( + state as Record + ).toString() + + const as = [path, paramsString].filter(Boolean).join('?') + + window.history.replaceState( + { + ...window.history.state, + ...state, + as, + url: as, + }, + '', + as + ) } diff --git a/src/lib/facilityFilterUtil.ts b/src/lib/facilityFilterUtil.ts index cae3df50..49013e9f 100644 --- a/src/lib/facilityFilterUtil.ts +++ b/src/lib/facilityFilterUtil.ts @@ -4,38 +4,100 @@ import { MinimalRecordType } from './mapRecordToMinimum' interface GetFilteredFacilitiesPropType { facilities: MinimalRecordType[] labels: GristLabelType[] + activeIdsBySearchTerm: MinimalRecordType['id'][] | null } -interface IsFaclilityActivePropType { +interface isFacilityActivePropType { activeTargetLabels: GristLabelType[] - activeTopcisLabels: GristLabelType[] + activeTopicsLabels: GristLabelType[] facilityLabels: GristLabelType['id'][] - isFilteredByTarget: boolean + facilityId: MinimalRecordType['id'] + activeIdsBySearchTerm: MinimalRecordType['id'][] | null +} + +type GetFilterStatusType = (props: { + activeIdsBySearchTerm: MinimalRecordType['id'][] | null + activeTargetLabels: GristLabelType[] + activeTopicsLabels: GristLabelType[] +}) => { isFilteredByTopic: boolean + isFilteredByTarget: boolean + isFilteredBySearchTerm: boolean + isNotFilteredAtAll: boolean } -export const isFaclilityActive = ({ +export const getFilterStatus: GetFilterStatusType = ({ + activeIdsBySearchTerm, activeTargetLabels, - activeTopcisLabels, - isFilteredByTarget, - isFilteredByTopic, + activeTopicsLabels, +}) => { + const isFilteredBySearchTerm = activeIdsBySearchTerm !== null + const isFilteredByTopic = activeTopicsLabels.length > 0 + const isFilteredByTarget = activeTargetLabels.length > 0 + return { + isFilteredByTopic, + isFilteredByTarget, + isFilteredBySearchTerm, + isNotFilteredAtAll: + !isFilteredByTopic && !isFilteredByTarget && !isFilteredBySearchTerm, + } +} + +const MAX_TEXT_SEARCH_STRING_LENGTH = 100 +export function truncateSearchTerm(searchTerm: unknown): string { + if (typeof searchTerm !== 'string') return '' + return searchTerm.slice(0, MAX_TEXT_SEARCH_STRING_LENGTH) +} + +export const isFacilityActive = ({ + activeTargetLabels, + activeTopicsLabels, facilityLabels, -}: IsFaclilityActivePropType): boolean => { - if (!isFilteredByTopic && !isFilteredByTarget) return true + facilityId, + activeIdsBySearchTerm, +}: isFacilityActivePropType): boolean => { + const { + isNotFilteredAtAll, + isFilteredByTopic, + isFilteredByTarget, + isFilteredBySearchTerm, + } = getFilterStatus({ + activeIdsBySearchTerm, + activeTargetLabels, + activeTopicsLabels, + }) + if (isNotFilteredAtAll) return true const hasAllOfTheActiveTopics = - activeTopcisLabels?.every((tag) => facilityLabels.includes(tag.id)) || false + activeTopicsLabels?.every((tag) => facilityLabels.includes(tag.id)) || false const hasAnActiveTarget = (activeTargetLabels[0] && facilityLabels.includes(activeTargetLabels[0].id)) || false + const includesTextSearchTerm = + activeIdsBySearchTerm !== null && activeIdsBySearchTerm.includes(facilityId) + + if (!includesTextSearchTerm) return false - if (!isFilteredByTopic && isFilteredByTarget) return hasAnActiveTarget - if (isFilteredByTopic && !isFilteredByTarget) return hasAllOfTheActiveTopics - return hasAllOfTheActiveTopics && hasAnActiveTarget + if (!isFilteredByTopic && !isFilteredBySearchTerm && isFilteredByTarget) + return hasAnActiveTarget + if (isFilteredByTopic && !isFilteredBySearchTerm && !isFilteredByTarget) + return hasAllOfTheActiveTopics + if (isFilteredByTopic && isFilteredBySearchTerm && !isFilteredByTarget) + return hasAllOfTheActiveTopics && includesTextSearchTerm + if (!isFilteredByTopic && isFilteredBySearchTerm && isFilteredByTarget) + return hasAnActiveTarget && includesTextSearchTerm + if (!isFilteredByTopic && isFilteredBySearchTerm && !isFilteredByTarget) + return includesTextSearchTerm + return hasAllOfTheActiveTopics && hasAnActiveTarget && includesTextSearchTerm } -export const getActiveLabelGroups = (labels: GristLabelType[]) => ({ - activeTopcisLabels: labels.filter( +export const getActiveLabelGroups = ( + labels: GristLabelType[] +): { + activeTopicsLabels: GristLabelType[] + activeTargetLabels: GristLabelType[] +} => ({ + activeTopicsLabels: labels.filter( (f) => f.isActive && f.fields.group2 !== 'zielpublikum' ), activeTargetLabels: labels.filter( @@ -46,19 +108,23 @@ export const getActiveLabelGroups = (labels: GristLabelType[]) => ({ export const getFilteredFacilities = ({ facilities, labels, + activeIdsBySearchTerm, }: GetFilteredFacilitiesPropType): MinimalRecordType[] => { - const { activeTopcisLabels, activeTargetLabels } = + const { activeTopicsLabels, activeTargetLabels } = getActiveLabelGroups(labels) - const isFilteredByTopic = activeTopcisLabels.length > 0 - const isFilteredByTarget = activeTargetLabels.length > 0 - if (!isFilteredByTopic && !isFilteredByTarget) return facilities + const { isNotFilteredAtAll } = getFilterStatus({ + activeIdsBySearchTerm, + activeTargetLabels, + activeTopicsLabels, + }) + if (isNotFilteredAtAll) return facilities return facilities.filter((facility) => { - return isFaclilityActive({ + return isFacilityActive({ facilityLabels: facility.labels, - activeTopcisLabels, + facilityId: facility.id, + activeTopicsLabels, activeTargetLabels, - isFilteredByTopic, - isFilteredByTarget, + activeIdsBySearchTerm, }) }) } diff --git a/src/lib/hooks/useActiveIdsBySearchTerm.ts b/src/lib/hooks/useActiveIdsBySearchTerm.ts new file mode 100644 index 00000000..79c3845e --- /dev/null +++ b/src/lib/hooks/useActiveIdsBySearchTerm.ts @@ -0,0 +1,88 @@ +import { TableRowType } from '@common/types/gristData' +import { + urlSearchCategoriesToStateSearchCategories, + useUrlState, +} from '@lib/UrlStateContext' +import { PageQueryType } from '@lib/mapRawQueryToState' +import { MinimalRecordType } from '@lib/mapRecordToMinimum' +import { useEffect, useState } from 'react' +import useSWR from 'swr' + +interface SearchResultBase { + params: Record +} +interface SearchResultSuccess extends SearchResultBase { + result: TableRowType[] + total: number +} + +interface SearchResultError extends SearchResultBase { + error: string +} + +export type SearchResult = SearchResultSuccess | SearchResultError + +interface UseActiveIdsBySearchTermReturnType { + ids: MinimalRecordType['id'][] | null + total: number + isLoading: boolean + key: string +} + +export const useActiveIdsBySearchTerm = + (): UseActiveIdsBySearchTermReturnType => { + const [urlState] = useUrlState() + const [ids, setIds] = useState(null) + const [total, setTotal] = useState(10000) + const q = urlState.q ?? null + const qCategories = urlState.qCategories ?? null + const key = `search-${q || 'empty'}-${qCategories?.join('-') || 'empty'}` + const { data, isLoading } = useSWR(key, async () => + searchRecords(q, qCategories, total) + ) + + useEffect(() => { + if (isLoading || !data || 'error' in data) return + setIds(data.result?.map((r) => r.id) || []) + setTotal(data.total) + }, [data, isLoading]) + + return { + ids, + isLoading, + total, + key: ids?.join('-') || 'empty', + } + } + +async function searchRecords( + q: string | null, + qCategories: PageQueryType['qCategories'] | null, + total: number +): Promise { + if (q === null && qCategories === null) + return { params: {}, result: [], total } + const url = new URLSearchParams({ + query: q || '', + filters: categoriesToFilterString(qCategories), + }) + const response = await fetch(`/api/search?${url.toString()}`) + if (!response.ok) throw new Error(await response.text()) + return (await response.json()) as SearchResult +} + +function categoriesToFilterString( + categories: PageQueryType['qCategories'] | null +): string { + if (categories === null) return '' + const categoriesState = urlSearchCategoriesToStateSearchCategories(categories) + return [ + categoriesState.categoryAdvising && 'Beratung', + categoriesState.categoryClinics && 'Klinik', + categoriesState.categoryDistrictOfficeHelp && 'Amt', + categoriesState.categoryOnlineOffers && 'Online', + categoriesState.categorySelfHelp && 'Selbsthilfe', + ] + .filter(Boolean) + .join(',') +} diff --git a/src/lib/hooks/useFilteredFacilitiesCount.ts b/src/lib/hooks/useFilteredFacilitiesCount.ts index f61a35bc..69529cc8 100644 --- a/src/lib/hooks/useFilteredFacilitiesCount.ts +++ b/src/lib/hooks/useFilteredFacilitiesCount.ts @@ -1,47 +1,65 @@ import { TableRowType } from '@common/types/gristData' import { getActiveLabelGroups, - isFaclilityActive, + getFilterStatus, + isFacilityActive, } from '@lib/facilityFilterUtil' import { useEffect, useState } from 'react' import { useFiltersWithActiveProp } from './useFiltersWithActiveProp' +import { useActiveIdsBySearchTerm } from './useActiveIdsBySearchTerm' + +export type RecordsWithOnlyLabelsType = [ + id: TableRowType['id'], + labels: TableRowType['fields']['Schlagworte'] +] export const useFilteredFacilitiesCount = ( - facilitiesWithOnlyLabels: TableRowType['fields']['Schlagworte'][] + facilitiesWithOnlyLabels: RecordsWithOnlyLabelsType[] ): number => { const labels = useFiltersWithActiveProp() const concatenatedActiveLabelIds = labels .filter((label) => label.isActive) .map((label) => label.id) .join('-') + const activeIdsBySearchTerm = useActiveIdsBySearchTerm() const [filteredFacilities, setFilteredFacilities] = useState( facilitiesWithOnlyLabels ) useEffect(() => { - const { activeTopcisLabels, activeTargetLabels } = + const { activeTopicsLabels, activeTargetLabels } = getActiveLabelGroups(labels) - const isFilteredByTopic = activeTopcisLabels.length > 0 - const isFilteredByTarget = activeTargetLabels.length > 0 + const { isNotFilteredAtAll } = getFilterStatus({ + activeIdsBySearchTerm: activeIdsBySearchTerm.ids, + activeTargetLabels, + activeTopicsLabels, + }) + + if (isNotFilteredAtAll) { + setFilteredFacilities([]) + return + } const filteredFacilities = facilitiesWithOnlyLabels.filter( - (recordLabels) => { - if (!isFilteredByTopic && !isFilteredByTarget) return true - return isFaclilityActive({ - isFilteredByTopic, - isFilteredByTarget, + ([id, recordLabels]) => + isFacilityActive({ facilityLabels: recordLabels, + facilityId: id, activeTargetLabels: activeTargetLabels, - activeTopcisLabels: activeTopcisLabels, + activeTopicsLabels: activeTopicsLabels, + activeIdsBySearchTerm: activeIdsBySearchTerm.ids, }) - } ) setFilteredFacilities(filteredFacilities) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [concatenatedActiveLabelIds, facilitiesWithOnlyLabels]) + }, [ + concatenatedActiveLabelIds, + facilitiesWithOnlyLabels, + activeIdsBySearchTerm.key, + ]) return filteredFacilities.length } diff --git a/src/lib/mapRawQueryToState/index.ts b/src/lib/mapRawQueryToState/index.ts index bdea0b39..545fa1e9 100644 --- a/src/lib/mapRawQueryToState/index.ts +++ b/src/lib/mapRawQueryToState/index.ts @@ -1,10 +1,15 @@ -import { removeNullAndUndefinedFromQuery } from '@lib/removeNullAndUndefinedFromQuery' +import { truncateSearchTerm } from '@lib/facilityFilterUtil' +import { removeFalsyFromQuery } from '@lib/removeFalsyFromQuery' +type qCategoryType = 1 | 2 | 3 | 4 | 5 export interface PageQueryType { latitude?: number longitude?: number zoom?: number tags?: number[] + q?: string + qCategories?: qCategoryType[] + back?: string } const isNumber = (val: unknown): boolean => @@ -48,9 +53,11 @@ const parseNumbersArray = ( export const mapRawQueryToState = ( rawQuery: Record ): PageQueryType => - removeNullAndUndefinedFromQuery({ + removeFalsyFromQuery({ latitude: parseSingleNumber(rawQuery.latitude), longitude: parseSingleNumber(rawQuery.longitude), zoom: parseSingleNumber(rawQuery.zoom), tags: parseNumbersArray(rawQuery.tags), + q: truncateSearchTerm(rawQuery.q), + qCategories: parseNumbersArray(rawQuery.qCategories) as qCategoryType[], }) diff --git a/src/lib/removeFalsyFromQuery.ts b/src/lib/removeFalsyFromQuery.ts new file mode 100644 index 00000000..e69e1274 --- /dev/null +++ b/src/lib/removeFalsyFromQuery.ts @@ -0,0 +1,12 @@ +import { PageQueryType } from './mapRawQueryToState' + +export const removeFalsyFromQuery = ( + obj: Record +): PageQueryType => { + Object.keys(obj).forEach((k) => { + const value = obj[k] + const isEmptyArray = Array.isArray(value) && value.length === 0 + if (!value || isEmptyArray) delete obj[k] + }) + return obj +} diff --git a/src/lib/removeNullAndUndefinedFromQuery.ts b/src/lib/removeNullAndUndefinedFromQuery.ts deleted file mode 100644 index 7c33008f..00000000 --- a/src/lib/removeNullAndUndefinedFromQuery.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { PageQueryType } from './mapRawQueryToState' - -export const removeNullAndUndefinedFromQuery = ( - obj: Record -): PageQueryType => { - Object.keys(obj).forEach( - (k) => (obj[k] === null || typeof obj[k] === 'undefined') && delete obj[k] - ) - return obj -} diff --git a/tailwind.config.js b/tailwind.config.js index f9d647d4..6ab71e7a 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -67,5 +67,6 @@ module.exports = { plugins: [ require('@tailwindcss/line-clamp'), require('@tailwindcss/typography'), + require('@tailwindcss/container-queries'), ], }