From 2b5ada8afae0292793cb6fa9b1fca5ed7aeb0178 Mon Sep 17 00:00:00 2001 From: "MTG\\mtg09" Date: Tue, 13 Dec 2022 11:45:31 +0400 Subject: [PATCH] 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} + + + + + + + + + ); }