From bc8f0d02a3cbb0571f1ad65bd54b40cacd244a87 Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Wed, 6 Jul 2022 17:28:04 +0200 Subject: [PATCH] feat: seller and offer whitelist from env vars (#55) * feat: seller and offer whitelist from env vars * fix: add memoization to allow pagination * style: disable unused vars * tests: fix get sellers query mock * fix: tests so that the mocks work in other chains * chore: add lint-staged * fix: memoized merged and sorted offers * remove refactored hook * tests: fix mock * test: add mocks and fix slicing * tests: fix e2e * fix: offers slice end index * fix: duplicate offers on infinite query * review change requests Co-authored-by: Albert Folch --- .env.example | 15 +- .husky/pre-commit | 2 +- e2e-tests/Explore.spec.ts | 10 +- e2e-tests/PublicAccount.spec.ts | 4 +- e2e-tests/environment.ts | 5 +- e2e-tests/mocks/mockGetBase.ts | 17 +- e2e-tests/mocks/mockGetOfferById.ts | 25 + package-lock.json | 787 ++++++++++++++++++ package.json | 12 +- src/lib/config.ts | 22 +- .../utils/hooks/offers/__tests_/memo.test.ts | 83 ++ src/lib/utils/hooks/offers/getOffers.ts | 164 +++- src/lib/utils/hooks/offers/graphql.ts | 12 +- src/lib/utils/hooks/offers/memo.ts | 51 ++ src/lib/utils/hooks/offers/types.ts | 7 + .../utils/hooks/offers/useInfiniteOffers.ts | 19 +- src/lib/utils/hooks/offers/useOffer.ts | 61 +- src/lib/utils/hooks/offers/useOffers.ts | 11 +- src/lib/utils/hooks/useSellers.ts | 47 +- src/lib/utils/validators.ts | 9 +- tsconfig.json | 11 +- 21 files changed, 1215 insertions(+), 159 deletions(-) create mode 100644 e2e-tests/mocks/mockGetOfferById.ts create mode 100644 src/lib/utils/hooks/offers/__tests_/memo.test.ts create mode 100644 src/lib/utils/hooks/offers/memo.ts diff --git a/.env.example b/.env.example index a18ef9ddd..ade9a6585 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,14 @@ -REACT_APP_CHAIN_ID=1234 \ No newline at end of file +REACT_APP_CHAIN_ID=1234 +REACT_APP_WIDGETS_URL= + +# A Biconomy API key can be set here, to allow meta transactions in the widgets +REACT_APP_META_TX_API_KEY= + +# Comma-separated default list of seller IDs that are shown in the app +REACT_APP_SELLER_WHITELIST= + +# Comma-separated default list of offer IDs that are shown in the app +REACT_APP_OFFER_WHITELIST= + +# +REACT_APP_ENABLE_WHITELISTS=false diff --git a/.husky/pre-commit b/.husky/pre-commit index 97de70eaa..d24fdfc60 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -npm run prettier && npm run lint && git add -A . +npx lint-staged diff --git a/e2e-tests/Explore.spec.ts b/e2e-tests/Explore.spec.ts index fa6f565cb..ed7b91b81 100644 --- a/e2e-tests/Explore.spec.ts +++ b/e2e-tests/Explore.spec.ts @@ -286,7 +286,6 @@ test.describe("Explore page", () => { const numberOfOffers = 17; const offers: Offer[] = await getFirstNOffers(numberOfOffers); - const offers1stPage = offers.slice(0, offersPerPage); const offers2ndPage = offers.slice(offersPerPage); expect(offers2ndPage.length).toStrictEqual( numberOfOffers - offersPerPage @@ -296,7 +295,7 @@ test.describe("Explore page", () => { options: { mockGetOffers: { offersPerPage: { - offersList: [offers1stPage, offers2ndPage], + offersList: [offers2ndPage], countOffersPerPage: offersPerPage } } @@ -432,9 +431,6 @@ test.describe("Explore page", () => { const offers1stPage = offers.slice(0, offersPerPage); const offers2ndPage = offers.slice(offersPerPage); - expect(offers2ndPage.length).toStrictEqual( - numberOfOffers - offersPerPage - ); await mockSubgraph({ page, @@ -469,11 +465,11 @@ test.describe("Explore page", () => { uiOffers = page.locator("[data-testid=offer]"); offerCount = await uiOffers.count(); - expect(offerCount).toStrictEqual(offers.length - offersPerPage); + expect(offerCount).toStrictEqual(offers.length - visibleOffersPerPage); for (let i = 0; i < offerCount; i++) { const offer = uiOffers.nth(i); - const expectedOffer = offers[offersPerPage + i]; + const expectedOffer = offers[visibleOffersPerPage + i]; await assertOffer(offer, expectedOffer); } }); diff --git a/e2e-tests/PublicAccount.spec.ts b/e2e-tests/PublicAccount.spec.ts index 570b68801..e9598f349 100644 --- a/e2e-tests/PublicAccount.spec.ts +++ b/e2e-tests/PublicAccount.spec.ts @@ -154,9 +154,7 @@ test.describe("Public Account page", () => { expect(offerCount).toStrictEqual(visibleOffersPerPage); await scrollDown(page); offerCount = await offers.count(); - expect(offerCount).toStrictEqual( - visibleOffersPerPage + secondPageOffers.length - ); + expect(offerCount).toStrictEqual(allOffers.length); }); test("should filter out invalid offers", async ({ page }) => { diff --git a/e2e-tests/environment.ts b/e2e-tests/environment.ts index 65df92f65..88d539a44 100644 --- a/e2e-tests/environment.ts +++ b/e2e-tests/environment.ts @@ -1,4 +1,7 @@ import { CONFIG } from "../src/lib/config"; export const graphqlEndpoint = - "**/" + CONFIG.subgraphUrl.substring("https://".length); + "**/" + + (CONFIG.subgraphUrl.indexOf("https") !== -1 + ? CONFIG.subgraphUrl.substring("https://".length) + : CONFIG.subgraphUrl.substring("http://".length)); diff --git a/e2e-tests/mocks/mockGetBase.ts b/e2e-tests/mocks/mockGetBase.ts index 655e77509..e2375fba2 100644 --- a/e2e-tests/mocks/mockGetBase.ts +++ b/e2e-tests/mocks/mockGetBase.ts @@ -9,6 +9,10 @@ import { mockGetExchanges, MockProps as mockGetExchangesProps } from "./mockGetExchanges"; +import { + mockGetOfferById, + MockProps as mockGetOfferByIdProps +} from "./mockGetOfferById"; import { mockGetOffers, MockProps as mockGetOffersProps @@ -26,6 +30,7 @@ interface MockSubgraphProps { page: Page; options?: Partial<{ mockGetOffers?: mockGetOffersProps["options"]; + mockGetOfferById?: mockGetOfferByIdProps["options"]; mockGetBrands?: mockGetBrandsProps["options"]; mockGetTokens?: mockGetTokensProps["options"]; mockGetSellers?: mockGetSellersProps["options"]; @@ -46,16 +51,22 @@ export async function mockSubgraph({ page, options }: MockSubgraphProps) { const isBaseEntitiesRequest = postData?.includes("baseMetadataEntities("); const isBrandsRequest = postData?.includes("productV1MetadataEntities"); const isExchangeTokensRequest = postData?.includes("exchangeTokens"); - const isGetSingleBaseEntity = postData?.includes("GetOfferById("); // iframe widget makes this request - const isGetSellersReq = postData?.includes("GetSellers"); + const isGetOfferByIdReq = postData?.includes("getOfferByIdQuery"); // iframe widget makes this request + const isGetSellersReq = + postData?.includes("getSellersQuery") || postData?.includes("GetSellers"); const isGetExchangesReq = postData?.includes("GetExchanges"); let mockResponse; - if (isBaseEntitiesRequest || isGetSingleBaseEntity) { + if (isBaseEntitiesRequest) { mockResponse = await mockGetOffers({ postData, options: options?.mockGetOffers || {} }); + } else if (isGetOfferByIdReq) { + mockResponse = await mockGetOfferById({ + postData, + options: options?.mockGetOfferById || {} + }); } else if (isBrandsRequest) { mockResponse = await mockGetBrands({ postData, diff --git a/e2e-tests/mocks/mockGetOfferById.ts b/e2e-tests/mocks/mockGetOfferById.ts new file mode 100644 index 000000000..0eb163d1f --- /dev/null +++ b/e2e-tests/mocks/mockGetOfferById.ts @@ -0,0 +1,25 @@ +import { Offer } from "../../src/lib/types/offer"; +import { defaultMockOffers } from "./defaultMockOffers"; +import { CustomResponse } from "./mockGetBase"; + +export interface MockProps { + postData: string | null; + options?: { + offer?: Offer | null; + response?: Partial; + }; +} +export async function mockGetOfferById({ + options: { offer = defaultMockOffers[0], response } = {} +}: MockProps) { + const options = { + status: 200, + body: JSON.stringify({ + data: { offer } + }), + contentType: "application/json", + ...response + }; + + return options; +} diff --git a/package-lock.json b/package-lock.json index 855ec4ff2..e6e7d72d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,7 @@ "eslint-plugin-unused-imports": "^2.0.0", "husky": "^8.0.1", "istanbul": "^0.4.5", + "lint-staged": "^13.0.3", "nyc": "^15.1.0", "prettier": "^2.7.1", "react-app-rewire-alias": "^1.1.7", @@ -6268,6 +6269,15 @@ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -7242,6 +7252,78 @@ "node": ">=6" } }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -8593,6 +8675,12 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -13633,6 +13721,268 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/lint-staged": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.0.3.tgz", + "integrity": "sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug==", + "dev": true, + "dependencies": { + "cli-truncate": "^3.1.0", + "colorette": "^2.0.17", + "commander": "^9.3.0", + "debug": "^4.3.4", + "execa": "^6.1.0", + "lilconfig": "2.0.5", + "listr2": "^4.0.5", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.2", + "pidtree": "^0.6.0", + "string-argv": "^0.3.1", + "yaml": "^2.1.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", + "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/lint-staged/node_modules/execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "dev": true, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/yaml": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", + "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/listr2": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz", + "integrity": "sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.5", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/rxjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", + "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/listr2/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -13712,6 +14062,41 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -14812,6 +15197,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -17179,6 +17576,19 @@ "node": ">=10" } }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/retimer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", @@ -17201,6 +17611,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -17722,6 +18138,46 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", + "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -17956,6 +18412,15 @@ } ] }, + "node_modules/string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -18579,6 +19044,12 @@ "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==" }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -24483,6 +24954,12 @@ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -25220,6 +25697,53 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "requires": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -26243,6 +26767,12 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -30014,6 +30544,180 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "lint-staged": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.0.3.tgz", + "integrity": "sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug==", + "dev": true, + "requires": { + "cli-truncate": "^3.1.0", + "colorette": "^2.0.17", + "commander": "^9.3.0", + "debug": "^4.3.4", + "execa": "^6.1.0", + "lilconfig": "2.0.5", + "listr2": "^4.0.5", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.2", + "pidtree": "^0.6.0", + "string-argv": "^0.3.1", + "yaml": "^2.1.1" + }, + "dependencies": { + "commander": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", + "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==", + "dev": true + }, + "execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + } + }, + "human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "dev": true + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + } + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + }, + "yaml": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", + "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==", + "dev": true + } + } + }, + "listr2": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz", + "integrity": "sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==", + "dev": true, + "requires": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.5", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "rxjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", + "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -30084,6 +30788,31 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" }, + "log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + } + } + }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -30925,6 +31654,12 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, + "pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -32463,6 +33198,16 @@ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==" }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "retimer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", @@ -32478,6 +33223,12 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -32886,6 +33637,30 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", + "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true + } + } + }, "sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -33070,6 +33845,12 @@ } } }, + "string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -33523,6 +34304,12 @@ "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==" }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, "thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", diff --git a/package.json b/package.json index 8c2d9dae2..9484bc8e6 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,13 @@ "coverage:report": "npm run coverage:unit && npm run coverage:e2e", "postinstall": "npx playwright install --with-deps chromium" }, - "husky": { - "hooks": { - "pre-commit": "npm run prettier && npm run lint" - } + "lint-staged": { + "src/**/*.+(js|json|ts|tsx)": [ + "npm run lint" + ], + "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [ + "prettier --write" + ] }, "dependencies": { "@bosonprotocol/common": "^1.7.0", @@ -88,6 +91,7 @@ "eslint-plugin-unused-imports": "^2.0.0", "husky": "^8.0.1", "istanbul": "^0.4.5", + "lint-staged": "^13.0.3", "nyc": "^15.1.0", "prettier": "^2.7.1", "react-app-rewire-alias": "^1.1.7", diff --git a/src/lib/config.ts b/src/lib/config.ts index 0f607ef88..02ae5b542 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -14,5 +14,25 @@ export const CONFIG = { ipfsMetadataUrl: process.env.REACT_APP_IPFS_METADATA_URL || "https://ipfs.infura.io:5001", sentryDSNUrl: - "https://ff9c04ed823a4658bc5de78945961937@o992661.ingest.sentry.io/6455090" + "https://ff9c04ed823a4658bc5de78945961937@o992661.ingest.sentry.io/6455090", + metaTransactionsApiKey: process.env.REACT_APP_META_TX_API_KEY, + sellerWhitelist: parseWhitelist(process.env.REACT_APP_SELLER_WHITELIST), + offerWhitelist: parseWhitelist(process.env.REACT_APP_OFFER_WHITELIST), + enableWhitelists: stringToBoolean(process.env.REACT_APP_ENABLE_WHITELISTS) }; + +function stringToBoolean(value?: string) { + if (typeof value === "string") { + return ["1", "true"].includes(value); + } + + return Boolean(value); +} + +function parseWhitelist(value?: string): string[] | undefined { + if (value) { + return value.split(","); + } + + return undefined; +} diff --git a/src/lib/utils/hooks/offers/__tests_/memo.test.ts b/src/lib/utils/hooks/offers/__tests_/memo.test.ts new file mode 100644 index 000000000..2b2850dfc --- /dev/null +++ b/src/lib/utils/hooks/offers/__tests_/memo.test.ts @@ -0,0 +1,83 @@ +import { getFirstNOffers } from "../../../../../../e2e-tests/utils/getFirstNOffers"; +import { memoMergeAndSortOffers } from "../memo"; + +jest.mock("@bosonprotocol/ipfs-storage", () => { + return { + validation: { + validateMetadata: () => true + } + }; +}); + +describe("#makeMemoizedMergeAndSortOffers()", () => { + test("merge and sort offers correctly", () => { + // offers with ids 000 - 009 + const sellerWhitelistOffers = getFirstNOffers(10); + const sellerWhitelistResult = { + baseMetadataEntities: sellerWhitelistOffers.map((offer) => ({ + offer + })) + }; + // offers with ids 000 - 004 + const offerWhitelistOffers = getFirstNOffers(5); + const offerWhitelistResult = { + baseMetadataEntities: offerWhitelistOffers.map((offer) => ({ + offer + })) + }; + + const memoizedMergeAndSortOffers = memoMergeAndSortOffers(); + + const mergedAndSortedOffers = memoizedMergeAndSortOffers( + "key", + sellerWhitelistResult, + offerWhitelistResult + ); + + // remove duplicates + expect(mergedAndSortedOffers.length).toBe(10); + // correct order + for (let i = 0; i < 10; i++) { + expect(mergedAndSortedOffers[i].id).toBe(`00${i}`); + } + }); + + test("memoize previous results", () => { + const offers = getFirstNOffers(10); + + // offers with ids 000 - 004 + const sellerWhitelistResult = { + baseMetadataEntities: offers.slice(0, 5).map((offer) => ({ + offer + })) + }; + // offers with ids 004 - 009 + const offerWhitelistResult = { + baseMetadataEntities: offers.slice(5).map((offer) => ({ + offer + })) + }; + + const memoizedMergeAndSortOffers = memoMergeAndSortOffers(); + + const firstMergedAndSortedOffers = memoizedMergeAndSortOffers( + "firstKey", + sellerWhitelistResult, + offerWhitelistResult + ); + const secondMergedAndSortedOffers = memoizedMergeAndSortOffers( + "firstKey", + { baseMetadataEntities: [] }, + { baseMetadataEntities: [] } + ); + const thirdMergedAndSortedOffers = memoizedMergeAndSortOffers( + "thirdKey", + { baseMetadataEntities: [] }, + { baseMetadataEntities: [] } + ); + expect(firstMergedAndSortedOffers.length).toBe( + secondMergedAndSortedOffers.length + ); + expect(thirdMergedAndSortedOffers.length).toBe(0); + }); +}); diff --git a/src/lib/utils/hooks/offers/getOffers.ts b/src/lib/utils/hooks/offers/getOffers.ts index 2041a3cd3..d6332a412 100644 --- a/src/lib/utils/hooks/offers/getOffers.ts +++ b/src/lib/utils/hooks/offers/getOffers.ts @@ -1,10 +1,15 @@ import dayjs from "dayjs"; -import { Offer } from "../../../types/offer"; import { fetchSubgraph } from "../../core-components/subgraph"; -import { checkOfferMetadata } from "../../validators"; -import { getOffersQuery } from "./graphql"; -import { UseOffersProps } from "./types"; +import { buildGetOffersQuery } from "./graphql"; +import { memoMergeAndSortOffers } from "./memo"; +import { + UseOfferProps, + UseOffersProps, + WhitelistGetOffersResult +} from "./types"; + +const memoizedMergeAndSortOffers = memoMergeAndSortOffers(); export const getOffers = async (props: UseOffersProps) => { const dateNow = Date.now(); @@ -23,8 +28,6 @@ export const getOffers = async (props: UseOffersProps) => { : null; const variables = { - first: props.first, - skip: props.skip, validFromDate_lte: validFromDate_lte, validFromDate_gte: validFromDate_gte, validUntilDate_lte: validUntilDate_lte, @@ -35,36 +38,129 @@ export const getOffers = async (props: UseOffersProps) => { orderBy: "name", orderDirection: "asc", quantityAvailable_lte: props.quantityAvailable_lte, - type: props.type + type: props.type, + sellerWhitelist: props.sellerWhitelist || [], + offerWhitelist: props.offerWhitelist || [], + first: props.first, + skip: props.skip + }; + + const getOffersQueryArgs = { + exchangeToken: !!props.exchangeTokenAddress, + sellerId: !!props.sellerId, + validFromDate_lte: !!validFromDate_lte, + validFromDate_gte: !!validFromDate_gte, + validUntilDate_lte: !!validUntilDate_lte, + validUntilDate_gte: !!validUntilDate_gte, + skip: !!props.skip, + quantityAvailable_lte: ![null, undefined].includes( + props.quantityAvailable_lte as null + ), + offer: false + }; + + return fetchWhitelistOffers(props, getOffersQueryArgs, variables); +}; + +export async function getOfferById( + id: string, + props: Omit +) { + const now = Math.floor(Date.now() / 1000); + const validFromDate_lte = props.valid ? now + "" : null; + const validUntilDate_gte = props.valid ? now + "" : null; + + const variables = { + offer: id, + validFromDate_lte: validFromDate_lte, + validUntilDate_gte: validUntilDate_gte, + name_contains_nocase: props.name || "", + exchangeToken: props.exchangeTokenAddress, + sellerId: props.sellerId, + sellerWhitelist: props.sellerWhitelist || [], + offerWhitelist: props.offerWhitelist || [] }; - const result = await fetchSubgraph<{ - baseMetadataEntities: { offer: Offer }[]; - }>( - getOffersQuery({ - exchangeToken: !!props.exchangeTokenAddress, - sellerId: !!props.sellerId, - validFromDate_lte: !!validFromDate_lte, - validFromDate_gte: !!validFromDate_gte, - validUntilDate_lte: !!validUntilDate_lte, - validUntilDate_gte: !!validUntilDate_gte, - skip: !!props.skip, - quantityAvailable_lte: ![null, undefined].includes( - props.quantityAvailable_lte as null - ), - offer: false - }), + const getOffersQueryArgs = { + exchangeToken: !!props.exchangeTokenAddress, + sellerId: !!props.sellerId, + validFromDate_lte: !!validFromDate_lte, + validFromDate_gte: false, + validUntilDate_lte: false, + validUntilDate_gte: !!validUntilDate_gte, + skip: false, + quantityAvailable_lte: false, + offer: true + }; + + const [offer] = await fetchWhitelistOffers( + props, + getOffersQueryArgs, variables ); - return result?.baseMetadataEntities?.map((base) => { - const isValid = checkOfferMetadata(base.offer); - return { - ...base.offer, - metadata: { - ...base.offer.metadata, - imageUrl: `https://picsum.photos/seed/${base.offer.id}/700` - }, - isValid - } as Offer; + return offer; +} + +async function fetchWhitelistOffers( + props: UseOffersProps, + getOffersQueryArgs: Omit< + Parameters[0], + "sellerWhitelist" | "offerWhitelist" + >, + queryVars: Record +) { + const sellerWhitelist = props.enableWhitelists + ? props.sellerWhitelist || [] + : null; + const offerWhitelist = props.enableWhitelists + ? props.offerWhitelist || [] + : null; + + const getSellerWhitelistOffersQuery = buildGetOffersQuery({ + ...getOffersQueryArgs, + sellerWhitelist: !!sellerWhitelist, + offerWhitelist: false }); -}; + const getOfferWhitelistOffersQuery = buildGetOffersQuery({ + ...getOffersQueryArgs, + sellerWhitelist: false, + offerWhitelist: !!offerWhitelist + }); + + const [sellerWhitelistResult, offerWhitelistResult] = await Promise.all([ + fetchSubgraph( + getSellerWhitelistOffersQuery, + queryVars + ), + fetchSubgraph( + getOfferWhitelistOffersQuery, + queryVars + ) + ]); + + const offers = memoizedMergeAndSortOffers( + getMergedAndSortedCacheKey(props), + sellerWhitelistResult, + offerWhitelistResult + ); + + return offers.slice(props.skip, getOffersSliceEnd(props.skip, props.first)); +} + +function getOffersSliceEnd(skip?: number, first?: number) { + if (skip && first) { + return skip + first; + } + + if (!skip && first) { + return first; + } + + return undefined; +} + +function getMergedAndSortedCacheKey(props: UseOffersProps) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { first, skip, ...rest } = props; + return JSON.stringify(rest); +} diff --git a/src/lib/utils/hooks/offers/graphql.ts b/src/lib/utils/hooks/offers/graphql.ts index c33cde922..61474e784 100644 --- a/src/lib/utils/hooks/offers/graphql.ts +++ b/src/lib/utils/hooks/offers/graphql.ts @@ -40,7 +40,7 @@ export const offerGraphQl = gql` } `; -export function getOffersQuery({ +export function buildGetOffersQuery({ exchangeToken, sellerId, validFromDate_lte, @@ -49,7 +49,9 @@ export function getOffersQuery({ validUntilDate_gte, skip, offer, - quantityAvailable_lte + quantityAvailable_lte, + sellerWhitelist, + offerWhitelist }: { exchangeToken: boolean; sellerId: boolean; @@ -60,6 +62,8 @@ export function getOffersQuery({ skip: boolean; offer: boolean; quantityAvailable_lte: boolean; + sellerWhitelist: boolean; + offerWhitelist: boolean; }) { return gql` query GetOffers( @@ -76,6 +80,8 @@ export function getOffersQuery({ $orderDirection: String ${offer ? "$offer: String" : ""} ${quantityAvailable_lte ? "$quantityAvailable_lte: Int" : ""} + ${sellerWhitelist ? "$sellerWhitelist: [String!]" : ""} + ${offerWhitelist ? "$offerWhitelist: [String!]" : ""} ) { baseMetadataEntities( first: $first @@ -95,6 +101,8 @@ export function getOffersQuery({ ? "quantityAvailable_lte: $quantityAvailable_lte" : "" } + ${sellerWhitelist ? "seller_in: $sellerWhitelist" : ""} + ${offerWhitelist ? "offer_in: $offerWhitelist" : ""} name_contains_nocase: $name_contains_nocase } ) { diff --git a/src/lib/utils/hooks/offers/memo.ts b/src/lib/utils/hooks/offers/memo.ts new file mode 100644 index 000000000..65d0aa398 --- /dev/null +++ b/src/lib/utils/hooks/offers/memo.ts @@ -0,0 +1,51 @@ +import { Offer } from "../../../types/offer"; +import { checkOfferMetadata } from "../../validators"; +import { WhitelistGetOffersResult } from "./types"; + +export function memoMergeAndSortOffers() { + const cache: Record = {}; + + function mergeAndSortWhitelistResults( + cacheKey: string, + sellerWhitelistResult: WhitelistGetOffersResult, + offerWhitelistResult: WhitelistGetOffersResult + ): Offer[] { + const cachedOffers = cache[cacheKey] || []; + const sellerWhitelistOffers = + sellerWhitelistResult?.baseMetadataEntities || []; + const offerWhitelistOffers = + offerWhitelistResult?.baseMetadataEntities || []; + + const mergedWhitelistOffers = [ + ...sellerWhitelistOffers, + ...offerWhitelistOffers + ].map((base) => { + const isValid = checkOfferMetadata(base.offer); + return { + ...base.offer, + metadata: { + ...base.offer.metadata, + imageUrl: `https://picsum.photos/seed/${base.offer.id}/700` + }, + isValid + } as Offer; + }); + const mergedOffers = [...cachedOffers, ...mergedWhitelistOffers]; + const ids = mergedOffers.map((offer) => Number(offer.id)); + const uniqueOffers = mergedOffers.filter( + (offer, index) => !ids.includes(Number(offer.id), index + 1) + ); + const sortedOffers = uniqueOffers.sort((a, b) => { + if (a.metadata.name && b.metadata.name) { + return a.metadata.name.localeCompare(b.metadata.name); + } + return 0; + }); + + cache[cacheKey] = sortedOffers; + + return sortedOffers; + } + + return mergeAndSortWhitelistResults; +} diff --git a/src/lib/utils/hooks/offers/types.ts b/src/lib/utils/hooks/offers/types.ts index 9f1d86868..4807613b2 100644 --- a/src/lib/utils/hooks/offers/types.ts +++ b/src/lib/utils/hooks/offers/types.ts @@ -1,5 +1,9 @@ import { Offer } from "../../../types/offer"; +export type WhitelistGetOffersResult = { + baseMetadataEntities: { offer: Offer }[]; +}; + interface CommonProps { name?: string; // TODO: to delete once brand is supported brand?: string; // TODO: not supported yet @@ -9,6 +13,9 @@ interface CommonProps { sellerId?: Offer["seller"]["id"]; first?: number; skip?: number; + sellerWhitelist?: string[]; + offerWhitelist?: string[]; + enableWhitelists?: boolean; } export interface UseOffersProps extends CommonProps { diff --git a/src/lib/utils/hooks/offers/useInfiniteOffers.ts b/src/lib/utils/hooks/offers/useInfiniteOffers.ts index c49a8cc22..0d41316a2 100644 --- a/src/lib/utils/hooks/offers/useInfiniteOffers.ts +++ b/src/lib/utils/hooks/offers/useInfiniteOffers.ts @@ -1,5 +1,6 @@ import { useInfiniteQuery } from "react-query"; +import { CONFIG } from "../../../config"; import { getOffers } from "./getOffers"; import { UseOffersProps } from "./types"; @@ -10,15 +11,25 @@ export function useInfiniteOffers( keepPreviousData?: boolean; } = {} ) { - const queryKey = ["offers", props]; + props = { + ...props, + sellerWhitelist: CONFIG.sellerWhitelist, + offerWhitelist: CONFIG.offerWhitelist, + enableWhitelists: CONFIG.enableWhitelists + }; return useInfiniteQuery( - queryKey, + ["offers", "infinite", props.sellerId], async (context) => { const skip = context.pageParam || 0; - return getOffers({ ...props, skip }); + return getOffers({ + ...props, + skip + }); }, { - ...options + ...options, + refetchOnWindowFocus: false, + refetchOnMount: false } ); } diff --git a/src/lib/utils/hooks/offers/useOffer.ts b/src/lib/utils/hooks/offers/useOffer.ts index 74319ceed..64b871838 100644 --- a/src/lib/utils/hooks/offers/useOffer.ts +++ b/src/lib/utils/hooks/offers/useOffer.ts @@ -1,9 +1,7 @@ import { useQuery } from "react-query"; -import { Offer } from "../../../types/offer"; -import { fetchSubgraph } from "../../core-components/subgraph"; -import { checkOfferMetadata } from "../../validators"; -import { getOffersQuery } from "./graphql"; +import { CONFIG } from "../../../config"; +import { getOfferById } from "./getOffers"; import { UseOfferProps } from "./types"; export function useOffer( @@ -15,53 +13,20 @@ export function useOffer( return useQuery( ["offer", offerId], async () => { - const offer = await getOfferById(offerId, restProps); - if (offer) { - const isMetadataValid = checkOfferMetadata(offer); - offer.isValid = isMetadataValid; - return { - ...offer, - metadata: { - ...offer.metadata, - imageUrl: `https://picsum.photos/seed/${offer.id}/700` - } - }; - } - return null; + restProps = { + sellerWhitelist: CONFIG.sellerWhitelist, + offerWhitelist: CONFIG.offerWhitelist, + enableWhitelists: CONFIG.enableWhitelists, + ...restProps + }; + const offer = await getOfferById(offerId, { + ...restProps + }); + + return offer; }, { ...options } ); } - -async function getOfferById(id: string, props: Omit) { - const now = Math.floor(Date.now() / 1000); - const validFromDate_lte = props.valid ? now + "" : null; - const validUntilDate_gte = props.valid ? now + "" : null; - - const result = await fetchSubgraph<{ - baseMetadataEntities: { offer: Offer }[]; - }>( - getOffersQuery({ - exchangeToken: !!props.exchangeTokenAddress, - sellerId: !!props.sellerId, - validFromDate_lte: !!validFromDate_lte, - validFromDate_gte: false, - validUntilDate_lte: false, - validUntilDate_gte: !!validUntilDate_gte, - skip: !!props.skip, - quantityAvailable_lte: false, - offer: true - }), - { - offer: id, - validFromDate_lte: validFromDate_lte, - validUntilDate_gte: validUntilDate_gte, - name_contains_nocase: props.name || "", - exchangeToken: props.exchangeTokenAddress, - sellerId: props.sellerId - } - ); - return result.baseMetadataEntities[0]?.offer; -} diff --git a/src/lib/utils/hooks/offers/useOffers.ts b/src/lib/utils/hooks/offers/useOffers.ts index c06fae8d5..8fc52b908 100644 --- a/src/lib/utils/hooks/offers/useOffers.ts +++ b/src/lib/utils/hooks/offers/useOffers.ts @@ -1,5 +1,6 @@ import { useQuery } from "react-query"; +import { CONFIG } from "../../../config"; import { getOffers } from "./getOffers"; import { UseOffersProps } from "./types"; @@ -9,13 +10,17 @@ export function useOffers( enabled?: boolean; } = {} ) { + props = { + ...props, + sellerWhitelist: CONFIG.sellerWhitelist, + offerWhitelist: CONFIG.offerWhitelist, + enableWhitelists: CONFIG.enableWhitelists + }; return useQuery( ["offers", props], async () => { return getOffers(props); }, - { - ...options - } + options ); } diff --git a/src/lib/utils/hooks/useSellers.ts b/src/lib/utils/hooks/useSellers.ts index 7fe8f5278..abea073b8 100644 --- a/src/lib/utils/hooks/useSellers.ts +++ b/src/lib/utils/hooks/useSellers.ts @@ -1,46 +1,21 @@ -import { gql } from "graphql-request"; +import { accounts, subgraph } from "@bosonprotocol/core-sdk"; import { useQuery } from "react-query"; -import { Offer } from "../../../lib/types/offer"; -import { fetchSubgraph } from "../core-components/subgraph"; +import { CONFIG } from "../../../lib/config"; interface Props { admin?: string; } + export function useSellers(props: Props = {}) { return useQuery(["sellers", props], async () => { - const result = await fetchSubgraph<{ - sellers: Pick< - Offer["seller"], - "id" | "operator" | "admin" | "clerk" | "treasury" | "active" - >[]; - }>( - gql` - query GetSellers( - $orderBy: String - $orderDirection: String - $admin: String - ) { - sellers( - orderBy: $orderBy - orderDirection: $orderDirection - ${props.admin ? "where: { admin: $admin }" : ""} - ) { - id - operator - admin - clerk - treasury - active - } - } - `, - { - orderBy: "sellerId", - orderDirection: "asc", - ...(props.admin && { admin: props.admin }) - } - ); - return result?.sellers ?? []; + return accounts.subgraph.getSellers(CONFIG.subgraphUrl, { + sellersFilter: { + admin: props.admin, + id_in: CONFIG.enableWhitelists ? CONFIG.sellerWhitelist : null + }, + sellersOrderBy: subgraph.Seller_OrderBy.SellerId, + sellersOrderDirection: subgraph.OrderDirection.Asc + }); }); } diff --git a/src/lib/utils/validators.ts b/src/lib/utils/validators.ts index 2070a0355..97c840ecf 100644 --- a/src/lib/utils/validators.ts +++ b/src/lib/utils/validators.ts @@ -1,9 +1,12 @@ import { AnyMetadata } from "@bosonprotocol/common"; +import { subgraph } from "@bosonprotocol/core-sdk"; import { validation } from "@bosonprotocol/ipfs-storage"; import { Offer } from "../types/offer"; -export function checkOfferMetadata(offer: Offer): boolean { +export function checkOfferMetadata( + offer: Offer | subgraph.OfferFieldsFragment +): boolean { const isValid = isOfferMetadataValid(offer); if (!isValid) { @@ -15,7 +18,9 @@ export function checkOfferMetadata(offer: Offer): boolean { return isValid; } -function isOfferMetadataValid(offer: Offer): boolean { +function isOfferMetadataValid( + offer: Offer | subgraph.OfferFieldsFragment +): boolean { if (!offer.metadata) { return false; } diff --git a/tsconfig.json b/tsconfig.json index e2af02ade..3dc34c5aa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -20,8 +16,5 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": [ - "src", - "e2e-tests" - ] + "include": ["src", "e2e-tests"] }