From 2b5ada8afae0292793cb6fa9b1fca5ed7aeb0178 Mon Sep 17 00:00:00 2001 From: "MTG\\mtg09" Date: Tue, 13 Dec 2022 11:45:31 +0400 Subject: [PATCH 1/6] feat: search feature --- package-lock.json | 557 ++++++++++++++++++ package.json | 2 + src/Components/Navbar/NavDesktop.tsx | 153 +++-- src/Components/Navbar/Search.tsx | 90 +++ .../pages/ExplorePage/ExplorePage.tsx | 406 +++++++------ .../ExplorePage/ProjectsGrid/ProjectsGrid.tsx | 88 +-- src/utils/Wrapper.tsx | 113 ++-- 7 files changed, 1091 insertions(+), 318 deletions(-) create mode 100644 src/Components/Navbar/Search.tsx diff --git a/package-lock.json b/package-lock.json index 0f30f95..f76db63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "@types/react": "^18.0.5", "@types/react-dom": "^18.0.1", "@use-gesture/react": "^10.2.11", + "algoliasearch": "^4.14.2", "apollo-server": "^3.6.7", "apollo-server-lambda": "^3.6.7", "axios": "^0.26.1", @@ -82,6 +83,7 @@ "react-helmet": "^6.1.0", "react-hook-form": "^7.30.0", "react-icons": "^4.3.1", + "react-instantsearch-hooks-web": "^6.38.1", "react-loader-spinner": "^6.0.0-0", "react-loading-skeleton": "^3.1.0", "react-modal": "^3.15.1", @@ -148,6 +150,140 @@ "webpack": "^5.72.0" } }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.14.2.tgz", + "integrity": "sha512-FRweBkK/ywO+GKYfAWbrepewQsPTIEirhi1BdykX9mxvBPtGNKccYAxvGdDCumU1jL4r3cayio4psfzKMejBlA==", + "dependencies": { + "@algolia/cache-common": "4.14.2" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.14.2.tgz", + "integrity": "sha512-SbvAlG9VqNanCErr44q6lEKD2qoK4XtFNx9Qn8FK26ePCI8I9yU7pYB+eM/cZdS9SzQCRJBbHUumVr4bsQ4uxg==" + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.14.2.tgz", + "integrity": "sha512-HrOukWoop9XB/VFojPv1R5SVXowgI56T9pmezd/djh2JnVN/vXswhXV51RKy4nCpqxyHt/aGFSq2qkDvj6KiuQ==", + "dependencies": { + "@algolia/cache-common": "4.14.2" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.14.2.tgz", + "integrity": "sha512-WHtriQqGyibbb/Rx71YY43T0cXqyelEU0lB2QMBRXvD2X0iyeGl4qMxocgEIcbHyK7uqE7hKgjT8aBrHqhgc1w==", + "dependencies": { + "@algolia/client-common": "4.14.2", + "@algolia/client-search": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.14.2.tgz", + "integrity": "sha512-yBvBv2mw+HX5a+aeR0dkvUbFZsiC4FKSnfqk9rrfX+QrlNOKEhCG0tJzjiOggRW4EcNqRmaTULIYvIzQVL2KYQ==", + "dependencies": { + "@algolia/client-common": "4.14.2", + "@algolia/client-search": "4.14.2", + "@algolia/requester-common": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "node_modules/@algolia/client-common": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.14.2.tgz", + "integrity": "sha512-43o4fslNLcktgtDMVaT5XwlzsDPzlqvqesRi4MjQz2x4/Sxm7zYg5LRYFol1BIhG6EwxKvSUq8HcC/KxJu3J0Q==", + "dependencies": { + "@algolia/requester-common": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.14.2.tgz", + "integrity": "sha512-ACCoLi0cL8CBZ1W/2juehSltrw2iqsQBnfiu/Rbl9W2yE6o2ZUb97+sqN/jBqYNQBS+o0ekTMKNkQjHHAcEXNw==", + "dependencies": { + "@algolia/client-common": "4.14.2", + "@algolia/requester-common": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "node_modules/@algolia/client-search": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.14.2.tgz", + "integrity": "sha512-L5zScdOmcZ6NGiVbLKTvP02UbxZ0njd5Vq9nJAmPFtjffUSOGEp11BmD2oMJ5QvARgx2XbX4KzTTNS5ECYIMWw==", + "dependencies": { + "@algolia/client-common": "4.14.2", + "@algolia/requester-common": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "node_modules/@algolia/events": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" + }, + "node_modules/@algolia/logger-common": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.14.2.tgz", + "integrity": "sha512-/JGlYvdV++IcMHBnVFsqEisTiOeEr6cUJtpjz8zc0A9c31JrtLm318Njc72p14Pnkw3A/5lHHh+QxpJ6WFTmsA==" + }, + "node_modules/@algolia/logger-console": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.14.2.tgz", + "integrity": "sha512-8S2PlpdshbkwlLCSAB5f8c91xyc84VM9Ar9EdfE9UmX+NrKNYnWR1maXXVDQQoto07G1Ol/tYFnFVhUZq0xV/g==", + "dependencies": { + "@algolia/logger-common": "4.14.2" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.14.2.tgz", + "integrity": "sha512-CEh//xYz/WfxHFh7pcMjQNWgpl4wFB85lUMRyVwaDPibNzQRVcV33YS+63fShFWc2+42YEipFGH2iPzlpszmDw==", + "dependencies": { + "@algolia/requester-common": "4.14.2" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.14.2.tgz", + "integrity": "sha512-73YQsBOKa5fvVV3My7iZHu1sUqmjjfs9TteFWwPwDmnad7T0VTCopttcsM3OjLxZFtBnX61Xxl2T2gmG2O4ehg==" + }, + "node_modules/@algolia/requester-node-http": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.14.2.tgz", + "integrity": "sha512-oDbb02kd1o5GTEld4pETlPZLY0e+gOSWjWMJHWTgDXbv9rm/o2cF7japO6Vj1ENnrqWvLBmW1OzV9g6FUFhFXg==", + "dependencies": { + "@algolia/requester-common": "4.14.2" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.14.2.tgz", + "integrity": "sha512-t89dfQb2T9MFQHidjHcfhh6iGMNwvuKUvojAj+JsrHAGbuSy7yE4BylhLX6R0Q1xYRoC4Vvv+O5qIw/LdnQfsQ==", + "dependencies": { + "@algolia/cache-common": "4.14.2", + "@algolia/logger-common": "4.14.2", + "@algolia/requester-common": "4.14.2" + } + }, + "node_modules/@algolia/ui-components-highlight-vdom": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@algolia/ui-components-highlight-vdom/-/ui-components-highlight-vdom-1.2.1.tgz", + "integrity": "sha512-IlYgIaCUEkz9ezNbwugwKv991oOHhveyq6nzL0F1jDzg1p3q5Yj/vO4KpNG910r2dwGCG3nEm5GtChcLnarhFA==", + "dependencies": { + "@algolia/ui-components-shared": "1.2.1", + "@babel/runtime": "^7.0.0" + } + }, + "node_modules/@algolia/ui-components-shared": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@algolia/ui-components-shared/-/ui-components-shared-1.2.1.tgz", + "integrity": "sha512-a7mYHf/GVQfhAx/HRiMveKkFvHspQv/REdG+C/FIOosiSmNZxX7QebDwJkrGSmDWdXO12D0Qv1xn3AytFcEDlQ==" + }, "node_modules/@ampproject/remapping": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", @@ -16247,6 +16383,11 @@ "@types/node": "*" } }, + "node_modules/@types/google.maps": { + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.51.0.tgz", + "integrity": "sha512-44/oQYjc5D6kxBcI3Qk9rk3IIOMwnlEMWDV7pwPJ2YI89s5Q1OzDrFvR7QJ3LFrpVXEhig+gyagFg54+foinFg==" + }, "node_modules/@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -16263,6 +16404,11 @@ "@types/unist": "*" } }, + "node_modules/@types/hogan.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/hogan.js/-/hogan.js-3.0.1.tgz", + "integrity": "sha512-D03i/2OY7kGyMq9wdQ7oD8roE49z/ZCZThe/nbahtvuqCNZY9T2MfedOWyeBdbEpY2W8Gnh/dyJLdFtUCOkYbg==" + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -17821,6 +17967,38 @@ "ajv": "^6.9.1" } }, + "node_modules/algoliasearch": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.14.2.tgz", + "integrity": "sha512-ngbEQonGEmf8dyEh5f+uOIihv4176dgbuOZspiuhmTTBRBuzWu3KCGHre6uHj5YyuC7pNvQGzB6ZNJyZi0z+Sg==", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.14.2", + "@algolia/cache-common": "4.14.2", + "@algolia/cache-in-memory": "4.14.2", + "@algolia/client-account": "4.14.2", + "@algolia/client-analytics": "4.14.2", + "@algolia/client-common": "4.14.2", + "@algolia/client-personalization": "4.14.2", + "@algolia/client-search": "4.14.2", + "@algolia/logger-common": "4.14.2", + "@algolia/logger-console": "4.14.2", + "@algolia/requester-browser-xhr": "4.14.2", + "@algolia/requester-common": "4.14.2", + "@algolia/requester-node-http": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "node_modules/algoliasearch-helper": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.11.1.tgz", + "integrity": "sha512-mvsPN3eK4E0bZG0/WlWJjeqe/bUD2KOEVOl0GyL/TGXn6wcpZU8NOuztGHCUKXkyg5gq6YzUakVTmnmSSO5Yiw==", + "dependencies": { + "@algolia/events": "^4.0.1" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 6" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -27366,6 +27544,41 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/hogan.js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz", + "integrity": "sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==", + "dependencies": { + "mkdirp": "0.3.0", + "nopt": "1.0.10" + }, + "bin": { + "hulk": "bin/hulk" + } + }, + "node_modules/hogan.js/node_modules/mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "engines": { + "node": "*" + } + }, + "node_modules/hogan.js/node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -27426,6 +27639,11 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/htm": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", + "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==" + }, "node_modules/html-dir-content": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/html-dir-content/-/html-dir-content-0.3.2.tgz", @@ -28008,6 +28226,39 @@ "node": ">=8" } }, + "node_modules/instantsearch.js": { + "version": "4.49.1", + "resolved": "https://registry.npmjs.org/instantsearch.js/-/instantsearch.js-4.49.1.tgz", + "integrity": "sha512-iYGRF+UujVaxn/twZXTfYkI+wc1luCiHZ/ozBME5MiVoit0wBvtERGUFR01uauZJSXCCXmjBg/JJojPngf7mAA==", + "dependencies": { + "@algolia/events": "^4.0.1", + "@algolia/ui-components-highlight-vdom": "^1.2.1", + "@algolia/ui-components-shared": "^1.2.1", + "@types/google.maps": "^3.45.3", + "@types/hogan.js": "^3.0.0", + "@types/qs": "^6.5.3", + "algoliasearch-helper": "^3.11.1", + "hogan.js": "^3.0.2", + "htm": "^3.0.0", + "preact": "^10.10.0", + "qs": "^6.5.1 < 6.10", + "search-insights": "^2.1.0" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 6" + } + }, + "node_modules/instantsearch.js/node_modules/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -60871,6 +61122,15 @@ "node": ">=0.10.0" } }, + "node_modules/preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prebuild-install": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.0.tgz", @@ -62469,6 +62729,36 @@ "react": "^16.8.4 || ^17.0.0" } }, + "node_modules/react-instantsearch-hooks": { + "version": "6.38.1", + "resolved": "https://registry.npmjs.org/react-instantsearch-hooks/-/react-instantsearch-hooks-6.38.1.tgz", + "integrity": "sha512-y3Y2w3mnaZWJb2GKVddX2ht4hYy5zd+Hxs+YdACgJeM9Ta1qA6xeH+DxEv3ECY6OgeBekccCTeJhC/CZ1v+VrQ==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "algoliasearch-helper": "^3.11.1", + "instantsearch.js": "^4.47.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 5", + "react": ">= 16.8.0 < 19" + } + }, + "node_modules/react-instantsearch-hooks-web": { + "version": "6.38.1", + "resolved": "https://registry.npmjs.org/react-instantsearch-hooks-web/-/react-instantsearch-hooks-web-6.38.1.tgz", + "integrity": "sha512-CSaHleVFVDz9mCos+eJMxagM9a6H9NO8nvkAqCwx2MOzqJFS1aE5htx8nnlTZt7nqpclPwu3VZUzMFrreA9D6g==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "instantsearch.js": "^4.47.0", + "react-instantsearch-hooks": "6.38.1" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 5", + "react": ">= 16.8.0 < 19", + "react-dom": ">= 16.8.0 < 19" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -64879,6 +65169,14 @@ "integrity": "sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==", "dev": true }, + "node_modules/search-insights": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.2.3.tgz", + "integrity": "sha512-fXwC0QzkBGZuGTb6FoQG+iLS81wljYuBU4Sco4TGTgp5boVkiKZeFqPV0e5h5++5QncTU2FQrQ+G3ILnqEa3yA==", + "engines": { + "node": ">=8.16.0" + } + }, "node_modules/secp256k1": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", @@ -71039,6 +71337,140 @@ } }, "dependencies": { + "@algolia/cache-browser-local-storage": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.14.2.tgz", + "integrity": "sha512-FRweBkK/ywO+GKYfAWbrepewQsPTIEirhi1BdykX9mxvBPtGNKccYAxvGdDCumU1jL4r3cayio4psfzKMejBlA==", + "requires": { + "@algolia/cache-common": "4.14.2" + } + }, + "@algolia/cache-common": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.14.2.tgz", + "integrity": "sha512-SbvAlG9VqNanCErr44q6lEKD2qoK4XtFNx9Qn8FK26ePCI8I9yU7pYB+eM/cZdS9SzQCRJBbHUumVr4bsQ4uxg==" + }, + "@algolia/cache-in-memory": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.14.2.tgz", + "integrity": "sha512-HrOukWoop9XB/VFojPv1R5SVXowgI56T9pmezd/djh2JnVN/vXswhXV51RKy4nCpqxyHt/aGFSq2qkDvj6KiuQ==", + "requires": { + "@algolia/cache-common": "4.14.2" + } + }, + "@algolia/client-account": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.14.2.tgz", + "integrity": "sha512-WHtriQqGyibbb/Rx71YY43T0cXqyelEU0lB2QMBRXvD2X0iyeGl4qMxocgEIcbHyK7uqE7hKgjT8aBrHqhgc1w==", + "requires": { + "@algolia/client-common": "4.14.2", + "@algolia/client-search": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "@algolia/client-analytics": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.14.2.tgz", + "integrity": "sha512-yBvBv2mw+HX5a+aeR0dkvUbFZsiC4FKSnfqk9rrfX+QrlNOKEhCG0tJzjiOggRW4EcNqRmaTULIYvIzQVL2KYQ==", + "requires": { + "@algolia/client-common": "4.14.2", + "@algolia/client-search": "4.14.2", + "@algolia/requester-common": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "@algolia/client-common": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.14.2.tgz", + "integrity": "sha512-43o4fslNLcktgtDMVaT5XwlzsDPzlqvqesRi4MjQz2x4/Sxm7zYg5LRYFol1BIhG6EwxKvSUq8HcC/KxJu3J0Q==", + "requires": { + "@algolia/requester-common": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "@algolia/client-personalization": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.14.2.tgz", + "integrity": "sha512-ACCoLi0cL8CBZ1W/2juehSltrw2iqsQBnfiu/Rbl9W2yE6o2ZUb97+sqN/jBqYNQBS+o0ekTMKNkQjHHAcEXNw==", + "requires": { + "@algolia/client-common": "4.14.2", + "@algolia/requester-common": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "@algolia/client-search": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.14.2.tgz", + "integrity": "sha512-L5zScdOmcZ6NGiVbLKTvP02UbxZ0njd5Vq9nJAmPFtjffUSOGEp11BmD2oMJ5QvARgx2XbX4KzTTNS5ECYIMWw==", + "requires": { + "@algolia/client-common": "4.14.2", + "@algolia/requester-common": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "@algolia/events": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" + }, + "@algolia/logger-common": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.14.2.tgz", + "integrity": "sha512-/JGlYvdV++IcMHBnVFsqEisTiOeEr6cUJtpjz8zc0A9c31JrtLm318Njc72p14Pnkw3A/5lHHh+QxpJ6WFTmsA==" + }, + "@algolia/logger-console": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.14.2.tgz", + "integrity": "sha512-8S2PlpdshbkwlLCSAB5f8c91xyc84VM9Ar9EdfE9UmX+NrKNYnWR1maXXVDQQoto07G1Ol/tYFnFVhUZq0xV/g==", + "requires": { + "@algolia/logger-common": "4.14.2" + } + }, + "@algolia/requester-browser-xhr": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.14.2.tgz", + "integrity": "sha512-CEh//xYz/WfxHFh7pcMjQNWgpl4wFB85lUMRyVwaDPibNzQRVcV33YS+63fShFWc2+42YEipFGH2iPzlpszmDw==", + "requires": { + "@algolia/requester-common": "4.14.2" + } + }, + "@algolia/requester-common": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.14.2.tgz", + "integrity": "sha512-73YQsBOKa5fvVV3My7iZHu1sUqmjjfs9TteFWwPwDmnad7T0VTCopttcsM3OjLxZFtBnX61Xxl2T2gmG2O4ehg==" + }, + "@algolia/requester-node-http": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.14.2.tgz", + "integrity": "sha512-oDbb02kd1o5GTEld4pETlPZLY0e+gOSWjWMJHWTgDXbv9rm/o2cF7japO6Vj1ENnrqWvLBmW1OzV9g6FUFhFXg==", + "requires": { + "@algolia/requester-common": "4.14.2" + } + }, + "@algolia/transporter": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.14.2.tgz", + "integrity": "sha512-t89dfQb2T9MFQHidjHcfhh6iGMNwvuKUvojAj+JsrHAGbuSy7yE4BylhLX6R0Q1xYRoC4Vvv+O5qIw/LdnQfsQ==", + "requires": { + "@algolia/cache-common": "4.14.2", + "@algolia/logger-common": "4.14.2", + "@algolia/requester-common": "4.14.2" + } + }, + "@algolia/ui-components-highlight-vdom": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@algolia/ui-components-highlight-vdom/-/ui-components-highlight-vdom-1.2.1.tgz", + "integrity": "sha512-IlYgIaCUEkz9ezNbwugwKv991oOHhveyq6nzL0F1jDzg1p3q5Yj/vO4KpNG910r2dwGCG3nEm5GtChcLnarhFA==", + "requires": { + "@algolia/ui-components-shared": "1.2.1", + "@babel/runtime": "^7.0.0" + } + }, + "@algolia/ui-components-shared": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@algolia/ui-components-shared/-/ui-components-shared-1.2.1.tgz", + "integrity": "sha512-a7mYHf/GVQfhAx/HRiMveKkFvHspQv/REdG+C/FIOosiSmNZxX7QebDwJkrGSmDWdXO12D0Qv1xn3AytFcEDlQ==" + }, "@ampproject/remapping": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", @@ -83199,6 +83631,11 @@ "@types/node": "*" } }, + "@types/google.maps": { + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.51.0.tgz", + "integrity": "sha512-44/oQYjc5D6kxBcI3Qk9rk3IIOMwnlEMWDV7pwPJ2YI89s5Q1OzDrFvR7QJ3LFrpVXEhig+gyagFg54+foinFg==" + }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -83215,6 +83652,11 @@ "@types/unist": "*" } }, + "@types/hogan.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/hogan.js/-/hogan.js-3.0.1.tgz", + "integrity": "sha512-D03i/2OY7kGyMq9wdQ7oD8roE49z/ZCZThe/nbahtvuqCNZY9T2MfedOWyeBdbEpY2W8Gnh/dyJLdFtUCOkYbg==" + }, "@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -84587,6 +85029,35 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" }, + "algoliasearch": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.14.2.tgz", + "integrity": "sha512-ngbEQonGEmf8dyEh5f+uOIihv4176dgbuOZspiuhmTTBRBuzWu3KCGHre6uHj5YyuC7pNvQGzB6ZNJyZi0z+Sg==", + "requires": { + "@algolia/cache-browser-local-storage": "4.14.2", + "@algolia/cache-common": "4.14.2", + "@algolia/cache-in-memory": "4.14.2", + "@algolia/client-account": "4.14.2", + "@algolia/client-analytics": "4.14.2", + "@algolia/client-common": "4.14.2", + "@algolia/client-personalization": "4.14.2", + "@algolia/client-search": "4.14.2", + "@algolia/logger-common": "4.14.2", + "@algolia/logger-console": "4.14.2", + "@algolia/requester-browser-xhr": "4.14.2", + "@algolia/requester-common": "4.14.2", + "@algolia/requester-node-http": "4.14.2", + "@algolia/transporter": "4.14.2" + } + }, + "algoliasearch-helper": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.11.1.tgz", + "integrity": "sha512-mvsPN3eK4E0bZG0/WlWJjeqe/bUD2KOEVOl0GyL/TGXn6wcpZU8NOuztGHCUKXkyg5gq6YzUakVTmnmSSO5Yiw==", + "requires": { + "@algolia/events": "^4.0.1" + } + }, "ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -92052,6 +92523,30 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hogan.js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz", + "integrity": "sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==", + "requires": { + "mkdirp": "0.3.0", + "nopt": "1.0.10" + }, + "dependencies": { + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==" + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "requires": { + "abbrev": "1" + } + } + } + }, "hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -92113,6 +92608,11 @@ } } }, + "htm": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", + "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==" + }, "html-dir-content": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/html-dir-content/-/html-dir-content-0.3.2.tgz", @@ -92539,6 +93039,32 @@ } } }, + "instantsearch.js": { + "version": "4.49.1", + "resolved": "https://registry.npmjs.org/instantsearch.js/-/instantsearch.js-4.49.1.tgz", + "integrity": "sha512-iYGRF+UujVaxn/twZXTfYkI+wc1luCiHZ/ozBME5MiVoit0wBvtERGUFR01uauZJSXCCXmjBg/JJojPngf7mAA==", + "requires": { + "@algolia/events": "^4.0.1", + "@algolia/ui-components-highlight-vdom": "^1.2.1", + "@algolia/ui-components-shared": "^1.2.1", + "@types/google.maps": "^3.45.3", + "@types/hogan.js": "^3.0.0", + "@types/qs": "^6.5.3", + "algoliasearch-helper": "^3.11.1", + "hogan.js": "^3.0.2", + "htm": "^3.0.0", + "preact": "^10.10.0", + "qs": "^6.5.1 < 6.10", + "search-insights": "^2.1.0" + }, + "dependencies": { + "qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==" + } + } + }, "internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -117378,6 +117904,11 @@ "xtend": "^4.0.0" } }, + "preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==" + }, "prebuild-install": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.0.tgz", @@ -118585,6 +119116,27 @@ "prop-types": "^15.0.0" } }, + "react-instantsearch-hooks": { + "version": "6.38.1", + "resolved": "https://registry.npmjs.org/react-instantsearch-hooks/-/react-instantsearch-hooks-6.38.1.tgz", + "integrity": "sha512-y3Y2w3mnaZWJb2GKVddX2ht4hYy5zd+Hxs+YdACgJeM9Ta1qA6xeH+DxEv3ECY6OgeBekccCTeJhC/CZ1v+VrQ==", + "requires": { + "@babel/runtime": "^7.1.2", + "algoliasearch-helper": "^3.11.1", + "instantsearch.js": "^4.47.0", + "use-sync-external-store": "^1.0.0" + } + }, + "react-instantsearch-hooks-web": { + "version": "6.38.1", + "resolved": "https://registry.npmjs.org/react-instantsearch-hooks-web/-/react-instantsearch-hooks-web-6.38.1.tgz", + "integrity": "sha512-CSaHleVFVDz9mCos+eJMxagM9a6H9NO8nvkAqCwx2MOzqJFS1aE5htx8nnlTZt7nqpclPwu3VZUzMFrreA9D6g==", + "requires": { + "@babel/runtime": "^7.1.2", + "instantsearch.js": "^4.47.0", + "react-instantsearch-hooks": "6.38.1" + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -120381,6 +120933,11 @@ "integrity": "sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==", "dev": true }, + "search-insights": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.2.3.tgz", + "integrity": "sha512-fXwC0QzkBGZuGTb6FoQG+iLS81wljYuBU4Sco4TGTgp5boVkiKZeFqPV0e5h5++5QncTU2FQrQ+G3ILnqEa3yA==" + }, "secp256k1": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", diff --git a/package.json b/package.json index 3e168bc..90ac984 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@types/react": "^18.0.5", "@types/react-dom": "^18.0.1", "@use-gesture/react": "^10.2.11", + "algoliasearch": "^4.14.2", "apollo-server": "^3.6.7", "apollo-server-lambda": "^3.6.7", "axios": "^0.26.1", @@ -77,6 +78,7 @@ "react-helmet": "^6.1.0", "react-hook-form": "^7.30.0", "react-icons": "^4.3.1", + "react-instantsearch-hooks-web": "^6.38.1", "react-loader-spinner": "^6.0.0-0", "react-loading-skeleton": "^3.1.0", "react-modal": "^3.15.1", diff --git a/src/Components/Navbar/NavDesktop.tsx b/src/Components/Navbar/NavDesktop.tsx index e84fdf6..2b09570 100644 --- a/src/Components/Navbar/NavDesktop.tsx +++ b/src/Components/Navbar/NavDesktop.tsx @@ -1,49 +1,124 @@ -import { Link, } from "react-router-dom"; -import { ReactNode, } from "react"; -import '@szhsin/react-menu/dist/index.css'; +import { Link } from "react-router-dom"; +import { ReactNode, useState } from "react"; +import "@szhsin/react-menu/dist/index.css"; import { PAGES_ROUTES } from "src/utils/routing"; +import Search from "./Search"; +import { IoClose, IoSearch } from "react-icons/io5"; +import IconButton from "../IconButton/IconButton"; +import { AnimatePresence, motion } from "framer-motion"; interface Props { - cta?: ReactNode + cta?: ReactNode; } export default function NavDesktop(props: Props) { + const [showSearch, setShowSearch] = useState(false); + return ( + - ); + + )} + + + {props.cta} + setShowSearch(true)}> + + + + + + ); } diff --git a/src/Components/Navbar/Search.tsx b/src/Components/Navbar/Search.tsx new file mode 100644 index 0000000..d152d56 --- /dev/null +++ b/src/Components/Navbar/Search.tsx @@ -0,0 +1,90 @@ +import { useCallback, useState } from "react"; +import { Hits, SearchBox } from "react-instantsearch-hooks-web"; +import { Projects } from "src/graphql"; +import { openModal } from "react-url-modal"; + +export default function Search() { + const [showSearchResults, setShowSearchResults] = useState(false); + + const autoFocus = useCallback((node: HTMLElement | null) => { + if (node) { + console.log(node.querySelector("input")); + node.querySelector("input")?.focus(); + } + }, []); + + return ( +
+ setShowSearchResults(true)} + onBlur={() => setTimeout(() => setShowSearchResults(false), 50)} + /> + {showSearchResults && ( +
+ +
+ )} +
+ ); +} + +type SearchProject = Pick< + Projects, + | "id" + | "title" + | "tags" + | "description" + | "status" + | "license" + | "website" + | "yearFounded" +> & { + category: NonNullable[number] | null; + logo: string | null; +}; + +function Hit({ hit }: { hit: SearchProject }) { + const clickProject = () => { + openModal({ + name: "projectDetails", + params: { + projectId: hit.id, + }, + }); + }; + + return ( + + ); +} diff --git a/src/features/Projects/pages/ExplorePage/ExplorePage.tsx b/src/features/Projects/pages/ExplorePage/ExplorePage.tsx index 85b93f3..009cb1f 100644 --- a/src/features/Projects/pages/ExplorePage/ExplorePage.tsx +++ b/src/features/Projects/pages/ExplorePage/ExplorePage.tsx @@ -1,208 +1,246 @@ - -import ErrorMessage from 'src/Components/Errors/ErrorMessage/ErrorMessage'; -import { useExplorePageQuery, useGetFiltersQuery } from 'src/graphql'; -import ProjectsGrid from './ProjectsGrid/ProjectsGrid'; -import Categories, { Category } from '../../Components/Categories/Categories'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import Header from './Header/Header'; -import Button from 'src/Components/Button/Button'; -import { useAppDispatch } from 'src/utils/hooks'; -import { openModal } from 'src/redux/features/modals.slice'; -import { createAction } from '@reduxjs/toolkit'; -import { useReduxEffect } from 'src/utils/hooks/useReduxEffect'; -import { NetworkStatus } from '@apollo/client'; -import { FiSliders } from 'react-icons/fi'; -import { HiOutlineChevronDoubleDown } from 'react-icons/hi' -import { ProjectsFilters } from './Filters/FiltersModal'; -import { useUpdateUrlWithFilters } from './helpers'; -import { withBasicProvider } from 'src/utils/helperFunctions'; -import { ProjectsFiltersProvider, useProjectsFilters } from './filters-context'; -import { setTheme } from 'src/redux/features/ui.slice'; -import OgTags from 'src/Components/OgTags/OgTags'; - +import ErrorMessage from "src/Components/Errors/ErrorMessage/ErrorMessage"; +import { useExplorePageQuery, useGetFiltersQuery } from "src/graphql"; +import ProjectsGrid from "./ProjectsGrid/ProjectsGrid"; +import Categories, { Category } from "../../Components/Categories/Categories"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import Header from "./Header/Header"; +import Button from "src/Components/Button/Button"; +import { useAppDispatch } from "src/utils/hooks"; +import { openModal } from "src/redux/features/modals.slice"; +import { createAction } from "@reduxjs/toolkit"; +import { useReduxEffect } from "src/utils/hooks/useReduxEffect"; +import { NetworkStatus } from "@apollo/client"; +import { FiSliders } from "react-icons/fi"; +import { HiOutlineChevronDoubleDown } from "react-icons/hi"; +import { ProjectsFilters } from "./Filters/FiltersModal"; +import { useUpdateUrlWithFilters } from "./helpers"; +import { withBasicProvider } from "src/utils/helperFunctions"; +import { ProjectsFiltersProvider, useProjectsFilters } from "./filters-context"; +import { setTheme } from "src/redux/features/ui.slice"; +import OgTags from "src/Components/OgTags/OgTags"; function ExplorePage() { + const dispatch = useAppDispatch(); - const dispatch = useAppDispatch(); + const [canFetchMore, setCanFetchMore] = useState(true); - const [canFetchMore, setCanFetchMore] = useState(true); - - const { filters, updateFilters } = useProjectsFilters(); - - useUpdateUrlWithFilters(filters) - const filtersQuery = useGetFiltersQuery(); - - const hiddenCategoriesIds = useMemo(() => { - if (filtersQuery.loading) return []; - return filtersQuery.data?.categoryList?.filter(c => c?.isHidden).map(c => c!.id!) ?? []; - }, [filtersQuery.data?.categoryList, filtersQuery.loading]) - - const { queryFilters, hasSearchFilters } = useMemo(() => createQueryFilters(filters, { - hiddenCategoriesIds - }), [filters, hiddenCategoriesIds]) - - - const { data, networkStatus, error, fetchMore } = useExplorePageQuery({ - variables: { - page: 1, - pageSize: PAGE_SIZE, - filter: queryFilters - }, - notifyOnNetworkStatusChange: true, - onCompleted: data => { - if ((data.projects?.length ?? 0) < PAGE_SIZE) setCanFetchMore(false); - }, - skip: filtersQuery.loading - }); - - - const onFiltersUpdated = useCallback(({ payload }: typeof UPDATE_FILTERS_ACTION) => { - updateFilters(payload) - }, [updateFilters]) - - useReduxEffect(onFiltersUpdated, UPDATE_FILTERS_ACTION.type); - - useEffect(() => { - dispatch(setTheme('light')) - }, [dispatch]) - - - useEffect(() => { - setCanFetchMore(true); - }, [filters]) - - - const openFilters = () => { - dispatch(openModal({ - Modal: "FiltersModal", - isPageModal: true, - props: { - callbackAction: { - type: UPDATE_FILTERS_ACTION.type, - payload: { - - } - }, - initFilters: { - ...filters - } - } - })) - } - - const selectCategoryTab = (category: Category | null) => { - updateFilters({ ...(filters ?? {}), categories: category ? [category] : undefined }) - } - - const clickFetchMore = () => { - fetchMore({ variables: { page: Math.floor((data?.projects?.length ?? 0) / PAGE_SIZE) + 1 } }) - .then(res => { - if (!res.data.projects || res.data.projects.length < PAGE_SIZE) setCanFetchMore(false); - }) - } - - if (error) { - return
- -
- } - - const isLoading = networkStatus === NetworkStatus.loading || networkStatus === NetworkStatus.refetch || networkStatus === NetworkStatus.setVariables || filtersQuery.loading; - const isLoadingMore = networkStatus === NetworkStatus.fetchMore; - const canLoadMore = !isLoading && !isLoadingMore && data?.projects && data.projects.length > 0 && canFetchMore; + const { filters, updateFilters } = useProjectsFilters(); + useUpdateUrlWithFilters(filters); + const filtersQuery = useGetFiltersQuery(); + const hiddenCategoriesIds = useMemo(() => { + if (filtersQuery.loading) return []; return ( - <> - c?.isHidden) + .map((c) => c!.id!) ?? [] + ); + }, [filtersQuery.data?.categoryList, filtersQuery.loading]); + + const { queryFilters, hasSearchFilters } = useMemo( + () => + createQueryFilters(filters, { + hiddenCategoriesIds, + }), + [filters, hiddenCategoriesIds] + ); + + const { data, networkStatus, error, fetchMore } = useExplorePageQuery({ + variables: { + page: 1, + pageSize: PAGE_SIZE, + filter: queryFilters, + }, + notifyOnNetworkStatusChange: true, + onCompleted: (data) => { + if ((data.projects?.length ?? 0) < PAGE_SIZE) setCanFetchMore(false); + }, + skip: filtersQuery.loading, + }); + + const onFiltersUpdated = useCallback( + ({ payload }: typeof UPDATE_FILTERS_ACTION) => { + updateFilters(payload); + }, + [updateFilters] + ); + + useReduxEffect(onFiltersUpdated, UPDATE_FILTERS_ACTION.type); + + useEffect(() => { + dispatch(setTheme("light")); + }, [dispatch]); + + useEffect(() => { + setCanFetchMore(true); + }, [filters]); + + const openFilters = () => { + dispatch( + openModal({ + Modal: "FiltersModal", + isPageModal: true, + props: { + callbackAction: { + type: UPDATE_FILTERS_ACTION.type, + payload: {}, + }, + initFilters: { + ...filters, + }, + }, + }) + ); + }; + + const selectCategoryTab = (category: Category | null) => { + updateFilters({ + ...(filters ?? {}), + categories: category ? [category] : undefined, + }); + }; + + const clickFetchMore = () => { + fetchMore({ + variables: { + page: Math.floor((data?.projects?.length ?? 0) / PAGE_SIZE) + 1, + }, + }).then((res) => { + if (!res.data.projects || res.data.projects.length < PAGE_SIZE) + setCanFetchMore(false); + }); + }; + + if (error) { + return ( +
+ +
+ ); + } + + const isLoading = + networkStatus === NetworkStatus.loading || + networkStatus === NetworkStatus.refetch || + networkStatus === NetworkStatus.setVariables || + filtersQuery.loading; + const isLoadingMore = networkStatus === NetworkStatus.fetchMore; + const canLoadMore = + !isLoading && + !isLoadingMore && + data?.projects && + data.projects.length > 0 && + canFetchMore; + + return ( + <> + +
+
+
+
+ selectCategoryTab(v)} /> -
-
-
-
selectCategoryTab(v)} />
- -
- p !== null) as any[] ?? []} - /> - {canLoadMore &&
- -
} -
- - ) +
+ +
+ p !== null) as any[]) ?? []} + /> + {canLoadMore && ( +
+ +
+ )} +
+ + ); } export default withBasicProvider(ProjectsFiltersProvider, ExplorePage); - -const UPDATE_FILTERS_ACTION = createAction>('PROJECTS_FILTERS_UPDATED')({}) +const UPDATE_FILTERS_ACTION = createAction>( + "PROJECTS_FILTERS_UPDATED" +)({}); const PAGE_SIZE = 28; type QueryFilter = Partial<{ - categoryId: object | null - tagId: object | null - yearFounded: number | null - dead: boolean | null - license: string | null -}> + categoryId: object | null; + tagId: object | null; + yearFounded: number | null; + dead: boolean | null; + license: string | null; +}>; +const createQueryFilters = ( + filters: Partial | null, + extraFilters: { hiddenCategoriesIds: string[] } +) => { + let filter: QueryFilter = {}; + let hasSearchFilters = false; -const createQueryFilters = (filters: Partial | null, extraFilters: { hiddenCategoriesIds: string[] }) => { - let filter: QueryFilter = {} - let hasSearchFilters = false; + if (filters?.categories) { + filter.categoryId = filters?.categories.map((c) => c.id!); + hasSearchFilters = true; + } else { + filter.categoryId = { + _nin: extraFilters.hiddenCategoriesIds, + }; + } + if (filters?.tags) { + filter.tagId = { + _in: filters?.tags.map((t) => t.id), + }; + hasSearchFilters = true; + } - if (filters?.categories) { - filter.categoryId = filters?.categories.map(c => c.id!) - hasSearchFilters = true; - } else { - filter.categoryId = { - _nin: extraFilters.hiddenCategoriesIds - }; - } + if (filters?.yearFounded) { + filter.yearFounded = Number(filters?.yearFounded); + hasSearchFilters = true; + } + if (filters?.projectStatus) { + filter.dead = filters?.projectStatus === "alive" ? false : true; + hasSearchFilters = true; + } - if (filters?.tags) { - filter.tagId = { - _in: filters?.tags.map(t => t.id) - } - hasSearchFilters = true; - } + if (filters?.projectLicense) { + filter.license = filters?.projectLicense; + hasSearchFilters = true; + } - if (filters?.yearFounded) { - filter.yearFounded = Number(filters?.yearFounded) - hasSearchFilters = true; - } + if (Object.keys(filter).length === 0) + return { queryFilters: null, hasSearchFilters }; - if (filters?.projectStatus) { - filter.dead = filters?.projectStatus === 'alive' ? false : true; - hasSearchFilters = true; - } - - - if (filters?.projectLicense) { - filter.license = filters?.projectLicense - hasSearchFilters = true; - } - - if (Object.keys(filter).length === 0) - return { queryFilters: null, hasSearchFilters } - - return { queryFilters: filter, hasSearchFilters }; -} \ No newline at end of file + return { queryFilters: filter, hasSearchFilters }; +}; diff --git a/src/features/Projects/pages/ExplorePage/ProjectsGrid/ProjectsGrid.tsx b/src/features/Projects/pages/ExplorePage/ProjectsGrid/ProjectsGrid.tsx index f218efc..306ea8d 100644 --- a/src/features/Projects/pages/ExplorePage/ProjectsGrid/ProjectsGrid.tsx +++ b/src/features/Projects/pages/ExplorePage/ProjectsGrid/ProjectsGrid.tsx @@ -1,48 +1,58 @@ - import { openModal } from "react-url-modal"; import ProjectCardMini from "src/features/Projects/Components/ProjectCardMini/ProjectCardMini"; -import ProjectCardMiniSkeleton from 'src/features/Projects/Components/ProjectCardMini/ProjectCardMini.Skeleton'; -// import { openModal } from 'src/redux/features/modals.slice'; -import { useAppDispatch } from 'src/utils/hooks'; -import { ProjectCard } from 'src/utils/interfaces'; +import ProjectCardMiniSkeleton from "src/features/Projects/Components/ProjectCardMini/ProjectCardMini.Skeleton"; +import { ProjectCard } from "src/utils/interfaces"; interface Props { - isLoading?: boolean; - isLoadingMore?: boolean; - projects: ProjectCard[] + isLoading?: boolean; + isLoadingMore?: boolean; + projects: ProjectCard[]; } -export default function ProjectsGrid({ isLoading, isLoadingMore, projects }: Props) { +export default function ProjectsGrid({ + isLoading, + isLoadingMore, + projects, +}: Props) { + const handleClick = (projectId: string) => { + openModal({ + name: "projectDetails", + params: { + projectId, + }, + }); + }; - const dispatch = useAppDispatch(); + return ( +
+ {isLoading && + Array(12) + .fill(0) + .map((_, idx) => )} + {!isLoading && projects.length === 0 && ( +

+ No results found here... +

+ )} + {!isLoading && + projects.map((project) => ( + + ))} - const handleClick = (projectId: string) => { - // dispatch(openModal({ - // Modal: "ProjectDetailsCard", - // isPageModal: true, - // props: { - // projectId - // } - // })) - openModal({ - name: "projectDetails", - params: { - projectId - } - }) - } - - return ( -
- {isLoading && Array(12).fill(0).map((_, idx) => )} - {!isLoading && projects.length === 0 &&

No results found here...

} - {!isLoading && projects.map((project) => )} - - {isLoadingMore && Array(4).fill(0).map((_, idx) => )} -
- ) + {isLoadingMore && + Array(4) + .fill(0) + .map((_, idx) => )} +
+ ); } diff --git a/src/utils/Wrapper.tsx b/src/utils/Wrapper.tsx index 153fd00..4c958d3 100644 --- a/src/utils/Wrapper.tsx +++ b/src/utils/Wrapper.tsx @@ -1,70 +1,71 @@ -import { store } from '../redux/store'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom' +import { store } from "../redux/store"; +import { Provider } from "react-redux"; +import { BrowserRouter } from "react-router-dom"; import { ApolloProvider } from "@apollo/client"; -import { apolloClient } from './apollo'; -import { useAppDispatch, useResizeListener } from './hooks'; -import { useCallback, useLayoutEffect } from 'react'; -import { setIsMobileScreen } from 'src/redux/features/ui.slice'; -import { isMobileScreen } from './helperFunctions'; -import ReactTooltip from 'react-tooltip'; -import { ToastContainer } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.css'; -import 'react-loading-skeleton/dist/skeleton.css' -import THEME from './theme'; -import { NotificationsService } from 'src/services'; -import ErrorPage from 'src/Components/Errors/ErrorPage/ErrorPage'; -import { ErrorBoundary } from 'react-error-boundary'; +import { apolloClient } from "./apollo"; +import { useAppDispatch, useResizeListener } from "./hooks"; +import { useCallback, useLayoutEffect } from "react"; +import { setIsMobileScreen } from "src/redux/features/ui.slice"; +import { isMobileScreen } from "./helperFunctions"; +import ReactTooltip from "react-tooltip"; +import { ToastContainer } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; +import "react-loading-skeleton/dist/skeleton.css"; +import THEME from "./theme"; +import { NotificationsService } from "src/services"; +import ErrorPage from "src/Components/Errors/ErrorPage/ErrorPage"; +import { ErrorBoundary } from "react-error-boundary"; +import algoliasearch from "algoliasearch/lite"; +import { InstantSearch } from "react-instantsearch-hooks-web"; THEME.injectStyles(); -let basename = '/'; +let basename = "/"; -if (process.env.REACT_APP_FOR_GITHUB) - basename = '/makers.bolt.fun/' +if (process.env.REACT_APP_FOR_GITHUB) basename = "/makers.bolt.fun/"; export const useWrapperSetup = () => { + const dispatch = useAppDispatch(); - const dispatch = useAppDispatch() + useLayoutEffect(() => { + // Setting CSS Vars + let root = document.documentElement; + root.style.setProperty("--primary", THEME.colors.primary[500]); + // root.style.setProperty('--secondary', THEME.colors.secondary[500]); + }, []); - useLayoutEffect(() => { - // Setting CSS Vars - let root = document.documentElement; - root.style.setProperty('--primary', THEME.colors.primary[500]); - // root.style.setProperty('--secondary', THEME.colors.secondary[500]); - }, []) + const resizeListener = useCallback(() => { + dispatch(setIsMobileScreen(isMobileScreen())); + }, [dispatch]); - const resizeListener = useCallback(() => { - dispatch(setIsMobileScreen(isMobileScreen())) - }, [dispatch]) + useResizeListener(resizeListener); +}; - useResizeListener(resizeListener) -} +const searchClient = algoliasearch( + "DSRCS0EZ0V", + "690c8ad6c43854d86243e9654fc84016" +); export default function Wrapper(props: any) { - - return ( - <> - - - - - {props.children} - - - - - - - - ) + return ( + <> + + + + + + {props.children} + + + + + + + + + ); } From 792cf7960a5f5efa6e0f9d856107714b63833067 Mon Sep 17 00:00:00 2001 From: "MTG\\mtg09" Date: Wed, 14 Dec 2022 12:22:44 +0400 Subject: [PATCH 2/6] update (search): add debouncing --- src/Components/Navbar/Search.tsx | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/Components/Navbar/Search.tsx b/src/Components/Navbar/Search.tsx index d152d56..4ef34d9 100644 --- a/src/Components/Navbar/Search.tsx +++ b/src/Components/Navbar/Search.tsx @@ -1,20 +1,20 @@ -import { useCallback, useState } from "react"; +import { useState } from "react"; import { Hits, SearchBox } from "react-instantsearch-hooks-web"; import { Projects } from "src/graphql"; import { openModal } from "react-url-modal"; +import { useDebouncedCallback } from "@react-hookz/web"; export default function Search() { const [showSearchResults, setShowSearchResults] = useState(false); - const autoFocus = useCallback((node: HTMLElement | null) => { - if (node) { - console.log(node.querySelector("input")); - node.querySelector("input")?.focus(); - } - }, []); + const debouncedSearch = useDebouncedCallback( + (query: string, search: (v: string) => void) => search(query), + [], + 300 + ); return ( -
+
setShowSearchResults(true)} - onBlur={() => setTimeout(() => setShowSearchResults(false), 50)} + onBlur={() => setTimeout(() => setShowSearchResults(false), 100)} + queryHook={debouncedSearch} /> {showSearchResults && (
@@ -68,14 +69,6 @@ function Hit({ hit }: { hit: SearchProject }) { className="hover:bg-gray-50 p-12 text-left w-full flex gap-12 items-center" onClick={clickProject} > -

{hit?.title} From f86cc77f3f900717b1d801399ee4dbb41113f8ed Mon Sep 17 00:00:00 2001 From: "MTG\\mtg09" Date: Wed, 14 Dec 2022 12:57:32 +0400 Subject: [PATCH 3/6] update (search): hanlde empty & no-query states, improve close icon --- src/Components/Navbar/Search.tsx | 68 +++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/src/Components/Navbar/Search.tsx b/src/Components/Navbar/Search.tsx index 4ef34d9..ef98196 100644 --- a/src/Components/Navbar/Search.tsx +++ b/src/Components/Navbar/Search.tsx @@ -1,5 +1,10 @@ -import { useState } from "react"; -import { Hits, SearchBox } from "react-instantsearch-hooks-web"; +import { ReactNode, useState } from "react"; +import { + Hits, + SearchBox, + useInstantSearch, +} from "react-instantsearch-hooks-web"; + import { Projects } from "src/graphql"; import { openModal } from "react-url-modal"; import { useDebouncedCallback } from "@react-hookz/web"; @@ -31,9 +36,19 @@ export default function Search() { queryHook={debouncedSearch} /> {showSearchResults && ( -

- -
+ +
+ + No Results found here +

+ } + > + +
+
+
)}
); @@ -81,3 +96,46 @@ function Hit({ hit }: { hit: SearchProject }) { ); } + +const NoResultsBoundary = ({ + children, + fallback, +}: { + children: ReactNode; + fallback: ReactNode; +}) => { + const { results, indexUiState } = useInstantSearch(); + + const isQueryEmpty = !indexUiState.query; + + if (isQueryEmpty) return null; + + // The `__isArtificial` flag makes sure not to display the No Results message + // when no hits have been returned yet. + if (!results.__isArtificial && results.nbHits === 0) { + return ( + <> + {fallback} + + + ); + } + + return <>{children}; +}; + +const EmptyQueryBoundary = ({ + children, + fallback, +}: { + children: ReactNode; + fallback: ReactNode; +}) => { + const { indexUiState } = useInstantSearch(); + + const isQueryEmpty = !indexUiState.query; + + if (isQueryEmpty) return <>{fallback}; + + return <>{children}; +}; From 7e51effb29bc62d85f3413f5d808c5120be34467 Mon Sep 17 00:00:00 2001 From: "MTG\\mtg09" Date: Wed, 14 Dec 2022 12:57:47 +0400 Subject: [PATCH 4/6] update (search): close icon --- src/Components/Navbar/NavDesktop.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Navbar/NavDesktop.tsx b/src/Components/Navbar/NavDesktop.tsx index 2b09570..2d96c67 100644 --- a/src/Components/Navbar/NavDesktop.tsx +++ b/src/Components/Navbar/NavDesktop.tsx @@ -100,7 +100,7 @@ export default function NavDesktop(props: Props) { setTimeout(() => setShowSearch(false), 109)} > - +
From 44ac303b8faacc6142e6ce6911524fc1ede0707d Mon Sep 17 00:00:00 2001 From: "MTG\\mtg09" Date: Thu, 15 Dec 2022 18:07:55 +0400 Subject: [PATCH 5/6] fix (merge) --- .../pages/ExplorePage/ExplorePage.tsx | 65 ------------------- 1 file changed, 65 deletions(-) diff --git a/src/features/Projects/pages/ExplorePage/ExplorePage.tsx b/src/features/Projects/pages/ExplorePage/ExplorePage.tsx index 15c4c0b..284beff 100644 --- a/src/features/Projects/pages/ExplorePage/ExplorePage.tsx +++ b/src/features/Projects/pages/ExplorePage/ExplorePage.tsx @@ -18,50 +18,17 @@ import { withBasicProvider } from "src/utils/helperFunctions"; import { ProjectsFiltersProvider, useProjectsFilters } from "./filters-context"; import { setTheme } from "src/redux/features/ui.slice"; import OgTags from "src/Components/OgTags/OgTags"; -import ErrorMessage from "src/Components/Errors/ErrorMessage/ErrorMessage"; -import { useExplorePageQuery, useGetFiltersQuery } from "src/graphql"; -import ProjectsGrid from "./ProjectsGrid/ProjectsGrid"; -import Categories, { Category } from "../../Components/Categories/Categories"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import Header from "./Header/Header"; -import Button from "src/Components/Button/Button"; -import { useAppDispatch } from "src/utils/hooks"; -import { openModal } from "src/redux/features/modals.slice"; -import { createAction } from "@reduxjs/toolkit"; -import { useReduxEffect } from "src/utils/hooks/useReduxEffect"; -import { NetworkStatus } from "@apollo/client"; -import { FiSliders } from "react-icons/fi"; -import { HiOutlineChevronDoubleDown } from "react-icons/hi"; -import { ProjectsFilters } from "./Filters/FiltersModal"; -import { useUpdateUrlWithFilters } from "./helpers"; -import { withBasicProvider } from "src/utils/helperFunctions"; -import { ProjectsFiltersProvider, useProjectsFilters } from "./filters-context"; -import { setTheme } from "src/redux/features/ui.slice"; -import OgTags from "src/Components/OgTags/OgTags"; function ExplorePage() { const dispatch = useAppDispatch(); - const dispatch = useAppDispatch(); - const [canFetchMore, setCanFetchMore] = useState(true); const [canFetchMore, setCanFetchMore] = useState(true); - const { filters, updateFilters } = useProjectsFilters(); const { filters, updateFilters } = useProjectsFilters(); - useUpdateUrlWithFilters(filters); - const filtersQuery = useGetFiltersQuery(); useUpdateUrlWithFilters(filters); const filtersQuery = useGetFiltersQuery(); - const hiddenCategoriesIds = useMemo(() => { - if (filtersQuery.loading) return []; - return ( - filtersQuery.data?.categoryList - ?.filter((c) => c?.isHidden) - .map((c) => c!.id!) ?? [] - ); - }, [filtersQuery.data?.categoryList, filtersQuery.loading]); const hiddenCategoriesIds = useMemo(() => { if (filtersQuery.loading) return []; return ( @@ -71,13 +38,6 @@ function ExplorePage() { ); }, [filtersQuery.data?.categoryList, filtersQuery.loading]); - const { queryFilters, hasSearchFilters } = useMemo( - () => - createQueryFilters(filters, { - hiddenCategoriesIds, - }), - [filters, hiddenCategoriesIds] - ); const { queryFilters, hasSearchFilters } = useMemo( () => createQueryFilters(filters, { @@ -86,18 +46,6 @@ function ExplorePage() { [filters, hiddenCategoriesIds] ); - const { data, networkStatus, error, fetchMore } = useExplorePageQuery({ - variables: { - page: 1, - pageSize: PAGE_SIZE, - filter: queryFilters, - }, - notifyOnNetworkStatusChange: true, - onCompleted: (data) => { - if ((data.projects?.length ?? 0) < PAGE_SIZE) setCanFetchMore(false); - }, - skip: filtersQuery.loading, - }); const { data, networkStatus, error, fetchMore } = useExplorePageQuery({ variables: { page: 1, @@ -111,12 +59,6 @@ function ExplorePage() { skip: filtersQuery.loading, }); - const onFiltersUpdated = useCallback( - ({ payload }: typeof UPDATE_FILTERS_ACTION) => { - updateFilters(payload); - }, - [updateFilters] - ); const onFiltersUpdated = useCallback( ({ payload }: typeof UPDATE_FILTERS_ACTION) => { updateFilters(payload); @@ -124,19 +66,12 @@ function ExplorePage() { [updateFilters] ); - useReduxEffect(onFiltersUpdated, UPDATE_FILTERS_ACTION.type); useReduxEffect(onFiltersUpdated, UPDATE_FILTERS_ACTION.type); - useEffect(() => { - dispatch(setTheme("light")); - }, [dispatch]); useEffect(() => { dispatch(setTheme("light")); }, [dispatch]); - useEffect(() => { - setCanFetchMore(true); - }, [filters]); useEffect(() => { setCanFetchMore(true); }, [filters]); From 0cf4f14800549c65a1596f5a315566bd46afeffb Mon Sep 17 00:00:00 2001 From: "MTG\\mtg09" Date: Tue, 27 Dec 2022 15:32:14 +0400 Subject: [PATCH 6/6] update: search api credentials, fix closing bug --- src/Components/Navbar/Search.tsx | 13 ++++++++----- src/utils/Wrapper.tsx | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Components/Navbar/Search.tsx b/src/Components/Navbar/Search.tsx index ef98196..228d60e 100644 --- a/src/Components/Navbar/Search.tsx +++ b/src/Components/Navbar/Search.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useState } from "react"; +import { ReactNode, useRef, useState } from "react"; import { Hits, SearchBox, @@ -7,19 +7,23 @@ import { import { Projects } from "src/graphql"; import { openModal } from "react-url-modal"; -import { useDebouncedCallback } from "@react-hookz/web"; +import { useClickOutside, useDebouncedCallback } from "@react-hookz/web"; export default function Search() { const [showSearchResults, setShowSearchResults] = useState(false); + const searchContainerRef = useRef(null!); const debouncedSearch = useDebouncedCallback( (query: string, search: (v: string) => void) => search(query), [], 300 ); + useClickOutside(searchContainerRef, () => { + // setTimeout(() => setShowSearchResults(false), 100); + }); return ( -
+
setShowSearchResults(true)} - onBlur={() => setTimeout(() => setShowSearchResults(false), 100)} queryHook={debouncedSearch} /> {showSearchResults && ( -
+
diff --git a/src/utils/Wrapper.tsx b/src/utils/Wrapper.tsx index 4c958d3..263a8a6 100644 --- a/src/utils/Wrapper.tsx +++ b/src/utils/Wrapper.tsx @@ -42,8 +42,8 @@ export const useWrapperSetup = () => { }; const searchClient = algoliasearch( - "DSRCS0EZ0V", - "690c8ad6c43854d86243e9654fc84016" + "YPRTIWSIAM", + "85731af4a62e17a4dd17399c609731d5" ); export default function Wrapper(props: any) {