diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6884d1b8..f0c64f04 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -9,7 +9,7 @@ jobs: test: strategy: matrix: - node-version: [14.x] + node: [16.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: diff --git a/package.json b/package.json index 5a39a261..02cf8a65 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@material-ui/core": "^4.12.3", "@material-ui/lab": "^4.0.0-alpha.60", "@openzeppelin/contracts": "^4.8.1", + "@reduxjs/toolkit": "^1.8.1", "ace-builds": "^1.15.0", "bignumber.js": "^9.0.2", "ethers": "^5.7.2", @@ -36,6 +37,7 @@ "react-csv-reader": "^3.4.0", "react-dom": "^17.0.2", "react-dropzone": "^14.2.3", + "react-redux": "^8.0.1", "react-scripts": "^4.0.3", "react-svg": "^15.1.7", "react-virtualized-auto-sizer": "^1.0.6", @@ -48,6 +50,7 @@ "@simbathesailor/use-what-changed": "^2.0.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^12.1.3", + "@testing-library/react-hooks": "^8.0.1", "@typechain/ethers-v5": "^7.1.2", "@types/chai": "^4.3.4", "@types/chai-as-promised": "^7.1.5", diff --git a/src/App.tsx b/src/App.tsx index 0910d8d4..2a5a5be3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,9 @@ import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; import { BaseTransaction, GatewayTransactionDetails } from "@gnosis.pm/safe-apps-sdk"; import { Breadcrumb, BreadcrumbElement, Button, Card, Divider, Loader } from "@gnosis.pm/safe-react-components"; import { setUseWhatChange } from "@simbathesailor/use-what-changed"; -import React, { useCallback, useState, useContext } from "react"; +import React, { useCallback, useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { Unsubscribe } from "redux"; import styled from "styled-components"; import { CSVForm } from "./components/CSVForm"; @@ -10,26 +12,41 @@ import { Header } from "./components/Header"; import { Loading } from "./components/Loading"; import { Summary } from "./components/Summary"; import { TransactionStatusScreen } from "./components/TransactionStatusScreen"; -import { MessageContext } from "./contexts/MessageContextProvider"; -import { useBalances } from "./hooks/balances"; +import { useEnsResolver } from "./hooks/ens"; import { useTokenList } from "./hooks/token"; -import { AssetTransfer, CollectibleTransfer, Transfer } from "./parser/csvParser"; +import { AssetTransfer, CollectibleTransfer, useCsvParser } from "./hooks/useCsvParser"; +import { useGetAssetBalanceQuery, useGetAllNFTsQuery } from "./stores/api/balanceApi"; +import { setupParserListener } from "./stores/middleware/parseListener"; +import { setSafeInfo } from "./stores/slices/safeInfoSlice"; +import { RootState, startAppListening } from "./stores/store"; import { buildAssetTransfers, buildCollectibleTransfers } from "./transfers/transfers"; setUseWhatChange(process.env.NODE_ENV === "development"); const App: React.FC = () => { const { isLoading } = useTokenList(); - const balanceLoader = useBalances(); - const [tokenTransfers, setTokenTransfers] = useState([]); - const { messages } = useContext(MessageContext); - const [parsing, setParsing] = useState(false); - const { sdk } = useSafeAppsSDK(); + const { sdk, safe } = useSafeAppsSDK(); + const assetBalanceQuery = useGetAssetBalanceQuery(); + const nftBalanceQuery = useGetAllNFTsQuery(); + + const { messages } = useSelector((state: RootState) => state.messages); + const { transfers, parsing } = useSelector((state: RootState) => state.csvEditor); + const [pendingTx, setPendingTx] = useState(); - const assetTransfers = tokenTransfers.filter( + const { parseCsv } = useCsvParser(); + const ensResolver = useEnsResolver(); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(setSafeInfo(safe)); + const subscriptions: Unsubscribe[] = [setupParserListener(startAppListening, parseCsv, ensResolver)]; + return () => subscriptions.forEach((unsubscribe) => unsubscribe()); + }, [dispatch, safe, sdk, parseCsv, ensResolver]); + + const assetTransfers = transfers.filter( (transfer) => transfer.token_type === "erc20" || transfer.token_type === "native", ) as AssetTransfer[]; - const collectibleTransfers = tokenTransfers.filter( + const collectibleTransfers = transfers.filter( (transfer) => transfer.token_type === "erc1155" || transfer.token_type === "erc721", ) as CollectibleTransfer[]; @@ -39,7 +56,6 @@ const App: React.FC = () => { txs.push(...buildAssetTransfers(assetTransfers)); txs.push(...buildCollectibleTransfers(collectibleTransfers)); - console.log(`Encoded ${txs.length} transfers.`); const sendTxResponse = await sdk.txs.send({ txs }); const safeTx = await sdk.txs.getBySafeTxHash(sendTxResponse.safeTxHash); setPendingTx(safeTx); @@ -53,7 +69,7 @@ const App: React.FC = () => {
{ <> - {isLoading || balanceLoader.isLoading ? ( + {isLoading || assetBalanceQuery.isLoading || nftBalanceQuery.isLoading ? ( ) : ( @@ -62,7 +78,7 @@ const App: React.FC = () => { - + )} @@ -79,7 +95,7 @@ const App: React.FC = () => { size="lg" color={messages.length === 0 ? "primary" : "error"} onClick={submitTx} - disabled={parsing || tokenTransfers.length + collectibleTransfers.length === 0} + disabled={parsing || transfers.length + collectibleTransfers.length === 0} > {parsing ? ( diff --git a/src/__tests__/balanceCheck.test.ts b/src/__tests__/balanceCheck.test.ts index fb7c5255..8cd754a6 100644 --- a/src/__tests__/balanceCheck.test.ts +++ b/src/__tests__/balanceCheck.test.ts @@ -1,9 +1,8 @@ -import BigNumber from "bignumber.js"; import { expect } from "chai"; +import { AssetBalance, NFTBalance } from "src/stores/api/balanceApi"; -import { AssetBalance, CollectibleBalance } from "../hooks/balances"; +import { AssetTransfer, CollectibleTransfer } from "../hooks/useCsvParser"; import { assetTransfersToSummary, checkAllBalances } from "../parser/balanceCheck"; -import { AssetTransfer, CollectibleTransfer } from "../parser/csvParser"; import { testData } from "../test/util"; import { toWei } from "../utils"; @@ -13,7 +12,7 @@ describe("transferToSummary and check balances", () => { { token_type: "native", tokenAddress: null, - amount: new BigNumber(1), + amount: "1", receiver: testData.addresses.receiver1, decimals: 18, symbol: "ETH", @@ -22,7 +21,7 @@ describe("transferToSummary and check balances", () => { { token_type: "native", tokenAddress: null, - amount: new BigNumber(2), + amount: "2", receiver: testData.addresses.receiver2, decimals: 18, symbol: "ETH", @@ -31,7 +30,7 @@ describe("transferToSummary and check balances", () => { { token_type: "native", tokenAddress: null, - amount: new BigNumber(3), + amount: "3", receiver: testData.addresses.receiver3, decimals: 18, symbol: "ETH", @@ -80,7 +79,7 @@ describe("transferToSummary and check balances", () => { { token_type: "native", tokenAddress: null, - amount: new BigNumber(0.1), + amount: "0.1", receiver: testData.addresses.receiver1, decimals: 18, symbol: "ETH", @@ -89,7 +88,7 @@ describe("transferToSummary and check balances", () => { { token_type: "native", tokenAddress: null, - amount: new BigNumber(0.01), + amount: "0.01", receiver: testData.addresses.receiver2, decimals: 18, symbol: "ETH", @@ -98,7 +97,7 @@ describe("transferToSummary and check balances", () => { { token_type: "native", tokenAddress: null, - amount: new BigNumber(0.001), + amount: "0.001", receiver: testData.addresses.receiver3, decimals: 18, symbol: "ETH", @@ -147,7 +146,7 @@ describe("transferToSummary and check balances", () => { { token_type: "erc20", tokenAddress: testData.unlistedERC20Token.address, - amount: new BigNumber(0.1), + amount: "0.1", receiver: testData.addresses.receiver1, decimals: 18, symbol: "ULT", @@ -156,7 +155,7 @@ describe("transferToSummary and check balances", () => { { token_type: "erc20", tokenAddress: testData.unlistedERC20Token.address, - amount: new BigNumber(0.01), + amount: "0.01", receiver: testData.addresses.receiver2, decimals: 18, symbol: "ULT", @@ -165,7 +164,7 @@ describe("transferToSummary and check balances", () => { { token_type: "erc20", tokenAddress: testData.unlistedERC20Token.address, - amount: new BigNumber(0.001), + amount: "0.001", receiver: testData.addresses.receiver3, decimals: 18, symbol: "ULT", @@ -226,7 +225,7 @@ describe("transferToSummary and check balances", () => { { token_type: "erc20", tokenAddress: testData.unlistedERC20Token.address, - amount: new BigNumber(1), + amount: "1", receiver: testData.addresses.receiver1, decimals: 18, symbol: "ULT", @@ -235,7 +234,7 @@ describe("transferToSummary and check balances", () => { { token_type: "erc20", tokenAddress: testData.unlistedERC20Token.address, - amount: new BigNumber(2), + amount: "2", receiver: testData.addresses.receiver2, decimals: 18, symbol: "ULT", @@ -244,7 +243,7 @@ describe("transferToSummary and check balances", () => { { token_type: "erc20", tokenAddress: testData.unlistedERC20Token.address, - amount: new BigNumber(3), + amount: "3", receiver: testData.addresses.receiver3, decimals: 18, symbol: "ULT", @@ -305,7 +304,7 @@ describe("transferToSummary and check balances", () => { { token_type: "erc20", tokenAddress: testData.unlistedERC20Token.address, - amount: new BigNumber(1.1), + amount: "1.1", receiver: testData.addresses.receiver1, decimals: 18, symbol: "ULT", @@ -314,7 +313,7 @@ describe("transferToSummary and check balances", () => { { token_type: "erc20", tokenAddress: testData.unlistedERC20Token.address, - amount: new BigNumber(2), + amount: "2", receiver: testData.addresses.receiver2, decimals: 18, symbol: "ULT", @@ -323,7 +322,7 @@ describe("transferToSummary and check balances", () => { { token_type: "erc20", tokenAddress: testData.unlistedERC20Token.address, - amount: new BigNumber(3.3), + amount: "3.3", receiver: testData.addresses.receiver3, decimals: 18, symbol: "ULT", @@ -332,7 +331,7 @@ describe("transferToSummary and check balances", () => { { token_type: "native", tokenAddress: null, - amount: new BigNumber(3), + amount: "3", receiver: testData.addresses.receiver1, decimals: 18, symbol: "ETH", @@ -341,7 +340,7 @@ describe("transferToSummary and check balances", () => { { token_type: "native", tokenAddress: null, - amount: new BigNumber(0.33), + amount: "0.33", receiver: testData.addresses.receiver1, decimals: 18, symbol: "ETH", @@ -453,67 +452,86 @@ describe("transferToSummary and check balances", () => { { token_type: "erc721", tokenAddress: testData.unlistedERC20Token.address, - tokenId: new BigNumber(69), + tokenId: "69", receiver: testData.addresses.receiver1, tokenName: "Test Collectible", receiverEnsName: null, - hasMetaData: false, from: testData.addresses.receiver2, }, { token_type: "erc721", tokenAddress: testData.unlistedERC20Token.address, - tokenId: new BigNumber(420), + tokenId: "420", receiver: testData.addresses.receiver1, tokenName: "Test Collectible", receiverEnsName: null, - hasMetaData: false, from: testData.addresses.receiver2, }, ]; - const exactBalance: CollectibleBalance = [ - { - address: testData.unlistedERC20Token.address, - id: "69", - tokenName: "Test Collectible", - tokenSymbol: "TC", - }, - { - address: testData.unlistedERC20Token.address, - id: "420", - tokenName: "Test Collectible", - tokenSymbol: "TC", - }, - ]; - const biggerBalance: CollectibleBalance = [ - { - address: testData.unlistedERC20Token.address, - id: "69", - tokenName: "Test Collectible", - tokenSymbol: "TC", - }, - { - address: testData.unlistedERC20Token.address, - id: "420", - tokenName: "Test Collectible", - tokenSymbol: "TC", - }, - { - address: testData.unlistedERC20Token.address, - id: "42069", - tokenName: "Test Collectible", - tokenSymbol: "TC", - }, - ]; - const smallerBalance: CollectibleBalance = [ - { - address: testData.unlistedERC20Token.address, - id: "69", - tokenName: "Test Collectible", - tokenSymbol: "TC", - }, - ]; + const exactBalance: NFTBalance = { + results: [ + { + address: testData.unlistedERC20Token.address, + id: "69", + tokenName: "Test Collectible", + tokenSymbol: "TC", + imageUri: "", + name: "", + }, + { + address: testData.unlistedERC20Token.address, + id: "420", + tokenName: "Test Collectible", + tokenSymbol: "TC", + imageUri: "", + name: "", + }, + ], + next: null, + }; + const biggerBalance: NFTBalance = { + results: [ + { + address: testData.unlistedERC20Token.address, + id: "69", + tokenName: "Test Collectible", + tokenSymbol: "TC", + imageUri: "", + name: "", + }, + { + address: testData.unlistedERC20Token.address, + id: "420", + tokenName: "Test Collectible", + tokenSymbol: "TC", + imageUri: "", + name: "", + }, + { + address: testData.unlistedERC20Token.address, + id: "42069", + tokenName: "Test Collectible", + tokenSymbol: "TC", + imageUri: "", + name: "", + }, + ], + next: null, + }; + const smallerBalance: NFTBalance = { + results: [ + { + address: testData.unlistedERC20Token.address, + id: "69", + tokenName: "Test Collectible", + tokenSymbol: "TC", + imageUri: "", + name: "", + }, + ], + next: null, + }; expect(checkAllBalances(undefined, exactBalance, transfers)).to.be.empty; expect(checkAllBalances(undefined, biggerBalance, transfers)).to.be.empty; @@ -521,7 +539,7 @@ describe("transferToSummary and check balances", () => { expect(smallBalanceCheckResult).to.have.length(1); expect(smallBalanceCheckResult[0].token).to.equal("Test Collectible"); expect(smallBalanceCheckResult[0].token_type).to.equal("erc721"); - expect(smallBalanceCheckResult[0].id?.toFixed()).to.equal("420"); + expect(smallBalanceCheckResult[0].id).to.equal("420"); expect(smallBalanceCheckResult[0].transferAmount).to.be.undefined; expect(smallBalanceCheckResult[0].isDuplicate).to.be.false; }); @@ -531,37 +549,40 @@ describe("transferToSummary and check balances", () => { { token_type: "erc721", tokenAddress: testData.unlistedERC20Token.address, - tokenId: new BigNumber(69), + tokenId: "69", receiver: testData.addresses.receiver1, receiverEnsName: null, - hasMetaData: false, from: testData.addresses.receiver2, }, { token_type: "erc721", tokenAddress: testData.unlistedERC20Token.address, - tokenId: new BigNumber(69), + tokenId: "69", receiver: testData.addresses.receiver2, receiverEnsName: null, - hasMetaData: false, from: testData.addresses.receiver2, }, ]; - const exactBalance: CollectibleBalance = [ - { - address: testData.unlistedERC20Token.address, - id: "69", - tokenName: "Test Collectible", - tokenSymbol: "TC", - }, - ]; + const exactBalance: NFTBalance = { + results: [ + { + address: testData.unlistedERC20Token.address, + id: "69", + tokenName: "Test Collectible", + tokenSymbol: "TC", + imageUri: "", + name: "", + }, + ], + next: null, + }; const balanceCheckResult = checkAllBalances(undefined, exactBalance, transfers); expect(balanceCheckResult).to.have.length(1); expect(balanceCheckResult[0].token).to.equal("Test Collectible"); expect(balanceCheckResult[0].token_type).to.equal("erc721"); - expect(balanceCheckResult[0].id?.toFixed()).to.equal("69"); + expect(balanceCheckResult[0].id).to.equal("69"); expect(balanceCheckResult[0].transferAmount).to.undefined; expect(balanceCheckResult[0].isDuplicate).to.be.true; }); diff --git a/src/__tests__/parser.test.ts b/src/__tests__/parser.test.ts index e05df430..6523bf49 100644 --- a/src/__tests__/parser.test.ts +++ b/src/__tests__/parser.test.ts @@ -1,12 +1,15 @@ -import { BigNumber } from "bignumber.js"; +import { renderHook } from "@testing-library/react-hooks"; import * as chai from "chai"; import { expect } from "chai"; import chaiAsPromised from "chai-as-promised"; -import { CollectibleTokenInfoProvider } from "../hooks/collectibleTokenInfoProvider"; -import { EnsResolver } from "../hooks/ens"; +import type { CollectibleTokenInfoProvider } from "../hooks/collectibleTokenInfoProvider"; +import * as useCollectibleTokenInfoProvider from "../hooks/collectibleTokenInfoProvider"; +import * as useEnsResolver from "../hooks/ens"; +import type { EnsResolver } from "../hooks/ens"; +import * as useTokenInfoProvider from "../hooks/token"; import { TokenMap, MinimalTokenInfo, fetchTokenList, TokenInfoProvider } from "../hooks/token"; -import { AssetTransfer, CollectibleTransfer, CSVParser } from "../parser/csvParser"; +import { AssetTransfer, CollectibleTransfer, useCsvParser } from "../hooks/useCsvParser"; import { testData } from "../test/util"; let tokenList: TokenMap; @@ -31,7 +34,7 @@ describe("Parsing CSVs ", () => { let mockCollectibleTokenInfoProvider: CollectibleTokenInfoProvider; let mockEnsResolver: EnsResolver; - beforeAll(async () => { + beforeEach(async () => { tokenList = await fetchTokenList(testData.dummySafeInfo.chainId); const fetchTokenFromList = async (tokenAddress: string) => tokenList.get(tokenAddress); @@ -46,6 +49,7 @@ describe("Parsing CSVs ", () => { getNativeTokenSymbol: () => "ETH", getSelectedNetworkShortname: () => "eth", }; + jest.spyOn(useTokenInfoProvider, "useTokenInfoProvider").mockReturnValue(mockTokenInfoProvider); mockCollectibleTokenInfoProvider = { getFromAddress: () => testData.dummySafeInfo.safeAddress, @@ -61,6 +65,9 @@ describe("Parsing CSVs ", () => { }, fetchMetaInfo: jest.fn(), }; + jest + .spyOn(useCollectibleTokenInfoProvider, "useCollectibleTokenInfoProvider") + .mockReturnValue(mockCollectibleTokenInfoProvider); mockEnsResolver = { resolveName: async (ensName: string) => { @@ -98,48 +105,42 @@ describe("Parsing CSVs ", () => { }, isEnsEnabled: async () => true, }; + jest.spyOn(useEnsResolver, "useEnsResolver").mockReturnValue(mockEnsResolver); }); it("should throw errors for invalid CSVs", async () => { + const { result } = renderHook(() => useCsvParser()); // this csv contains more values than headers in row1 const invalidCSV = "head1,header2\nvalue1,value2,value3"; - expect( - CSVParser.parseCSV(invalidCSV, mockTokenInfoProvider, mockCollectibleTokenInfoProvider, mockEnsResolver), - ).to.be.rejectedWith("column header mismatch expected: 2 columns got: 3"); + expect(result.current.parseCsv(invalidCSV)).to.be.rejectedWith("column header mismatch expected: 2 columns got: 3"); }); it("should skip files with >400 lines of transfers", async () => { - let largeCSV = csvStringFromRows(...Array(401).fill(["erc20", listedToken.address, validReceiverAddress, "1"])); - expect( - CSVParser.parseCSV(largeCSV, mockTokenInfoProvider, mockCollectibleTokenInfoProvider, mockEnsResolver), - ).to.be.rejectedWith( - "Max number of lines exceeded. Due to the block gas limit transactions are limited to 400 lines.", + const { result } = renderHook(() => useCsvParser()); + + let largeCSV = csvStringFromRows(...Array(501).fill(["erc20", listedToken.address, validReceiverAddress, "1"])); + expect(result.current.parseCsv(largeCSV)).to.be.rejectedWith( + "Max number of lines exceeded. Due to the block gas limit transactions are limited to 500 lines.", ); }); it("should throw errors for unexpected errors while parsing", async () => { + const { result } = renderHook(() => useCsvParser()); + // we hard coded in our mock that a ens of "error.eth" throws an error. const rowWithErrorReceiver = ["erc20", listedToken.address, "error.eth", "1"]; - expect( - CSVParser.parseCSV( - csvStringFromRows(rowWithErrorReceiver), - mockTokenInfoProvider, - mockCollectibleTokenInfoProvider, - mockEnsResolver, - ), - ).to.be.rejectedWith("unexpected error!"); + expect(result.current.parseCsv(csvStringFromRows(rowWithErrorReceiver))).to.be.rejectedWith("unexpected error!"); }); it("should transform simple, valid CSVs correctly", async () => { + const { result } = renderHook(() => useCsvParser()); + const rowWithoutDecimal = ["erc20", listedToken.address, validReceiverAddress, "1"]; const rowWithDecimalAmount = ["erc20", listedToken.address, validReceiverAddress, "69.420"]; const rowWithoutTokenAddress = ["native", "", validReceiverAddress, "1"]; - const [payment, warnings] = await CSVParser.parseCSV( + const [payment, warnings] = await result.current.parseCsv( csvStringFromRows(rowWithoutDecimal, rowWithDecimalAmount, rowWithoutTokenAddress), - mockTokenInfoProvider, - mockCollectibleTokenInfoProvider, - mockEnsResolver, ); expect(warnings).to.be.empty; expect(payment).to.have.lengthOf(3); @@ -147,23 +148,25 @@ describe("Parsing CSVs ", () => { expect(paymentWithoutDecimal.decimals).to.be.equal(18); expect(paymentWithoutDecimal.receiver).to.equal(validReceiverAddress); expect(paymentWithoutDecimal.tokenAddress).to.equal(listedToken.address); - expect(paymentWithoutDecimal.amount.isEqualTo(new BigNumber(1))).to.be.true; + expect(paymentWithoutDecimal.amount).to.equal("1"); expect(paymentWithoutDecimal.receiverEnsName).to.be.null; expect(paymentWithDecimal.receiver).to.equal(validReceiverAddress); expect(paymentWithDecimal.tokenAddress?.toLowerCase()).to.equal(listedToken.address.toLowerCase()); expect(paymentWithDecimal.decimals).to.equal(18); - expect(paymentWithDecimal.amount.isEqualTo(new BigNumber(69.42))).to.be.true; + expect(paymentWithDecimal.amount).to.equal("69.420"); expect(paymentWithDecimal.receiverEnsName).to.be.null; expect(paymentWithoutTokenAddress.decimals).to.be.equal(18); expect(paymentWithoutTokenAddress.receiver).to.equal(validReceiverAddress); expect(paymentWithoutTokenAddress.tokenAddress).to.equal(null); - expect(paymentWithoutTokenAddress.amount.isEqualTo(new BigNumber(1))).to.be.true; + expect(paymentWithoutTokenAddress.amount).to.equal("1"); expect(paymentWithoutTokenAddress.receiverEnsName).to.be.null; }); it("should generate erc20 validation warnings", async () => { + const { result } = renderHook(() => useCsvParser()); + const rowWithNegativeAmount = ["erc20", listedToken.address, validReceiverAddress, "-1"]; const unlistedTokenWithoutDecimalInContract = [ @@ -175,16 +178,13 @@ describe("Parsing CSVs ", () => { const rowWithInvalidTokenAddress = ["erc20", "0x420", validReceiverAddress, "1"]; const rowWithInvalidReceiverAddress = ["erc20", listedToken.address, "0x420", "1"]; - const [payment, warnings] = await CSVParser.parseCSV( + const [payment, warnings] = await result.current.parseCsv( csvStringFromRows( rowWithNegativeAmount, unlistedTokenWithoutDecimalInContract, rowWithInvalidTokenAddress, rowWithInvalidReceiverAddress, ), - mockTokenInfoProvider, - mockCollectibleTokenInfoProvider, - mockEnsResolver, ); expect(warnings).to.have.lengthOf(5); const [ @@ -214,16 +214,15 @@ describe("Parsing CSVs ", () => { }); it("tries to resolve ens names", async () => { + const { result } = renderHook(() => useCsvParser()); + const receiverEnsName = ["erc20", listedToken.address, "receiver1.eth", "1"]; const tokenEnsName = ["erc20", "token.eth", validReceiverAddress, "69.420"]; const unknownReceiverEnsName = ["erc20", listedToken.address, "unknown.eth", "1"]; const unknownTokenEnsName = ["erc20", "unknown.eth", "receiver1.eth", "1"]; - const [payment, warnings] = await CSVParser.parseCSV( + const [payment, warnings] = await result.current.parseCsv( csvStringFromRows(receiverEnsName, tokenEnsName, unknownReceiverEnsName, unknownTokenEnsName), - mockTokenInfoProvider, - mockCollectibleTokenInfoProvider, - mockEnsResolver, ); expect(warnings).to.have.lengthOf(3); expect(payment).to.have.lengthOf(2); @@ -232,13 +231,13 @@ describe("Parsing CSVs ", () => { expect(paymentReceiverEnsName.decimals).to.be.equal(18); expect(paymentReceiverEnsName.receiver).to.equal(testData.addresses.receiver1); expect(paymentReceiverEnsName.tokenAddress).to.equal(listedToken.address); - expect(paymentReceiverEnsName.amount.isEqualTo(new BigNumber(1))).to.be.true; + expect(paymentReceiverEnsName.amount).to.equal("1"); expect(paymentReceiverEnsName.receiverEnsName).to.equal("receiver1.eth"); expect(paymentTokenEnsName.receiver).to.equal(validReceiverAddress); expect(paymentTokenEnsName.tokenAddress?.toLowerCase()).to.equal(listedToken.address.toLowerCase()); expect(paymentTokenEnsName.decimals).to.equal(18); - expect(paymentTokenEnsName.amount.isEqualTo(new BigNumber(69.42))).to.be.true; + expect(paymentTokenEnsName.amount).to.equal("69.420"); expect(paymentReceiverEnsName.receiverEnsName).to.equal("receiver1.eth"); expect(warningUnknownReceiverEnsName.lineNo).to.equal(3); @@ -252,13 +251,15 @@ describe("Parsing CSVs ", () => { }); it("parses valid collectible transfers", async () => { + const { result } = renderHook(() => useCsvParser()); + const rowWithErc721AndAddress = ["nft", testData.addresses.dummyErc721Address, validReceiverAddress, "", "1"]; const rowWithErc721AndENS = ["nft", testData.addresses.dummyErc721Address, "receiver2.eth", "", "69"]; const rowWithErc721AndIDZero = ["nft", testData.addresses.dummyErc721Address, "receiver1.eth", "", "0"]; const rowWithErc1155AndAddress = ["nft", testData.addresses.dummyErc1155Address, validReceiverAddress, "69", "420"]; const rowWithErc1155AndENS = ["nft", testData.addresses.dummyErc1155Address, "receiver3.eth", "9", "99"]; - const [payment, warnings] = await CSVParser.parseCSV( + const [payment, warnings] = await result.current.parseCsv( csvStringFromRows( rowWithErc721AndAddress, rowWithErc721AndENS, @@ -266,9 +267,6 @@ describe("Parsing CSVs ", () => { rowWithErc1155AndAddress, rowWithErc1155AndENS, ), - mockTokenInfoProvider, - mockCollectibleTokenInfoProvider, - mockEnsResolver, ); expect(warnings).to.be.empty; expect(payment).to.have.lengthOf(5); @@ -282,18 +280,18 @@ describe("Parsing CSVs ", () => { expect(transferErc721AndAddress.receiver).to.equal(validReceiverAddress); expect(transferErc721AndAddress.tokenAddress).to.equal(testData.addresses.dummyErc721Address); expect(transferErc721AndAddress.amount).to.be.undefined; - expect(transferErc721AndAddress.tokenId.isEqualTo(new BigNumber(1))).to.be.true; + expect(transferErc721AndAddress.tokenId).to.equal("1"); expect(transferErc721AndAddress.receiverEnsName).to.be.null; expect(transferErc721AndENS.receiver).to.equal(testData.addresses.receiver2); expect(transferErc721AndENS.tokenAddress).to.equal(testData.addresses.dummyErc721Address); - expect(transferErc721AndENS.tokenId.isEqualTo(new BigNumber(69))).to.be.true; + expect(transferErc721AndENS.tokenId).to.equal("69"); expect(transferErc721AndENS.amount).to.be.undefined; expect(transferErc721AndENS.receiverEnsName).to.equal("receiver2.eth"); expect(transferErc721AndIDZero.receiver).to.equal(testData.addresses.receiver1); expect(transferErc721AndIDZero.tokenAddress).to.equal(testData.addresses.dummyErc721Address); - expect(transferErc721AndIDZero.tokenId.isEqualTo(new BigNumber(0))).to.be.true; + expect(transferErc721AndIDZero.tokenId).to.equal("0"); expect(transferErc721AndIDZero.amount).to.be.undefined; expect(transferErc721AndIDZero.receiverEnsName).to.equal("receiver1.eth"); @@ -302,8 +300,8 @@ describe("Parsing CSVs ", () => { testData.addresses.dummyErc1155Address.toLowerCase(), ); expect(transferErc1155AndAddress.amount).not.to.be.undefined; - expect(transferErc1155AndAddress.amount?.isEqualTo(new BigNumber(69))).to.be.true; - expect(transferErc1155AndAddress.tokenId.isEqualTo(new BigNumber(420))).to.be.true; + expect(transferErc1155AndAddress.amount).to.equal("69"); + expect(transferErc1155AndAddress.tokenId).to.equal("420"); expect(transferErc1155AndAddress.receiverEnsName).to.be.null; expect(transferErc1155AndENS.receiver).to.equal(testData.addresses.receiver3); @@ -311,12 +309,14 @@ describe("Parsing CSVs ", () => { testData.addresses.dummyErc1155Address.toLowerCase(), ); expect(transferErc1155AndENS.amount).not.to.be.undefined; - expect(transferErc1155AndENS.amount?.isEqualTo(new BigNumber(9))).to.be.true; - expect(transferErc1155AndENS.tokenId.isEqualTo(new BigNumber(99))).to.be.true; + expect(transferErc1155AndENS.amount).to.equal("9"); + expect(transferErc1155AndENS.tokenId).to.equal("99"); expect(transferErc1155AndENS.receiverEnsName).to.equal("receiver3.eth"); }); it("should generate erc721/erc1155 validation warnings", async () => { + const { result } = renderHook(() => useCsvParser()); + const rowErc1155WithNegativeValue = [ "nft", testData.addresses.dummyErc1155Address, @@ -333,8 +333,6 @@ describe("Parsing CSVs ", () => { "5", ]; - const rowErc1155WithMissingValue = ["nft", testData.addresses.dummyErc1155Address, validReceiverAddress, "", "5"]; - const rowErc1155WithMissingId = ["nft", testData.addresses.dummyErc1155Address, validReceiverAddress, "5", ""]; const rowErc1155WithInvalidTokenAddress = ["nft", "0xwhoopsie", validReceiverAddress, "5", "5"]; @@ -357,11 +355,10 @@ describe("Parsing CSVs ", () => { const rowErc721WithInvalidReceiver = ["nft", testData.addresses.dummyErc721Address, "0xwhoopsie", "", "69"]; - const [payment, warnings] = await CSVParser.parseCSV( + const [payment, warnings] = await result.current.parseCsv( csvStringFromRows( rowErc1155WithNegativeValue, rowErc1155WithDecimalValue, - rowErc1155WithMissingValue, rowErc1155WithMissingId, rowErc1155WithInvalidTokenAddress, rowErc1155WithInvalidReceiverAddress, @@ -371,15 +368,11 @@ describe("Parsing CSVs ", () => { rowErc721WithInvalidToken, rowErc721WithInvalidReceiver, ), - mockTokenInfoProvider, - mockCollectibleTokenInfoProvider, - mockEnsResolver, ); - expect(warnings).to.have.lengthOf(15); + expect(warnings).to.have.lengthOf(14); const [ warningErc1155WithNegativeValue, warningErc1155WithDecimalValue, - warningErc1155WithMissingValue, warningErc1155WithMissingId, warningErc1155WithMissingId2, warningErc1155WithInvalidTokenAddress, @@ -399,58 +392,52 @@ describe("Parsing CSVs ", () => { expect(warningErc1155WithNegativeValue.message).to.equal("ERC1155 Tokens need a defined value > 0: -1"); expect(warningErc1155WithDecimalValue.lineNo).to.equal(2); - expect(warningErc1155WithDecimalValue.message).to.equal("Value of ERC1155 must be an integer: 1.5"); + expect(warningErc1155WithDecimalValue.message).to.equal("Value / amount of ERC1155 must be an integer: 1.5"); - expect(warningErc1155WithMissingValue.lineNo).to.equal(3); - expect(warningErc1155WithMissingValue.message).to.equal("ERC1155 Tokens need a defined value > 0: NaN"); - - expect(warningErc1155WithMissingId.lineNo).to.equal(4); + expect(warningErc1155WithMissingId.lineNo).to.equal(3); expect(warningErc1155WithMissingId.message).to.equal("Only positive Token IDs possible: NaN"); - expect(warningErc1155WithMissingId2.lineNo).to.equal(4); + expect(warningErc1155WithMissingId2.lineNo).to.equal(3); expect(warningErc1155WithMissingId2.message).to.equal("Token IDs must be integer numbers: NaN"); - expect(warningErc1155WithInvalidTokenAddress.lineNo).to.equal(5); + expect(warningErc1155WithInvalidTokenAddress.lineNo).to.equal(4); expect(warningErc1155WithInvalidTokenAddress.message).to.equal("Invalid Token Address: 0xwhoopsie"); - expect(warningErc1155WithInvalidTokenAddress2.lineNo).to.equal(5); + expect(warningErc1155WithInvalidTokenAddress2.lineNo).to.equal(4); expect(warningErc1155WithInvalidTokenAddress2.message).to.equal("No token contract was found at 0xwhoopsie"); - expect(warningErc1155WithInvalidReceiverAddress.lineNo).to.equal(6); + expect(warningErc1155WithInvalidReceiverAddress.lineNo).to.equal(5); expect(warningErc1155WithInvalidReceiverAddress.message).to.equal("Invalid Receiver Address: 0xwhoopsie"); - expect(warningErc721WithNegativeId.lineNo).to.equal(7); + expect(warningErc721WithNegativeId.lineNo).to.equal(6); expect(warningErc721WithNegativeId.message).to.equal("Only positive Token IDs possible: -20"); - expect(warningErc721WithDecimalId.lineNo).to.equal(8); + expect(warningErc721WithDecimalId.lineNo).to.equal(7); expect(warningErc721WithDecimalId.message).to.equal("Token IDs must be integer numbers: 69.42"); - expect(warningErc721WithMissingId.lineNo).to.equal(9); + expect(warningErc721WithMissingId.lineNo).to.equal(8); expect(warningErc721WithMissingId.message).to.equal("Only positive Token IDs possible: NaN"); - expect(warningErc721WithMissingId2.lineNo).to.equal(9); + expect(warningErc721WithMissingId2.lineNo).to.equal(8); expect(warningErc721WithMissingId2.message).to.equal("Token IDs must be integer numbers: NaN"); - expect(warningErc721WithInvalidToken.lineNo).to.equal(10); + expect(warningErc721WithInvalidToken.lineNo).to.equal(9); expect(warningErc721WithInvalidToken.message).to.equal("Invalid Token Address: 0xwhoopsie"); - expect(warningErc721WithInvalidToken2.lineNo).to.equal(10); + expect(warningErc721WithInvalidToken2.lineNo).to.equal(9); expect(warningErc721WithInvalidToken2.message).to.equal("No token contract was found at 0xwhoopsie"); - expect(warningErc721WithInvalidReceiver.lineNo).to.equal(11); + expect(warningErc721WithInvalidReceiver.lineNo).to.equal(10); expect(warningErc721WithInvalidReceiver.message).to.equal("Invalid Receiver Address: 0xwhoopsie"); }); describe("Support backward compatibility", () => { it("fallback to erc20 without token_type", async () => { + const { result } = renderHook(() => useCsvParser()); + const missingTokenType = ["", listedToken.address, validReceiverAddress, "15"]; - const [payment, warnings] = await CSVParser.parseCSV( - csvStringFromRows(missingTokenType), - mockTokenInfoProvider, - mockCollectibleTokenInfoProvider, - mockEnsResolver, - ); + const [payment, warnings] = await result.current.parseCsv(csvStringFromRows(missingTokenType)); expect(warnings).to.be.empty; expect(payment).to.have.length(1); const [erc20Transfer] = payment as AssetTransfer[]; @@ -459,21 +446,18 @@ describe("Parsing CSVs ", () => { }); it("allow value instead of amount column", async () => { + const { result } = renderHook(() => useCsvParser()); + const nativeTransfer = ["native", listedToken.address, validReceiverAddress, "15"]; const headerRow = "token_type,token_address,receiver,value,id"; const csvString = [headerRow, nativeTransfer.join(",")].join("\n"); - const [payment, warnings] = await CSVParser.parseCSV( - csvString, - mockTokenInfoProvider, - mockCollectibleTokenInfoProvider, - mockEnsResolver, - ); + const [payment, warnings] = await result.current.parseCsv(csvString); expect(warnings).to.be.empty; expect(payment).to.have.length(1); const [nativeTransferData] = payment as AssetTransfer[]; - expect(nativeTransferData.amount.isEqualTo(new BigNumber(15))).to.be.true; + expect(nativeTransferData.amount).to.equal("15"); }); }); }); diff --git a/src/__tests__/transfers.test.ts b/src/__tests__/transfers.test.ts index 1966168b..44e0acb3 100644 --- a/src/__tests__/transfers.test.ts +++ b/src/__tests__/transfers.test.ts @@ -3,7 +3,7 @@ import { expect } from "chai"; import { ethers } from "ethers"; import { fetchTokenList, MinimalTokenInfo } from "../hooks/token"; -import { AssetTransfer, CollectibleTransfer } from "../parser/csvParser"; +import { AssetTransfer, CollectibleTransfer } from "../hooks/useCsvParser"; import { testData } from "../test/util"; import { erc1155Interface } from "../transfers/erc1155"; import { erc20Interface } from "../transfers/erc20"; @@ -32,7 +32,7 @@ describe("Build Transfers:", () => { { token_type: "erc20", receiver, - amount: fromWei(MAX_U256, listedToken.decimals), + amount: fromWei(MAX_U256, listedToken.decimals).toFixed(), tokenAddress: listedToken.address, decimals: listedToken.decimals, symbol: "LIT", @@ -42,7 +42,7 @@ describe("Build Transfers:", () => { { token_type: "erc20", receiver, - amount: fromWei(MAX_U256, testData.unlistedERC20Token.decimals), + amount: fromWei(MAX_U256, testData.unlistedERC20Token.decimals).toFixed(), tokenAddress: testData.unlistedERC20Token.address, decimals: testData.unlistedERC20Token.decimals, symbol: "ULT", @@ -52,7 +52,7 @@ describe("Build Transfers:", () => { { token_type: "native", receiver, - amount: fromWei(MAX_U256, 18), + amount: fromWei(MAX_U256, 18).toFixed(), tokenAddress: null, decimals: 18, symbol: "ETH", @@ -87,7 +87,7 @@ describe("Build Transfers:", () => { { token_type: "erc20", receiver, - amount: tinyAmount, + amount: tinyAmount.toFixed(), tokenAddress: listedToken.address, decimals: listedToken.decimals, symbol: "LIT", @@ -97,7 +97,7 @@ describe("Build Transfers:", () => { { token_type: "erc20", receiver, - amount: tinyAmount, + amount: tinyAmount.toFixed(), tokenAddress: testData.unlistedERC20Token.address, decimals: testData.unlistedERC20Token.decimals, symbol: "ULT", @@ -107,7 +107,7 @@ describe("Build Transfers:", () => { { token_type: "native", receiver, - amount: tinyAmount, + amount: tinyAmount.toFixed(), tokenAddress: null, decimals: 18, symbol: "ETH", @@ -145,7 +145,7 @@ describe("Build Transfers:", () => { { token_type: "erc20", receiver, - amount: mixedAmount, + amount: mixedAmount.toFixed(), tokenAddress: listedToken.address, decimals: listedToken.decimals, symbol: "LIT", @@ -155,7 +155,7 @@ describe("Build Transfers:", () => { { token_type: "erc20", receiver, - amount: mixedAmount, + amount: mixedAmount.toFixed(), tokenAddress: testData.unlistedERC20Token.address, decimals: testData.unlistedERC20Token.decimals, symbol: "ULT", @@ -165,7 +165,7 @@ describe("Build Transfers:", () => { { token_type: "native", receiver, - amount: mixedAmount, + amount: mixedAmount.toFixed(), tokenAddress: null, decimals: 18, symbol: "ETH", @@ -209,7 +209,7 @@ describe("Build Transfers:", () => { const payment: AssetTransfer = { token_type: "erc20", receiver, - amount, + amount: amount.toFixed(), tokenAddress: crappyToken.address, decimals: crappyToken.decimals, symbol: "BTC", @@ -233,8 +233,7 @@ describe("Build Transfers:", () => { receiverEnsName: null, tokenAddress: testData.addresses.dummyErc721Address, tokenName: "Test NFT", - tokenId: new BigNumber("69"), - hasMetaData: false, + tokenId: new BigNumber("69").toFixed(), }, { token_type: "erc1155", @@ -243,9 +242,8 @@ describe("Build Transfers:", () => { receiverEnsName: null, tokenAddress: testData.addresses.dummyErc1155Address, tokenName: "Test MultiToken", - amount: new BigNumber("69"), - tokenId: new BigNumber("420"), - hasMetaData: false, + amount: new BigNumber("69").toFixed(), + tokenId: new BigNumber("420").toFixed(), }, ]; diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index f52a8745..0dbabaa3 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -47,6 +47,6 @@ describe("resolveIpfsUri", () => { it("returns infura url for ipfs urls", () => { const ipfsURI = "ipfs://SomeHash"; - expect(resolveIpfsUri(ipfsURI)).to.be.equal("https://ipfs.infura.io/ipfs/SomeHash"); + expect(resolveIpfsUri(ipfsURI)).to.be.equal("https://cloudflare-ipfs.com/ipfs/SomeHash"); }); }); diff --git a/src/components/CSVEditor.tsx b/src/components/CSVEditor.tsx index d7979b4c..66b362fa 100644 --- a/src/components/CSVEditor.tsx +++ b/src/components/CSVEditor.tsx @@ -1,25 +1,27 @@ -import React, { useContext } from "react"; +import React from "react"; import AceEditor, { IMarker, IAnnotation } from "react-ace"; import "ace-builds/src-noconflict/theme-tomorrow"; import "ace-builds/src-noconflict/mode-text"; +import { useDispatch, useSelector } from "react-redux"; +import { RootState } from "src/stores/store"; import styled from "styled-components"; -import { MessageContext } from "../../src/contexts/MessageContextProvider"; +import { updateCsvContent } from "../stores/slices/csvEditorSlice"; const EditorWrapper = styled.div``; -export type CSVEditorProps = { - onChange: (csvContent: string) => void; - csvText: string; -}; +export type CSVEditorProps = {}; export const CSVEditor = (props: CSVEditorProps): JSX.Element => { - const { codeWarnings } = useContext(MessageContext); + const { codeWarnings } = useSelector((state: RootState) => state.messages); + const dispatch = useDispatch(); + const csvText = useSelector((state: RootState) => state.csvEditor.csvContent); + return ( props.onChange(newCode)} - value={props.csvText} + onChange={(newCode) => dispatch(updateCsvContent({ csvContent: newCode }))} + value={csvText} theme="tomorrow" width={"100%"} mode={"text"} diff --git a/src/components/CSVForm.tsx b/src/components/CSVForm.tsx index 517e957d..d9432654 100644 --- a/src/components/CSVForm.tsx +++ b/src/components/CSVForm.tsx @@ -1,17 +1,6 @@ import { Text } from "@gnosis.pm/safe-react-components"; -import { Grid } from "@material-ui/core"; -import debounce from "lodash.debounce"; -import React, { useContext, useEffect, useMemo, useState } from "react"; import styled from "styled-components"; -import { MessageContext } from "../contexts/MessageContextProvider"; -import { useBalances } from "../hooks/balances"; -import { useCollectibleTokenInfoProvider } from "../hooks/collectibleTokenInfoProvider"; -import { useEnsResolver } from "../hooks/ens"; -import { useTokenInfoProvider } from "../hooks/token"; -import { checkAllBalances } from "../parser/balanceCheck"; -import { CSVParser, Transfer } from "../parser/csvParser"; - import { CSVEditor } from "./CSVEditor"; import { CSVUpload } from "./CSVUpload"; import { GenerateTransfersMenu } from "./GenerateTransfersMenu"; @@ -23,110 +12,9 @@ const Form = styled.div` justify-content: space-around; gap: 8px; `; -export interface CSVFormProps { - updateTransferTable: (transfers: Transfer[]) => void; - setParsing: (parsing: boolean) => void; -} +export interface CSVFormProps {} export const CSVForm = (props: CSVFormProps): JSX.Element => { - const { updateTransferTable, setParsing } = props; - const [csvText, setCsvText] = useState("token_type,token_address,receiver,amount,id"); - - const { setCodeWarnings, setMessages } = useContext(MessageContext); - - const { assetBalance, collectibleBalance } = useBalances(); - const tokenInfoProvider = useTokenInfoProvider(); - const ensResolver = useEnsResolver(); - const erc721TokenInfoProvider = useCollectibleTokenInfoProvider(); - - const onChangeTextHandler = (csvText: string) => { - setCsvText(csvText); - }; - - const parseAndValidateCSV = useMemo( - () => - debounce((csvText: string) => { - setParsing(true); - - CSVParser.parseCSV(csvText, tokenInfoProvider, erc721TokenInfoProvider, ensResolver) - .then(async ([transfers, warnings]) => { - const uniqueReceiversWithoutEnsName = transfers.reduce( - (previousValue, currentValue): Set => - currentValue.receiverEnsName === null ? previousValue.add(currentValue.receiver) : previousValue, - new Set(), - ); - if (uniqueReceiversWithoutEnsName.size < 15) { - transfers = await Promise.all( - // If there is no ENS Name we will try to lookup the address - transfers.map(async (transfer) => - transfer.receiverEnsName - ? transfer - : { - ...transfer, - receiverEnsName: (await ensResolver.isEnsEnabled()) - ? await ensResolver.lookupAddress(transfer.receiver) - : null, - }, - ), - ); - } - transfers = transfers.map((transfer, idx) => ({ ...transfer, position: idx + 1 })); - updateTransferTable(transfers); - // If we have no balances we dont need to check them. - if (assetBalance || collectibleBalance) { - const insufficientBalances = checkAllBalances(assetBalance, collectibleBalance, transfers); - setMessages( - insufficientBalances.map((insufficientBalanceInfo) => { - if ( - insufficientBalanceInfo.token_type === "erc20" || - insufficientBalanceInfo.token_type === "native" - ) { - if (insufficientBalanceInfo.token_type === "native") { - insufficientBalanceInfo.token = tokenInfoProvider.getNativeTokenSymbol(); - } - return { - message: `Insufficient Balance: ${insufficientBalanceInfo.transferAmount} of ${insufficientBalanceInfo.token}`, - severity: "error", - }; - } else { - if (insufficientBalanceInfo.isDuplicate) { - return { - message: `Duplicate transfer for ERC721 token ${insufficientBalanceInfo.token} with ID ${insufficientBalanceInfo.id}`, - severity: "warning", - }; - } else { - return { - message: `Collectible ERC721 token ${insufficientBalanceInfo.token} with ID ${insufficientBalanceInfo.id} is not held by this safe`, - severity: "error", - }; - } - } - }), - ); - } - - setCodeWarnings(warnings); - setParsing(false); - }) - .catch((reason: any) => setMessages([{ severity: "error", message: reason.message }])); - }, 750), - [ - ensResolver, - erc721TokenInfoProvider, - assetBalance, - collectibleBalance, - setCodeWarnings, - setMessages, - setParsing, - tokenInfoProvider, - updateTransferTable, - ], - ); - - useEffect(() => { - parseAndValidateCSV(csvText); - }, [csvText, parseAndValidateCSV]); - return (
@@ -137,21 +25,9 @@ export const CSVForm = (props: CSVFormProps): JSX.Element => { Upload, edit or paste your asset transfer CSV
( token_type,token_address,receiver,amount,id)
- - - - - - - - - - + + + ); }; diff --git a/src/components/CSVUpload.tsx b/src/components/CSVUpload.tsx index 357c07e0..0814dfc2 100644 --- a/src/components/CSVUpload.tsx +++ b/src/components/CSVUpload.tsx @@ -2,13 +2,15 @@ import { Button, Text, theme as GnosisTheme } from "@gnosis.pm/safe-react-compon import { createStyles } from "@material-ui/core"; import React, { useCallback, useMemo } from "react"; import { useDropzone } from "react-dropzone"; +import { useDispatch } from "react-redux"; -export type CSVUploadProps = { - onChange: (string) => void; -}; +import { updateCsvContent } from "../stores/slices/csvEditorSlice"; + +export type CSVUploadProps = {}; export const CSVUpload = (props: CSVUploadProps): JSX.Element => { - const { onChange } = props; + const dispatch = useDispatch(); + const onDrop = useCallback( (acceptedFiles: File[]) => { acceptedFiles.forEach((file) => { @@ -17,12 +19,16 @@ export const CSVUpload = (props: CSVUploadProps): JSX.Element => { if (!evt.target) { return; } - onChange(evt.target.result as string); + dispatch( + updateCsvContent({ + csvContent: evt.target.result as string, + }), + ); }; reader.readAsText(file); }); }, - [onChange], + [dispatch], ); const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject } = useDropzone({ diff --git a/src/components/DonateDialog.tsx b/src/components/DonateDialog.tsx index 152eb763..8a037576 100644 --- a/src/components/DonateDialog.tsx +++ b/src/components/DonateDialog.tsx @@ -3,24 +3,26 @@ import { Button, GenericModal, Icon, Select, TextFieldInput } from "@gnosis.pm/s import { InputAdornment, Typography } from "@material-ui/core"; import { BigNumber, ethers } from "ethers"; import { useEffect, useState } from "react"; -import { AssetBalance } from "src/hooks/balances"; +import { useCsvContent } from "src/hooks/useCsvContent"; import { networkInfo } from "src/networks"; +import { AssetBalance } from "src/stores/api/balanceApi"; +import { updateCsvContent } from "src/stores/slices/csvEditorSlice"; +import { useAppDispatch } from "src/stores/store"; import { DONATION_ADDRESS } from "src/utils"; export const DonateDialog = ({ - onSubmit, isOpen, onClose, assetBalance, - csvText, }: { - onSubmit: (donationRow: string) => void; isOpen: boolean; onClose: () => void; assetBalance: AssetBalance; - csvText: string; }) => { const { safe } = useSafeAppsSDK(); + const dispatch = useAppDispatch(); + const csvContent = useCsvContent(); + const nativeSymbol = networkInfo.get(safe.chainId)?.currencySymbol || "ETH"; const items = assetBalance?.map((asset) => ({ @@ -66,7 +68,7 @@ export const DonateDialog = ({ const handleSubmit = () => { if (selectedToken && selectedAmount) { - const headerRow = csvText.split(/\r\n|\r|\n/)[0]; + const headerRow = csvContent.split(/\r\n|\r|\n/)[0]; const donationCSVRow = headerRow .replace("token_type", "erc20") .replace("token_address", selectedToken === "0x0" ? "" : selectedToken) @@ -75,7 +77,11 @@ export const DonateDialog = ({ .replace("value", selectedAmount) .replace("id", ""); - onSubmit(`${csvText}\n${donationCSVRow}`); + dispatch( + updateCsvContent({ + csvContent: `${csvContent}\n${donationCSVRow}`, + }), + ); onClose(); } }; diff --git a/src/components/DrainSafeDialog.tsx b/src/components/DrainSafeDialog.tsx index 59ce2400..b1936508 100644 --- a/src/components/DrainSafeDialog.tsx +++ b/src/components/DrainSafeDialog.tsx @@ -4,27 +4,29 @@ import { Typography } from "@material-ui/core"; import BigNumber from "bignumber.js"; import { utils } from "ethers"; import { useState } from "react"; -import { AssetBalance, CollectibleBalance } from "src/hooks/balances"; import { useEnsResolver } from "src/hooks/ens"; import { networkInfo } from "src/networks"; +import { AssetBalance, NFTBalance } from "src/stores/api/balanceApi"; +import { updateCsvContent } from "src/stores/slices/csvEditorSlice"; +import { useAppDispatch } from "src/stores/store"; import { fromWei } from "src/utils"; export const DrainSafeDialog = ({ - onSubmit, isOpen, onClose, assetBalance, - collectibleBalance, + nftBalance, }: { - onSubmit: (csvText: string) => void; isOpen: boolean; onClose: () => void; assetBalance: AssetBalance; - collectibleBalance: CollectibleBalance; + nftBalance: NFTBalance; }) => { const [drainAddress, setDrainAddress] = useState(""); const { safe } = useSafeAppsSDK(); + const dispatch = useAppDispatch(); + const selectedNetworkInfo = networkInfo.get(safe.chainId); const ensResolver = useEnsResolver(); @@ -54,11 +56,11 @@ export const DrainSafeDialog = ({ } }); - collectibleBalance?.forEach((collectible) => { + nftBalance?.results.forEach((collectible) => { drainCSV += `\nnft,${collectible.address},${drainAddress},,${collectible.id}`; }); } - onSubmit(drainCSV); + dispatch(updateCsvContent({ csvContent: drainCSV })); }; if (!isOpen) { diff --git a/src/components/GenerateTransfersMenu.tsx b/src/components/GenerateTransfersMenu.tsx index f6f96900..e17a98e9 100644 --- a/src/components/GenerateTransfersMenu.tsx +++ b/src/components/GenerateTransfersMenu.tsx @@ -1,21 +1,14 @@ import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; import { Breadcrumb, BreadcrumbElement, ButtonLink, Tooltip } from "@gnosis.pm/safe-react-components"; import { useState } from "react"; +import { useGetAssetBalanceQuery, useGetAllNFTsQuery } from "src/stores/api/balanceApi"; import styled from "styled-components"; -import { AssetBalance, CollectibleBalance } from "../hooks/balances"; import { NETWORKS_WITH_DONATIONS_DEPLOYED } from "../networks"; import { DonateDialog } from "./DonateDialog"; import { DrainSafeDialog } from "./DrainSafeDialog"; -export interface GenerateTransfersMenuProps { - assetBalance?: AssetBalance; - collectibleBalance?: CollectibleBalance; - setCsvText: (csv: string) => void; - csvText: string; -} - const GenerateHeader = styled(Breadcrumb)` padding: 0px 0px 8px 0px; `; @@ -30,8 +23,13 @@ const GenerateMenuButton = styled(ButtonLink)` } `; -export const GenerateTransfersMenu = (props: GenerateTransfersMenuProps) => { - const { assetBalance, collectibleBalance, setCsvText, csvText } = props; +export const GenerateTransfersMenu = () => { + const assetBalanceQuery = useGetAssetBalanceQuery(); + const nftBalanceQuery = useGetAllNFTsQuery(); + + const assetBalance = assetBalanceQuery.currentData; + const nftBalance = nftBalanceQuery.currentData; + const [isDrainModalOpen, setIsDrainModalOpen] = useState(false); const [isDonateModalOpen, setIsDonateModalOpen] = useState(false); @@ -71,12 +69,11 @@ export const GenerateTransfersMenu = (props: GenerateTransfersMenuProps) => { )} - {assetBalance && collectibleBalance && ( + {nftBalance && assetBalance && ( setIsDrainModalOpen(false)} - onSubmit={(drainCsv) => setCsvText(drainCsv)} isOpen={isDrainModalOpen} /> )} @@ -85,8 +82,6 @@ export const GenerateTransfersMenu = (props: GenerateTransfersMenuProps) => { assetBalance={assetBalance} isOpen={isDonateModalOpen} onClose={() => setIsDonateModalOpen(false)} - onSubmit={(updatedCSV) => setCsvText(updatedCSV)} - csvText={csvText} /> )} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index a5f1cedb..eb193630 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,11 +1,12 @@ import { Icon, Text } from "@gnosis.pm/safe-react-components"; import { Fab, Snackbar } from "@material-ui/core"; import MuiAlert from "@material-ui/lab/Alert"; -import React, { useContext } from "react"; +import React from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { hideMessages, Message, removeMessage, toggleMessages } from "src/stores/slices/messageSlice"; +import { RootState } from "src/stores/store"; import styled from "styled-components"; -import { MessageContext, Message } from "../contexts/MessageContextProvider"; - import { FAQModal } from "./FAQModal"; export function Alert(props) { @@ -32,14 +33,13 @@ const AlertWrapper = styled.div` `; export const Header = (): JSX.Element => { - const { messages, showMessages, hideMessages, toggleMessages, removeMessage } = useContext(MessageContext); - + const dispatch = useDispatch(); + const { messages, showMessages } = useSelector((state: RootState) => state.messages); const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => { if (reason === "clickaway") { return; } - - hideMessages(); + dispatch(hideMessages()); }; return ( @@ -49,7 +49,7 @@ export const Header = (): JSX.Element => { size="small" className={messages.length === 0 ? "statusDotButtonEmpty" : "statusDotButtonErrors"} style={{ textTransform: "none", width: "34px", height: "34px" }} - onClick={toggleMessages} + onClick={() => dispatch(toggleMessages())} > {messages.length === 0 ? ( @@ -74,7 +74,7 @@ export const Header = (): JSX.Element => { )} {messages.map((message: Message, index: number) => ( - removeMessage(message)}> + dispatch(removeMessage(message))}> {message.message} ))} diff --git a/src/components/Summary.tsx b/src/components/Summary.tsx index 37ec4a62..472c095c 100644 --- a/src/components/Summary.tsx +++ b/src/components/Summary.tsx @@ -1,6 +1,6 @@ import { Accordion, AccordionDetails, AccordionSummary, Icon, Text } from "@gnosis.pm/safe-react-components"; -import { AssetTransfer, CollectibleTransfer } from "../parser/csvParser"; +import { AssetTransfer, CollectibleTransfer } from "../hooks/useCsvParser"; import { AssetTransferTable } from "./assets/AssetTransferTable"; import { CollectiblesTransferTable } from "./assets/CollectiblesTransferTable"; diff --git a/src/components/assets/AssetTransferTable.tsx b/src/components/assets/AssetTransferTable.tsx index 7609f1f5..9f802d32 100644 --- a/src/components/assets/AssetTransferTable.tsx +++ b/src/components/assets/AssetTransferTable.tsx @@ -3,7 +3,7 @@ import React, { memo } from "react"; import AutoSizer from "react-virtualized-auto-sizer"; import { areEqual, FixedSizeList as List } from "react-window"; -import { AssetTransfer } from "../../parser/csvParser"; +import { AssetTransfer } from "../../hooks/useCsvParser"; import { Receiver } from "../Receiver"; import { ERC20Token } from "./ERC20Token"; @@ -64,7 +64,7 @@ const Row = memo((props: RowProps) => {
- {row.amount?.toFixed()} + {row.amount}
diff --git a/src/components/assets/CollectiblesTransferTable.tsx b/src/components/assets/CollectiblesTransferTable.tsx index 78e20fad..5df92e55 100644 --- a/src/components/assets/CollectiblesTransferTable.tsx +++ b/src/components/assets/CollectiblesTransferTable.tsx @@ -3,7 +3,7 @@ import React, { memo } from "react"; import AutoSizer from "react-virtualized-auto-sizer"; import { areEqual, FixedSizeList as List } from "react-window"; -import { CollectibleTransfer } from "../../parser/csvParser"; +import { CollectibleTransfer } from "../../hooks/useCsvParser"; import { Receiver } from "../Receiver"; import { ERC721Token } from "./ERC721Token"; @@ -69,21 +69,16 @@ export const Row = memo((props: RowProps) => { alignItems: "center", }} > - +
{row.token_type.toUpperCase()}
- {row.amount?.toFixed()} + {row.amount}
- {row.tokenId.toFixed()} + {row.tokenId}
diff --git a/src/components/assets/ERC721Token.tsx b/src/components/assets/ERC721Token.tsx index becc246f..d6f1c3c6 100644 --- a/src/components/assets/ERC721Token.tsx +++ b/src/components/assets/ERC721Token.tsx @@ -8,9 +8,8 @@ import { CollectibleTokenMetaInfo, useCollectibleTokenInfoProvider } from "../.. type TokenProps = { tokenAddress: string; - id: BigNumber; + id: string; token_type: "erc721" | "erc1155"; - hasMetaData: boolean; }; const Container = styled.div` @@ -33,24 +32,23 @@ export const ERC721Token = (props: TokenProps) => { const collectibleTokenInfoProvider = useCollectibleTokenInfoProvider(); - const { tokenAddress, id, token_type, hasMetaData } = props; + const { tokenAddress, id, token_type } = props; const imageZoomedIn = Boolean(anchorEl); useEffect(() => { let isMounted = true; - if (hasMetaData) { - setIsMetaDataLoading(true); - collectibleTokenInfoProvider.fetchMetaInfo(tokenAddress, id, token_type).then((result) => { - if (isMounted) { - setTokenMetaData(result); - setIsMetaDataLoading(false); - } - }); - } + setIsMetaDataLoading(true); + collectibleTokenInfoProvider.fetchMetaInfo(tokenAddress, new BigNumber(id), token_type).then((result) => { + if (isMounted) { + setTokenMetaData(result); + setIsMetaDataLoading(false); + } + }); + return function callback() { isMounted = false; }; - }, [hasMetaData, collectibleTokenInfoProvider, tokenAddress, id, token_type]); + }, [collectibleTokenInfoProvider, tokenAddress, id, token_type]); return ( diff --git a/src/contexts/MessageContextProvider.tsx b/src/contexts/MessageContextProvider.tsx deleted file mode 100644 index b552eea9..00000000 --- a/src/contexts/MessageContextProvider.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useState, ReactElement, useCallback } from "react"; - -export type CodeWarning = { - message: string; - severity: string; - lineNo: number; -}; - -export type Message = { - message: string; - severity: string; -}; - -type MessageContextType = { - messages: Message[]; - setMessages: (messages: Message[]) => void; - removeMessage: (message: Message) => void; - addMessage: (message: Message) => void; - codeWarnings: CodeWarning[]; - setCodeWarnings: (messages: CodeWarning[]) => void; - showMessages: boolean; - hideMessages: () => void; - toggleMessages: () => void; -}; - -export const MessageContext = React.createContext({ - messages: [], - setMessages: (messages: (Message | CodeWarning)[]) => {}, - removeMessage: (message: Message | CodeWarning) => {}, - addMessage: (message: Message | CodeWarning) => {}, - codeWarnings: [], - setCodeWarnings: (messages: CodeWarning[]) => {}, - showMessages: false, - hideMessages: () => {}, - toggleMessages: () => {}, -}); - -type MessageContextProviderProps = { - children: ReactElement; -}; - -export const MessageContextProvider = (props: MessageContextProviderProps) => { - const [messages, internalSetMessages] = useState([]); - const [codeWarnings, setCodeWarnings] = useState([]); - const [showMessages, setShowMessages] = useState(false); - - const removeMessage = (messageToRemove: Message | CodeWarning) => { - setMessages(messages.filter((message) => message.message !== messageToRemove.message)); - }; - - const setMessages = useCallback((newMessages: Message[]) => { - internalSetMessages(newMessages); - if (newMessages.length > 0) { - setShowMessages(true); - } - }, []); - - const addMessage = (messageToAdd: Message | CodeWarning) => { - // Do not add equal message - if (!messages.some((message) => message.message === messageToAdd.message)) { - setMessages([messageToAdd, ...messages]); - } - }; - - const hideMessages = () => setShowMessages(false); - - const toggleMessages = () => setShowMessages(!showMessages); - - const contextValue = { - messages, - setMessages, - removeMessage, - addMessage, - codeWarnings, - setCodeWarnings, - showMessages, - hideMessages, - toggleMessages, - }; - - return {props.children}; -}; diff --git a/src/hooks/balances.ts b/src/hooks/balances.ts deleted file mode 100644 index d7d825c3..00000000 --- a/src/hooks/balances.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; -import { useCallback, useEffect, useMemo, useState } from "react"; - -import { networkInfo } from "../networks"; - -type Token = { - name: string; - symbol: string; - decimals: number; -}; - -type AssetBalanceEntry = { - tokenAddress: string | null; - token: Token | null; - balance: string; - decimals: number; -}; - -type CollectibleBalanceEntry = { - address: string; - tokenName: string; - tokenSymbol: string; - id: string; -}; - -export type AssetBalance = AssetBalanceEntry[]; -export type CollectibleBalance = CollectibleBalanceEntry[]; - -export interface BalanceLoader { - assetBalance: AssetBalance | undefined; - - collectibleBalance: CollectibleBalance | undefined; - - isLoading: boolean; -} - -export const useBalances: () => BalanceLoader = () => { - const { safe } = useSafeAppsSDK(); - - const [isAssetBalanceLoading, setIsAssetBalanceLoading] = useState(true); - const [isCollectibleBalanceLoading, setIsCollectibleBalanceLoading] = useState(true); - - const [assetBalance, setAssetBalance] = useState(undefined); - const [collectibleBalance, setCollectibleBalance] = useState(undefined); - - const fetchAssetBalance = useCallback(async (chainId: number, safeAddress: string) => { - if (networkInfo.has(chainId) && networkInfo.get(chainId)?.baseAPI) { - return await fetch( - `${networkInfo.get(chainId)?.baseAPI}/safes/${safeAddress}/balances?trusted=false&exclude_spam=true`, - ) - .then((response) => { - if (response.ok) { - return response.json() as Promise; - } else { - throw Error(response.statusText); - } - }) - .catch(() => undefined); - } else { - return undefined; - } - }, []); - - const fetchCollectibleBalance = useCallback(async (chainId: number, safeAddress: string) => { - if (networkInfo.has(chainId) && networkInfo.get(chainId)?.baseAPI) { - return await fetch( - `${networkInfo.get(chainId)?.baseAPI}/safes/${safeAddress}/collectibles?trusted=false&exclude_spam=true`, - ) - .then((response) => { - if (response.ok) { - return response.json() as Promise; - } else { - throw Error(response.statusText); - } - }) - .catch(() => undefined); - } else { - return undefined; - } - }, []); - - useEffect(() => { - let isMounted = true; - setIsAssetBalanceLoading(true); - setIsCollectibleBalanceLoading(true); - - fetchAssetBalance(safe.chainId, safe.safeAddress).then((result) => { - if (isMounted) { - setAssetBalance(result); - setIsAssetBalanceLoading(false); - } - }); - - fetchCollectibleBalance(safe.chainId, safe.safeAddress).then((result) => { - if (isMounted) { - setCollectibleBalance(result); - setIsCollectibleBalanceLoading(false); - } - }); - return function callback() { - isMounted = false; - }; - }, [fetchAssetBalance, fetchCollectibleBalance, safe.chainId, safe.safeAddress]); - - return { - assetBalance: useMemo(() => assetBalance, [assetBalance]), - collectibleBalance: useMemo(() => collectibleBalance, [collectibleBalance]), - isLoading: isAssetBalanceLoading || isCollectibleBalanceLoading, - }; -}; diff --git a/src/hooks/collectibleTokenInfoProvider.ts b/src/hooks/collectibleTokenInfoProvider.ts index f97294a4..ea71b3b5 100644 --- a/src/hooks/collectibleTokenInfoProvider.ts +++ b/src/hooks/collectibleTokenInfoProvider.ts @@ -3,6 +3,7 @@ import { useSafeAppsSDK } from "@gnosis.pm/safe-apps-react-sdk"; import BigNumber from "bignumber.js"; import { ethers } from "ethers"; import { useCallback, useMemo } from "react"; +import { useGetAllNFTsQuery } from "src/stores/api/balanceApi"; import { resolveIpfsUri } from "src/utils"; import { erc1155Instance } from "../transfers/erc1155"; @@ -10,14 +11,11 @@ import { erc165Instance } from "../transfers/erc165"; import { erc721Instance } from "../transfers/erc721"; const ERC721_INTERFACE_ID = "0x80ac58cd"; -const ERC721_METADATA_INTERFACE_ID = "0x5b5e139f"; const ERC1155_INTERFACE_ID = "0xd9b67a26"; -const ERC1155_METADATA_INTERFACE_ID = "0x0e89341c"; export type CollectibleTokenInfo = { token_type: "erc721" | "erc1155"; address: string; - hasMetaInfo: boolean; }; export type CollectibleTokenMetaInfo = { @@ -38,36 +36,33 @@ export interface CollectibleTokenInfoProvider { export const useCollectibleTokenInfoProvider: () => CollectibleTokenInfoProvider = () => { const { safe, sdk } = useSafeAppsSDK(); const web3Provider = useMemo(() => new ethers.providers.Web3Provider(new SafeAppProvider(safe, sdk)), [sdk, safe]); + const nftBalanceQuery = useGetAllNFTsQuery(); + const currentNftBalance = nftBalanceQuery.currentData; const collectibleContractCache = useMemo(() => new Map(), []); - const contractInterfaceCache = useMemo( - () => new Map(), - [], - ); + const contractInterfaceCache = useMemo(() => new Map(), []); - const determineInterface: ( - tokenAddress: string, - ) => Promise<["erc721" | "erc721_Meta" | "erc1155" | "erc1155_Meta" | undefined]> = useCallback( + const determineInterface: (tokenAddress: string) => Promise<["erc721" | "erc1155" | undefined]> = useCallback( async (tokenAddress: string) => { if (contractInterfaceCache.has(tokenAddress)) { return contractInterfaceCache.get(tokenAddress) ?? [undefined]; } - let determinedInterface: ["erc721" | "erc721_Meta" | "erc1155" | "erc1155_Meta" | undefined] = [undefined]; + if (currentNftBalance) { + const tokenInfo = currentNftBalance.results.find((nftEntry) => nftEntry.address === tokenAddress); + if (tokenInfo) { + return Promise.resolve(["erc721"]); + } + } + let determinedInterface: ["erc721" | "erc1155" | undefined] = [undefined]; const erc165Contract = erc165Instance(tokenAddress, web3Provider); const isErc1155 = await erc165Contract.supportsInterface(ERC1155_INTERFACE_ID).catch(() => false); if (isErc1155) { - determinedInterface = ["erc1155"]; - if (await erc165Contract.supportsInterface(ERC1155_METADATA_INTERFACE_ID).catch(() => false)) { - determinedInterface.push("erc1155_Meta"); - } + return ["erc1155"]; } else { const isErc721 = await erc165Contract.supportsInterface(ERC721_INTERFACE_ID).catch(() => false); if (isErc721) { - determinedInterface = ["erc721"]; - if (await erc165Contract.supportsInterface(ERC721_METADATA_INTERFACE_ID).catch(() => false)) { - determinedInterface.push("erc721_Meta"); - } + return ["erc721"]; } } if (determinedInterface) { @@ -75,7 +70,7 @@ export const useCollectibleTokenInfoProvider: () => CollectibleTokenInfoProvider } return determinedInterface; }, - [contractInterfaceCache, web3Provider], + [contractInterfaceCache, currentNftBalance, web3Provider], ); const getTokenInfo = useCallback( async (tokenAddress: string, id: BigNumber) => { @@ -92,13 +87,11 @@ export const useCollectibleTokenInfoProvider: () => CollectibleTokenInfoProvider fetchedTokenInfo = { token_type: "erc721", address: tokenAddress, - hasMetaInfo: tokenInterfaces.includes("erc721_Meta"), }; } else if (tokenInterfaces.includes("erc1155")) { fetchedTokenInfo = { token_type: "erc1155", address: tokenAddress, - hasMetaInfo: tokenInterfaces.includes("erc1155_Meta"), }; } // We don't remember undefined tokenInfos in case there was a i.e. connection problem @@ -117,6 +110,15 @@ export const useCollectibleTokenInfoProvider: () => CollectibleTokenInfoProvider ) => Promise = useCallback( async (tokenAddress: string, id: BigNumber, token_type: "erc1155" | "erc721") => { if (token_type === "erc721") { + if (currentNftBalance) { + const tokenInfo = currentNftBalance.results.find((nftEntry) => nftEntry.address === tokenAddress); + if (tokenInfo && tokenInfo.imageUri && tokenInfo.name) { + return { + imageURI: tokenInfo.imageUri, + name: tokenInfo.name, + }; + } + } const erc721Contract = erc721Instance(tokenAddress, web3Provider); const metaInfo: CollectibleTokenMetaInfo = { name: await erc721Contract.name().catch(() => undefined), @@ -141,7 +143,7 @@ export const useCollectibleTokenInfoProvider: () => CollectibleTokenInfoProvider return metaInfo; } }, - [web3Provider], + [currentNftBalance, web3Provider], ); const getFromAddress = useCallback(() => { diff --git a/src/hooks/useCsvContent.ts b/src/hooks/useCsvContent.ts new file mode 100644 index 00000000..a2b2ba8a --- /dev/null +++ b/src/hooks/useCsvContent.ts @@ -0,0 +1,4 @@ +import { selectCsvContent } from "src/stores/slices/csvEditorSlice"; +import { useAppSelector } from "src/stores/store"; + +export const useCsvContent = () => useAppSelector(selectCsvContent); diff --git a/src/hooks/useCsvParser.ts b/src/hooks/useCsvParser.ts new file mode 100644 index 00000000..dbbce3b3 --- /dev/null +++ b/src/hooks/useCsvParser.ts @@ -0,0 +1,104 @@ +import { parseString, RowValidateCallback } from "@fast-csv/parse"; +import { useCallback } from "react"; +import { transform } from "src/parser/transformation"; +import { validateRow } from "src/parser/validation"; +import { CodeWarning } from "src/stores/slices/messageSlice"; + +import { useCollectibleTokenInfoProvider } from "./collectibleTokenInfoProvider"; +import { useEnsResolver } from "./ens"; +import { useTokenInfoProvider } from "./token"; + +export type Transfer = AssetTransfer | CollectibleTransfer; + +export type AssetTokenType = "erc20" | "native"; +export type CollectibleTokenType = "erc721" | "erc1155"; + +export interface AssetTransfer { + token_type: AssetTokenType; + receiver: string; + amount: string; + tokenAddress: string | null; + decimals: number; + symbol?: string; + receiverEnsName: string | null; + position?: number; +} + +export interface CollectibleTransfer { + token_type: CollectibleTokenType; + from: string; + receiver: string; + tokenAddress: string; + tokenName?: string; + tokenId: string; + amount?: string; + receiverEnsName: string | null; +} + +export interface UnknownTransfer { + token_type: "unknown"; +} + +export type CSVRow = { + token_type?: string; + token_address: string; + receiver: string; + value?: string; + amount?: string; + id?: string; +}; + +const generateWarnings = ( + // We need the row parameter because of the api of fast-csv + _row: Transfer, + rowNumber: number, + warnings: string, +) => { + const messages: CodeWarning[] = warnings.split(";").map((warning: string) => ({ + message: warning, + severity: "warning", + lineNo: rowNumber, + })); + return messages; +}; + +const countLines = (text: string) => text.split(/\r\n|\r|\n/).length; + +export const useCsvParser = (): { parseCsv: (csvText: string) => Promise<[Transfer[], CodeWarning[]]> } => { + const collectibleTokenInfoProvider = useCollectibleTokenInfoProvider(); + const tokenInfoProvider = useTokenInfoProvider(); + const ensResolver = useEnsResolver(); + + const parseCsv = useCallback( + (csvText: string) => { + const noLines = countLines(csvText); + // Hard limit at 500 lines of txs + if (noLines > 501) { + return new Promise<[Transfer[], CodeWarning[]]>((resolve, reject) => { + reject("Max number of lines exceeded. Due to the block gas limit transactions are limited to 500 lines."); + }); + } + + return new Promise<[Transfer[], CodeWarning[]]>((resolve, reject) => { + const results: Transfer[] = []; + const resultingWarnings: CodeWarning[] = []; + parseString(csvText, { headers: true }) + .transform((row: CSVRow, callback) => + transform(row, tokenInfoProvider, collectibleTokenInfoProvider, ensResolver, callback), + ) + .validate((row: Transfer | UnknownTransfer, callback: RowValidateCallback) => validateRow(row, callback)) + .on("data", (data: Transfer) => results.push(data)) + .on("end", () => resolve([results, resultingWarnings])) + .on("data-invalid", (row: Transfer, rowNumber: number, warnings: string) => + resultingWarnings.push(...generateWarnings(row, rowNumber, warnings)), + ) + .on("error", (error) => reject(error)); + }); + }, + [collectibleTokenInfoProvider, ensResolver, tokenInfoProvider], + ); + + return { + parseCsv, + }; +}; diff --git a/src/index.tsx b/src/index.tsx index ee8a60d1..36a79ba5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,11 +2,12 @@ import SafeProvider from "@gnosis.pm/safe-apps-react-sdk"; import { theme, Loader, Title } from "@gnosis.pm/safe-react-components"; import React from "react"; import ReactDOM from "react-dom"; +import { Provider as ReduxProvider } from "react-redux"; import { ThemeProvider } from "styled-components"; import App from "./App"; import GlobalStyle from "./GlobalStyle"; -import { MessageContextProvider } from "./contexts/MessageContextProvider"; +import { store } from "./stores/store"; ReactDOM.render( @@ -20,9 +21,9 @@ ReactDOM.render( } > - + - + , diff --git a/src/parser/balanceCheck.ts b/src/parser/balanceCheck.ts index 79b6248b..72b7be8e 100644 --- a/src/parser/balanceCheck.ts +++ b/src/parser/balanceCheck.ts @@ -1,10 +1,9 @@ import { BigNumber } from "bignumber.js"; +import { AssetBalance, NFTBalance } from "src/stores/api/balanceApi"; -import { AssetBalance, CollectibleBalance } from "../hooks/balances"; +import { AssetTransfer, CollectibleTransfer, Transfer } from "../hooks/useCsvParser"; import { toWei } from "../utils"; -import { AssetTransfer, CollectibleTransfer, Transfer } from "./csvParser"; - export type AssetSummaryEntry = { tokenAddress: string | null; amount: BigNumber; @@ -32,14 +31,14 @@ export const assetTransfersToSummary = (transfers: AssetTransfer[]) => { export type CollectibleSummaryEntry = { tokenAddress: string; - id: BigNumber; + id: string; count: number; name?: string; }; export const collectibleTransfersToSummary = (transfers: CollectibleTransfer[]) => { return transfers.reduce((previousValue, currentValue): Map => { - const entryKey = `${currentValue.tokenAddress}:${currentValue.tokenId.toFixed()}`; + const entryKey = `${currentValue.tokenAddress}:${currentValue.tokenId}`; let tokenSummary = previousValue.get(entryKey); if (typeof tokenSummary === "undefined") { tokenSummary = { @@ -61,12 +60,12 @@ export type InsufficientBalanceInfo = { transferAmount?: string; isDuplicate: boolean; token_type: "erc20" | "native" | "erc721"; - id?: BigNumber; + id?: string; }; export const checkAllBalances = ( assetBalance: AssetBalance | undefined, - collectibleBalance: CollectibleBalance | undefined, + collectibleBalance: NFTBalance | undefined, transfers: Transfer[], ): InsufficientBalanceInfo[] => { const insufficientTokens: InsufficientBalanceInfo[] = []; @@ -92,7 +91,7 @@ export const checkAllBalances = ( !isSufficientBalance(new BigNumber(tokenBalance.balance), amount, 18) ) { insufficientTokens.push({ - token: "ETH", + token: tokenBalance?.token?.symbol || "ETH", token_type: "native", transferAmount: amount.toFixed(), isDuplicate: false, // For Erc20 / Coin Transfers duplicates are never an issue @@ -117,16 +116,16 @@ export const checkAllBalances = ( } for (const { tokenAddress, count, name, id } of collectibleSummary.values()) { - const tokenBalance = collectibleBalance?.find( - (balanceEntry) => - balanceEntry.address?.toLowerCase() === tokenAddress.toLowerCase() && balanceEntry.id === id.toFixed(), + const tokenBalance = collectibleBalance?.results.find( + (balanceEntry) => balanceEntry.address?.toLowerCase() === tokenAddress.toLowerCase() && balanceEntry.id === id, ); if (typeof tokenBalance === "undefined" || count > 1) { const tokenName = name ?? tokenBalance?.tokenName ?? - collectibleBalance?.find((balanceEntry) => balanceEntry.address?.toLowerCase() === tokenAddress.toLowerCase()) - ?.tokenName; + collectibleBalance?.results.find( + (balanceEntry) => balanceEntry.address?.toLowerCase() === tokenAddress.toLowerCase(), + )?.tokenName; insufficientTokens.push({ token: tokenName ?? tokenAddress, token_type: "erc721", diff --git a/src/parser/csvParser.ts b/src/parser/csvParser.ts deleted file mode 100644 index 30ded0b2..00000000 --- a/src/parser/csvParser.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { parseString, RowValidateCallback } from "@fast-csv/parse"; -import { BigNumber } from "bignumber.js"; - -import { CodeWarning } from "../contexts/MessageContextProvider"; -import { CollectibleTokenInfoProvider } from "../hooks/collectibleTokenInfoProvider"; -import { EnsResolver } from "../hooks/ens"; -import { TokenInfoProvider } from "../hooks/token"; - -import { transform } from "./transformation"; -import { validateRow } from "./validation"; - -/** - * Includes methods to parse, transform and validate csv content - */ - -export type Transfer = AssetTransfer | CollectibleTransfer; - -export type AssetTokenType = "erc20" | "native"; -export type CollectibleTokenType = "erc721" | "erc1155"; - -export interface AssetTransfer { - token_type: AssetTokenType; - receiver: string; - amount: BigNumber; - tokenAddress: string | null; - decimals: number; - symbol?: string; - receiverEnsName: string | null; - position?: number; -} - -export interface CollectibleTransfer { - token_type: CollectibleTokenType; - from: string; - receiver: string; - tokenAddress: string; - tokenName?: string; - tokenId: BigNumber; - amount?: BigNumber; - receiverEnsName: string | null; - hasMetaData: boolean; -} - -export interface UnknownTransfer { - token_type: "unknown"; -} - -export type CSVRow = { - token_type?: string; - token_address: string; - receiver: string; - value?: string; - amount?: string; - id?: string; -}; - -const generateWarnings = ( - // We need the row parameter because of the api of fast-csv - _row: Transfer, - rowNumber: number, - warnings: string, -) => { - const messages: CodeWarning[] = warnings.split(";").map((warning: string) => ({ - message: warning, - severity: "warning", - lineNo: rowNumber, - })); - return messages; -}; - -const countLines = (text: string) => text.split(/\r\n|\r|\n/).length; - -export class CSVParser { - public static parseCSV = ( - csvText: string, - tokenInfoProvider: TokenInfoProvider, - erc721TokenInfoProvider: CollectibleTokenInfoProvider, - ensResolver: EnsResolver, - ): Promise<[Transfer[], CodeWarning[]]> => { - const noLines = countLines(csvText); - // Hard limit at 400 lines of txs - if (noLines > 401) { - return new Promise<[Transfer[], CodeWarning[]]>((resolve, reject) => { - reject({ - message: "Max number of lines exceeded. Due to the block gas limit transactions are limited to 400 lines.", - }); - }); - } - - return new Promise<[Transfer[], CodeWarning[]]>((resolve, reject) => { - const results: Transfer[] = []; - const resultingWarnings: CodeWarning[] = []; - parseString(csvText, { headers: true }) - .transform((row: CSVRow, callback) => - transform(row, tokenInfoProvider, erc721TokenInfoProvider, ensResolver, callback), - ) - .validate((row: Transfer | UnknownTransfer, callback: RowValidateCallback) => validateRow(row, callback)) - .on("data", (data: Transfer) => results.push(data)) - .on("end", () => resolve([results, resultingWarnings])) - .on("data-invalid", (row: Transfer, rowNumber: number, warnings: string) => - resultingWarnings.push(...generateWarnings(row, rowNumber, warnings)), - ) - .on("error", (error) => reject(error)); - }); - }; -} diff --git a/src/parser/transformation.ts b/src/parser/transformation.ts index 8f1f74c2..da10b5f9 100644 --- a/src/parser/transformation.ts +++ b/src/parser/transformation.ts @@ -5,22 +5,21 @@ import { utils } from "ethers"; import { CollectibleTokenInfoProvider } from "../hooks/collectibleTokenInfoProvider"; import { EnsResolver } from "../hooks/ens"; import { TokenInfoProvider } from "../hooks/token"; - -import { AssetTransfer, CollectibleTransfer, CSVRow, Transfer, UnknownTransfer } from "./csvParser"; +import { AssetTransfer, CollectibleTransfer, CSVRow, Transfer, UnknownTransfer } from "../hooks/useCsvParser"; interface PrePayment { receiver: string; - amount: BigNumber; + amount: string; tokenAddress: string | null; tokenType: "erc20" | "native"; } interface PreCollectibleTransfer { receiver: string; - tokenId: BigNumber; + tokenId: string; tokenAddress: string; tokenType: "nft"; - amount?: BigNumber; + amount?: string; } export const transform = ( @@ -83,7 +82,7 @@ export const transformAsset = ( const prePayment: PrePayment = { // avoids errors from getAddress. Invalid addresses are later caught in validateRow tokenAddress: transformERC20TokenAddress(row.token_address), - amount: new BigNumber(row.amount ?? row.value ?? ""), + amount: row.amount ?? row.value ?? "", receiver: normalizeAddress(trimMatchingNetwork(row.receiver, selectedChainShortname)), tokenType: row.token_type, }; @@ -155,13 +154,15 @@ export const transformCollectible = ( ensResolver: EnsResolver, callback: RowTransformCallback, ): void => { + let amount = row.amount ?? row.value ?? "1"; + amount = amount === "" ? "1" : amount; const prePayment: PreCollectibleTransfer = { // avoids errors from getAddress. Invalid addresses are later caught in validateRow tokenAddress: normalizeAddress(row.token_address), - tokenId: new BigNumber(row.id ?? ""), + tokenId: row.id ?? "", receiver: normalizeAddress(row.receiver), tokenType: row.token_type, - amount: new BigNumber(row.amount ?? ""), + amount, }; toCollectibleTransfer(prePayment, erc721InfoProvider, ensResolver) @@ -186,7 +187,7 @@ const toCollectibleTransfer = async ( const tokenInfo = await collectibleTokenInfoProvider.getTokenInfo( preCollectible.tokenAddress, - preCollectible.tokenId, + new BigNumber(preCollectible.tokenId), ); if (tokenInfo?.token_type === "erc721") { @@ -197,7 +198,6 @@ const toCollectibleTransfer = async ( tokenAddress: preCollectible.tokenAddress, receiverEnsName, token_type: "erc721", - hasMetaData: tokenInfo.hasMetaInfo, }; } else if (tokenInfo?.token_type === "erc1155") { return { @@ -208,7 +208,6 @@ const toCollectibleTransfer = async ( receiverEnsName, amount: preCollectible.amount, token_type: "erc1155", - hasMetaData: tokenInfo.hasMetaInfo, }; } else { // return a fake token which will fail validation. @@ -220,7 +219,6 @@ const toCollectibleTransfer = async ( tokenName: "TOKEN_NOT_FOUND", receiverEnsName, token_type: "erc721", - hasMetaData: false, }; } }; diff --git a/src/parser/validation.ts b/src/parser/validation.ts index c4414b4f..d439cacc 100644 --- a/src/parser/validation.ts +++ b/src/parser/validation.ts @@ -1,7 +1,8 @@ import { RowValidateCallback } from "@fast-csv/parse"; +import { BigNumber } from "bignumber.js"; import { utils } from "ethers"; -import { AssetTransfer, CollectibleTransfer, Transfer, UnknownTransfer } from "./csvParser"; +import { AssetTransfer, CollectibleTransfer, Transfer, UnknownTransfer } from "../hooks/useCsvParser"; export const validateRow = (row: Transfer | UnknownTransfer, callback: RowValidateCallback) => { switch (row.token_type) { @@ -54,7 +55,7 @@ const areAddressesValid = (row: Transfer): string[] => { }; const isAmountPositive = (row: AssetTransfer): string[] => - row.amount.isGreaterThan(0) ? [] : ["Only positive amounts/values possible: " + row.amount.toFixed()]; + new BigNumber(row.amount).isGreaterThan(0) ? [] : ["Only positive amounts/values possible: " + row.amount]; const isAssetTokenValid = (row: AssetTransfer): string[] => row.decimals === -1 && row.symbol === "TOKEN_NOT_FOUND" ? [`No token contract was found at ${row.tokenAddress}`] : []; @@ -62,18 +63,25 @@ const isAssetTokenValid = (row: AssetTransfer): string[] => const isCollectibleTokenValid = (row: CollectibleTransfer): string[] => row.tokenName === "TOKEN_NOT_FOUND" ? [`No token contract was found at ${row.tokenAddress}`] : []; -const isTokenIdPositive = (row: CollectibleTransfer): string[] => - row.tokenId.isPositive() ? [] : [`Only positive Token IDs possible: ${row.tokenId.toFixed()}`]; - -const isTokenIdInteger = (row: CollectibleTransfer): string[] => - row.tokenId.isInteger() ? [] : [`Token IDs must be integer numbers: ${row.tokenId.toFixed()}`]; - -const isTokenValueInteger = (row: CollectibleTransfer): string[] => - !row.amount || row.amount.isNaN() || row.amount.isInteger() - ? [] - : [`Value of ERC1155 must be an integer: ${row.amount.toFixed()}`]; +const isTokenIdPositive = (row: CollectibleTransfer): string[] => { + const tokenIdAsNumber = new BigNumber(row.tokenId); + return tokenIdAsNumber.isPositive() ? [] : [`Only positive Token IDs possible: ${tokenIdAsNumber.toFixed()}`]; +}; +const isTokenIdInteger = (row: CollectibleTransfer): string[] => { + const tokenIdAsNumber = new BigNumber(row.tokenId); + return tokenIdAsNumber.isInteger() ? [] : [`Token IDs must be integer numbers: ${tokenIdAsNumber.toFixed()}`]; +}; +const isTokenValueInteger = (row: CollectibleTransfer): string[] => { + if (row.amount) { + const amountAsNumber = new BigNumber(row.amount); + if (amountAsNumber.isNaN() || !amountAsNumber.isInteger()) { + return [`Value / amount of ERC1155 must be an integer: ${row.amount}`]; + } + } + return []; +}; const isTokenValueValid = (row: CollectibleTransfer): string[] => - row.token_type === "erc721" || (typeof row.amount !== "undefined" && row.amount.isGreaterThan(0)) + row.token_type === "erc721" || (typeof row.amount !== "undefined" && new BigNumber(row.amount).isGreaterThan(0)) ? [] - : [`ERC1155 Tokens need a defined value > 0: ${row.amount?.toFixed()}`]; + : [`ERC1155 Tokens need a defined value > 0: ${row.amount}`]; diff --git a/src/stores/api/balanceApi.ts b/src/stores/api/balanceApi.ts new file mode 100644 index 00000000..8f67801f --- /dev/null +++ b/src/stores/api/balanceApi.ts @@ -0,0 +1,117 @@ +import { BaseQueryFn, createApi, FetchArgs, fetchBaseQuery, FetchBaseQueryError } from "@reduxjs/toolkit/query/react"; +import { networkInfo } from "src/networks"; + +import { RootState } from "../store"; + +/** + * Currently the tx service is rate limited to 5 requests / minute and fetching NFTs is very slow. + */ +const MAX_NFTS = 50; + +type AssetBalanceEntry = { + tokenAddress: string | null; + token: Token | null; + balance: string; + decimals: number; +}; + +type NFTBalanceEntry = { + address: string; + tokenName: string; + tokenSymbol: string; + id: string; + imageUri: string; + name: string; +}; + +type Token = { + name: string; + symbol: string; + decimals: number; +}; + +export type AssetBalance = AssetBalanceEntry[]; +export type NFTBalance = { next: string | null; results: NFTBalanceEntry[] }; + +const dynamicBaseQuery: BaseQueryFn = async ( + args, + api, + extraoptions, +) => { + const isCollectible = args.toString().startsWith("collectibles"); + const safeInfo = (api.getState() as RootState).safeInfo.safeInfo; + console.log("dynamic", args, safeInfo); + if (!safeInfo) { + return { + error: { + status: 400, + statusText: "Bad Request", + data: "No Safe Info received", + }, + }; + } + const { chainId, safeAddress } = safeInfo; + const baseAPI = networkInfo.get(chainId)?.baseAPI; + if (!baseAPI) { + return { + error: { + status: 400, + statusText: "Bad Request", + data: "No Base API for Chain ID found", + }, + }; + } + return fetchBaseQuery({ + baseUrl: isCollectible + ? `${networkInfo.get(chainId)?.baseAPI?.replace("v1", "v2")}/safes/${safeAddress}` + : `${networkInfo.get(chainId)?.baseAPI}/safes/${safeAddress}`, + })(args, api, extraoptions); +}; + +export const balanceApi = createApi({ + reducerPath: "balancerApi", + baseQuery: dynamicBaseQuery, + endpoints: (builder) => ({ + getAssetBalance: builder.query({ + query: () => "balances?trusted=false&exclude_spam=true", + }), + getNFTPage: builder.query({ + query: ({ offset }) => `collectibles/?trusted=false&exclude_spam=true&offset=${offset}&limit=10`, + }), + getAllNFTs: builder.query({ + queryFn: async (_args, queryApi, _extraOptions, fetchWithBQ) => { + let allNFTs: NFTBalance = { results: [], next: "initialPage" }; + let offset = 0; + while (allNFTs.next !== null) { + console.log("Fetching next page of NFTs"); + const safeInfo = (queryApi.getState() as RootState).safeInfo.safeInfo; + console.log("safeInfo state in queryFn", safeInfo); + + const { data, error } = await fetchWithBQ( + `collectibles/?trusted=false&exclude_spam=true&offset=${offset}&limit=10`, + ); + if (error) { + return Promise.resolve({ error }); + } + const nextBalance = data as NFTBalance; + console.log("Result:", nextBalance); + allNFTs.next = nextBalance?.next ?? null; + if (offset >= MAX_NFTS) { + break; + } + offset += 10; + + if (nextBalance) { + const { results } = nextBalance; + if (results) { + allNFTs.results.push(...results); + } + } + } + return Promise.resolve({ data: allNFTs }); + }, + }), + }), +}); + +export const { useGetAssetBalanceQuery, useGetAllNFTsQuery } = balanceApi; diff --git a/src/stores/middleware/parseListener.ts b/src/stores/middleware/parseListener.ts new file mode 100644 index 00000000..1b5ddd95 --- /dev/null +++ b/src/stores/middleware/parseListener.ts @@ -0,0 +1,94 @@ +import { EnsResolver } from "src/hooks/ens"; +import { Transfer } from "src/hooks/useCsvParser"; +import { checkAllBalances } from "src/parser/balanceCheck"; + +import { balanceApi } from "../api/balanceApi"; +import { setTransfers, startParsing, stopParsing, updateCsvContent } from "../slices/csvEditorSlice"; +import { CodeWarning, setCodeWarnings, setMessages } from "../slices/messageSlice"; +import { AppStartListening } from "../store"; + +export const setupParserListener = ( + startListening: AppStartListening, + parseCsv: (csvText: string) => Promise<[Transfer[], CodeWarning[]]>, + ensResolver: EnsResolver, +) => { + const subscription = startListening({ + actionCreator: updateCsvContent, + effect: async (action, listenerApi) => { + const { csvContent } = action.payload; + listenerApi.cancelActiveListeners(); + await listenerApi.delay(750); + listenerApi.dispatch(startParsing()); + try { + let [transfers, codeWarnings] = await parseCsv(csvContent); + const uniqueReceiversWithoutEnsName = transfers.reduce( + (previousValue, currentValue): Set => + currentValue.receiverEnsName === null ? previousValue.add(currentValue.receiver) : previousValue, + new Set(), + ); + if (uniqueReceiversWithoutEnsName.size < 15) { + transfers = await Promise.all( + // If there is no ENS Name we will try to lookup the address + transfers.map(async (transfer) => + transfer.receiverEnsName + ? transfer + : { + ...transfer, + receiverEnsName: (await ensResolver.isEnsEnabled()) + ? await ensResolver.lookupAddress(transfer.receiver) + : null, + }, + ), + ); + } + transfers = transfers.map((transfer, idx) => ({ ...transfer, position: idx + 1 })); + listenerApi.dispatch(setTransfers(transfers)); + listenerApi.dispatch(setCodeWarnings(codeWarnings)); + + const currentState = listenerApi.getState(); + const assetBalanceResult = balanceApi.endpoints.getAssetBalance.select()(currentState); + const nftBalanceResult = balanceApi.endpoints.getAllNFTs.select()(currentState); + const insufficientBalances = checkAllBalances(assetBalanceResult.data, nftBalanceResult.data, transfers); + + listenerApi.dispatch(stopParsing()); + + listenerApi.dispatch( + setMessages( + insufficientBalances.map((insufficientBalanceInfo) => { + if (insufficientBalanceInfo.token_type === "erc20" || insufficientBalanceInfo.token_type === "native") { + return { + message: `Insufficient Balance: ${insufficientBalanceInfo.transferAmount} of ${insufficientBalanceInfo.token}`, + severity: "error", + }; + } else { + if (insufficientBalanceInfo.isDuplicate) { + return { + message: `Duplicate transfer for ERC721 token ${insufficientBalanceInfo.token} with ID ${insufficientBalanceInfo.id}`, + severity: "warning", + }; + } else { + return { + message: `Collectible ERC721 token ${insufficientBalanceInfo.token} with ID ${insufficientBalanceInfo.id} is not held by this safe`, + severity: "error", + }; + } + } + }), + ), + ); + } catch (err) { + listenerApi.dispatch( + setMessages([ + { + message: JSON.stringify(err), + severity: "error", + }, + ]), + ); + listenerApi.dispatch(stopParsing()); + } + }, + }); + + return () => subscription(); +}; diff --git a/src/stores/slices/csvEditorSlice.ts b/src/stores/slices/csvEditorSlice.ts new file mode 100644 index 00000000..9056b03d --- /dev/null +++ b/src/stores/slices/csvEditorSlice.ts @@ -0,0 +1,45 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { Transfer } from "src/hooks/useCsvParser"; + +import { RootState } from "../store"; +export interface CSVEditorState { + csvContent: string; + transfers: Transfer[]; + parsing: boolean; +} + +const initialState: CSVEditorState = { + csvContent: "token_type,token_address,receiver,amount,id", + transfers: [], + parsing: false, +}; + +export const csvEditorSlice = createSlice({ + name: "csvEditor", + initialState, + reducers: { + updateCsvContent: ( + state, + action: PayloadAction<{ + csvContent: string; + }>, + ) => { + state.csvContent = action.payload.csvContent; + }, + setTransfers: (state, action: PayloadAction) => { + state.transfers = action.payload; + }, + startParsing: (state) => { + state.parsing = true; + }, + stopParsing: (state) => { + state.parsing = false; + }, + }, +}); + +export const { updateCsvContent, setTransfers, startParsing, stopParsing } = csvEditorSlice.actions; + +export default csvEditorSlice.reducer; + +export const selectCsvContent = ({ csvEditor }: RootState) => csvEditor.csvContent; diff --git a/src/stores/slices/messageSlice.ts b/src/stores/slices/messageSlice.ts new file mode 100644 index 00000000..782af9f5 --- /dev/null +++ b/src/stores/slices/messageSlice.ts @@ -0,0 +1,50 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; + +export type CodeWarning = { + message: string; + severity: string; + lineNo: number; +}; + +export type Message = { + message: string; + severity: string; +}; + +interface MessageState { + messages: Message[]; + codeWarnings: CodeWarning[]; + showMessages: boolean; +} + +const initialState: MessageState = { + messages: [], + codeWarnings: [], + showMessages: false, +}; + +export const messageSlice = createSlice({ + name: "messages", + initialState, + reducers: { + setMessages: (state, action: PayloadAction) => { + state.messages = action.payload; + }, + setCodeWarnings: (state, action: PayloadAction) => { + state.codeWarnings = action.payload; + }, + removeMessage: (state, action: PayloadAction) => { + state.messages = state.messages.filter((message) => message.message !== action.payload.message); + }, + hideMessages: (state) => { + state.showMessages = false; + }, + toggleMessages: (state) => { + state.showMessages = !state.showMessages; + }, + }, +}); + +export const { setMessages, setCodeWarnings, removeMessage, hideMessages, toggleMessages } = messageSlice.actions; + +export default messageSlice.reducer; diff --git a/src/stores/slices/safeInfoSlice.ts b/src/stores/slices/safeInfoSlice.ts new file mode 100644 index 00000000..f7b2786c --- /dev/null +++ b/src/stores/slices/safeInfoSlice.ts @@ -0,0 +1,24 @@ +import { SafeInfo } from "@gnosis.pm/safe-apps-sdk"; +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; + +export interface SafeInfoState { + safeInfo: SafeInfo | undefined; +} + +const initialState: SafeInfoState = { + safeInfo: undefined, +}; + +export const safeInfoSlice = createSlice({ + name: "safeInfo", + initialState, + reducers: { + setSafeInfo: (state, action: PayloadAction) => { + state.safeInfo = action.payload; + }, + }, +}); + +export const { setSafeInfo } = safeInfoSlice.actions; + +export default safeInfoSlice.reducer; diff --git a/src/stores/store.ts b/src/stores/store.ts new file mode 100644 index 00000000..4f92b7e0 --- /dev/null +++ b/src/stores/store.ts @@ -0,0 +1,46 @@ +import { + configureStore, + createListenerMiddleware, + ListenerEffectAPI, + TypedAddListener, + TypedStartListening, +} from "@reduxjs/toolkit"; +import { setupListeners } from "@reduxjs/toolkit/dist/query"; +import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; + +import { balanceApi } from "./api/balanceApi"; +import csvReducer from "./slices/csvEditorSlice"; +import messageReducer from "./slices/messageSlice"; +import safeInfoReducer from "./slices/safeInfoSlice"; + +const listenerMiddlewareInstance = createListenerMiddleware({ + onError: () => console.error, +}); + +export const store = configureStore({ + reducer: { + csvEditor: csvReducer, + messages: messageReducer, + safeInfo: safeInfoReducer, + [balanceApi.reducerPath]: balanceApi.reducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().prepend(listenerMiddlewareInstance.middleware).concat(balanceApi.middleware), +}); + +setupListeners(store.dispatch); + +export type RootState = ReturnType; + +export type AppDispatch = typeof store.dispatch; + +export type AppListenerEffectAPI = ListenerEffectAPI; + +export type AppStartListening = TypedStartListening; + +export type AppAddListener = TypedAddListener; + +export const startAppListening = listenerMiddlewareInstance.startListening as AppStartListening; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/src/test/util.ts b/src/test/util.ts index 16751c2a..05ff1b6e 100644 --- a/src/test/util.ts +++ b/src/test/util.ts @@ -22,13 +22,11 @@ const unlistedERC20Token: TokenInfo = { const dummyERC721Token: CollectibleTokenInfo = { token_type: "erc721", address: "0x5500000000000000000000000000000000000000", - hasMetaInfo: false, }; const dummyERC1155Token: CollectibleTokenInfo = { token_type: "erc1155", address: "0x88b48f654c30e99bc2e4a1559b4dcf1ad93fa656", - hasMetaInfo: false, }; const addresses = { diff --git a/src/transfers/transfers.ts b/src/transfers/transfers.ts index 1451a966..a2ad2503 100644 --- a/src/transfers/transfers.ts +++ b/src/transfers/transfers.ts @@ -1,7 +1,7 @@ import { BaseTransaction } from "@gnosis.pm/safe-apps-sdk"; import { ethers } from "ethers"; -import { AssetTransfer, CollectibleTransfer } from "../parser/csvParser"; +import { AssetTransfer, CollectibleTransfer } from "../hooks/useCsvParser"; import { toWei } from "../utils"; import { erc1155Interface } from "./erc1155"; @@ -40,7 +40,7 @@ export function buildCollectibleTransfers(transferData: CollectibleTransfer[]): data: erc721Interface.encodeFunctionData("safeTransferFrom", [ transfer.from, transfer.receiver, - transfer.tokenId.toFixed(), + transfer.tokenId, ]), }; } else { @@ -50,8 +50,8 @@ export function buildCollectibleTransfers(transferData: CollectibleTransfer[]): data: erc1155Interface.encodeFunctionData("safeTransferFrom", [ transfer.from, transfer.receiver, - transfer.tokenId.toFixed(), - transfer.amount?.toFixed() ?? "0", + transfer.tokenId, + transfer.amount ?? "0", ethers.utils.hexlify("0x00"), ]), }; diff --git a/src/utils.ts b/src/utils.ts index 816f90c2..3954cc1c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -50,5 +50,7 @@ export function fromWei(amount: BigNumber, decimals: number): BigNumber { * @returns URI resolved to the infura ipfs host or uri if it's not an ipfs uri. */ export function resolveIpfsUri(uri: string): string { - return uri.startsWith("ipfs://") ? uri.replace("ipfs://", "https://ipfs.infura.io/ipfs/") : uri; + return uri.startsWith("ipfs://") + ? uri.replace("ipfs://ipfs/", "ipfs://").replace("ipfs://", "https://cloudflare-ipfs.com/ipfs/") + : uri; } diff --git a/yarn.lock b/yarn.lock index 850f4ac0..6e560cb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd" integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g== -"@ampproject/remapping@^2.1.0": +"@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== @@ -41,6 +41,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.1.tgz#72d647b4ff6a4f82878d184613353af1dd0290f9" integrity sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg== +"@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.0.tgz#c241dc454e5b5917e40d37e525e2f4530c399298" + integrity sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g== + "@babel/core@7.12.3": version "7.12.3" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.3.tgz#1b436884e1e3bff6fb1328dc02b208759de92ad8" @@ -63,7 +68,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.7.5", "@babel/core@^7.8.4": +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.16.0": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.1.tgz#c8fa615c5e88e272564ace3d42fbc8b17bfeb22b" integrity sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw== @@ -84,6 +89,27 @@ json5 "^2.2.1" semver "^6.3.0" +"@babel/core@^7.7.5", "@babel/core@^7.8.4": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.0.tgz#1341aefdcc14ccc7553fcc688dd8986a2daffc13" + integrity sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.21.0" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-module-transforms" "^7.21.0" + "@babel/helpers" "^7.21.0" + "@babel/parser" "^7.21.0" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.0" + "@babel/eslint-parser@^7.16.3": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz#4f68f6b0825489e00a24b41b6a1ae35414ecd2f4" @@ -93,7 +119,17 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.0" -"@babel/generator@^7.12.1", "@babel/generator@^7.19.0": +"@babel/generator@^7.12.1", "@babel/generator@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.0.tgz#45d731e84f506ce02a7b22b9ba5861ea88eef64f" + integrity sha512-z/zN3SePOtxN1/vPFdqrkuJGCD2Vx469+dSbNRD+4TF2+6e4Of5exHqAtcfL/2Nwu0RN0QsFwjyDBFwdUMzNSA== + dependencies: + "@babel/types" "^7.21.0" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/generator@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a" integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== @@ -127,6 +163,17 @@ browserslist "^4.21.3" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.20.0", "@babel/helper-compilation-targets@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" + integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== + dependencies: + "@babel/compat-data" "^7.20.5" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + lru-cache "^5.1.1" + semver "^6.3.0" + "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" @@ -180,6 +227,14 @@ "@babel/template" "^7.18.10" "@babel/types" "^7.19.0" +"@babel/helper-function-name@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" + integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== + dependencies: + "@babel/template" "^7.20.7" + "@babel/types" "^7.21.0" + "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" @@ -194,6 +249,13 @@ dependencies: "@babel/types" "^7.18.9" +"@babel/helper-member-expression-to-functions@^7.20.7": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5" + integrity sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q== + dependencies: + "@babel/types" "^7.21.0" + "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" @@ -201,7 +263,21 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0": +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.0.tgz#89a8f86ad748870e3d024e470b2e8405e869db67" + integrity sha512-eD/JQ21IG2i1FraJnTMbUarAUkA7G988ofehG5MDCRXaUU91rEBJuCeSoou2Sk1y4RbLYXzqEg1QLwEmRU4qcQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" + +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== @@ -227,6 +303,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== +"@babel/helper-plugin-utils@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== + "@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" @@ -248,6 +329,18 @@ "@babel/traverse" "^7.19.1" "@babel/types" "^7.19.0" +"@babel/helper-replace-supers@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" + integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.20.7" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.7" + "@babel/types" "^7.20.7" + "@babel/helper-simple-access@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" @@ -255,6 +348,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-simple-access@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" + integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== + dependencies: + "@babel/types" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818" @@ -274,7 +374,12 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== -"@babel/helper-validator-identifier@^7.18.6": +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== @@ -294,7 +399,16 @@ "@babel/traverse" "^7.19.0" "@babel/types" "^7.19.0" -"@babel/helpers@^7.12.1", "@babel/helpers@^7.19.0": +"@babel/helpers@^7.12.1", "@babel/helpers@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" + integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== + dependencies: + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.0" + "@babel/types" "^7.21.0" + +"@babel/helpers@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18" integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== @@ -312,11 +426,16 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.3", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.1", "@babel/parser@^7.7.0": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.1", "@babel/parser@^7.7.0": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.1.tgz#6f6d6c2e621aad19a92544cc217ed13f1aac5b4c" integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A== +"@babel/parser@^7.12.3", "@babel/parser@^7.20.7", "@babel/parser@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.0.tgz#cc09288743b867763cb927ba101ccdf0b600b7e4" + integrity sha512-ONjtg4renj14A9pj3iA5T5+r5Eijxbr2eNIkMBTC74occDSsRZUpe8vowmowAjFR1imWlkD8eEmjYXiREZpGZg== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -343,6 +462,16 @@ "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-syntax-async-generators" "^7.8.4" +"@babel/plugin-proposal-async-generator-functions@^7.20.1": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326" + integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-proposal-class-properties@^7.16.0", "@babel/plugin-proposal-class-properties@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" @@ -430,6 +559,17 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.18.8" +"@babel/plugin-proposal-object-rest-spread@^7.20.2": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" + integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== + dependencies: + "@babel/compat-data" "^7.20.5" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.20.7" + "@babel/plugin-proposal-optional-catch-binding@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" @@ -536,6 +676,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-syntax-import-assertions@^7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" + integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" @@ -650,6 +797,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-block-scoping@^7.20.2": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02" + integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-transform-classes@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20" @@ -665,6 +819,21 @@ "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" +"@babel/plugin-transform-classes@^7.20.2": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665" + integrity sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-replace-supers" "^7.20.7" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" + "@babel/plugin-transform-computed-properties@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" @@ -679,6 +848,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-destructuring@^7.20.2": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz#8bda578f71620c7de7c93af590154ba331415454" + integrity sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" @@ -749,6 +925,14 @@ "@babel/helper-plugin-utils" "^7.18.6" babel-plugin-dynamic-import-node "^2.3.3" +"@babel/plugin-transform-modules-amd@^7.19.6": + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz#3daccca8e4cc309f03c3a0c4b41dc4b26f55214a" + integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g== + dependencies: + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-transform-modules-commonjs@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz#afd243afba166cca69892e24a8fd8c9f2ca87883" @@ -759,6 +943,15 @@ "@babel/helper-simple-access" "^7.18.6" babel-plugin-dynamic-import-node "^2.3.3" +"@babel/plugin-transform-modules-commonjs@^7.19.6": + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz#8cb23010869bf7669fd4b3098598b6b2be6dc607" + integrity sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw== + dependencies: + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-simple-access" "^7.20.2" + "@babel/plugin-transform-modules-systemjs@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz#5f20b471284430f02d9c5059d9b9a16d4b085a1f" @@ -770,6 +963,16 @@ "@babel/helper-validator-identifier" "^7.18.6" babel-plugin-dynamic-import-node "^2.3.3" +"@babel/plugin-transform-modules-systemjs@^7.19.6": + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz#467ec6bba6b6a50634eea61c9c232654d8a4696e" + integrity sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw== + dependencies: + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/plugin-transform-modules-umd@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" @@ -808,6 +1011,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-parameters@^7.20.1", "@babel/plugin-transform-parameters@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz#0ee349e9d1bc96e78e3b37a7af423a4078a7083f" + integrity sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-transform-property-literals@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" @@ -942,7 +1152,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/preset-env@^7.12.1", "@babel/preset-env@^7.16.4", "@babel/preset-env@^7.8.4": +"@babel/preset-env@^7.12.1", "@babel/preset-env@^7.16.4": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.1.tgz#9f04c916f9c0205a48ebe5cc1be7768eb1983f67" integrity sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA== @@ -1023,6 +1233,87 @@ core-js-compat "^3.25.1" semver "^6.3.0" +"@babel/preset-env@^7.8.4": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.20.2.tgz#9b1642aa47bb9f43a86f9630011780dab7f86506" + integrity sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg== + dependencies: + "@babel/compat-data" "^7.20.1" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions" "^7.20.1" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.9" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.20.2" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.20.0" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.20.2" + "@babel/plugin-transform-classes" "^7.20.2" + "@babel/plugin-transform-computed-properties" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.20.2" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.19.6" + "@babel/plugin-transform-modules-commonjs" "^7.19.6" + "@babel/plugin-transform-modules-systemjs" "^7.19.6" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.20.1" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.19.0" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.9" + "@babel/plugin-transform-typeof-symbol" "^7.18.9" + "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.20.2" + babel-plugin-polyfill-corejs2 "^0.3.3" + babel-plugin-polyfill-corejs3 "^0.6.0" + babel-plugin-polyfill-regenerator "^0.4.1" + core-js-compat "^3.25.1" + semver "^6.3.0" + "@babel/preset-modules@^0.1.5": version "0.1.5" resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" @@ -1070,7 +1361,30 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.10.4", "@babel/template@^7.18.10", "@babel/template@^7.3.3": +"@babel/runtime@^7.12.1": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" + integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ== + dependencies: + regenerator-runtime "^0.13.11" + +"@babel/runtime@^7.20.7": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" + integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== + dependencies: + regenerator-runtime "^0.13.11" + +"@babel/template@^7.10.4", "@babel/template@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + +"@babel/template@^7.18.10", "@babel/template@^7.3.3": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== @@ -1079,7 +1393,23 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.1", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0": +"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.1", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.0.tgz#0e1807abd5db98e6a19c204b80ed1e3f5bca0edc" + integrity sha512-Xdt2P1H4LKTO8ApPfnO1KmzYMFpp7D/EinoXzLYN/cHcBNrVCAkAtGUcXnHXrl/VGktureU6fkQrHSBE2URfoA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.21.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.21.0" + "@babel/types" "^7.21.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.1.tgz#0fafe100a8c2a603b4718b1d9bf2568d1d193347" integrity sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA== @@ -1095,7 +1425,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.12.1", "@babel/types@^7.12.6", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": +"@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== @@ -1104,6 +1434,15 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" +"@babel/types@^7.12.1", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.0.tgz#1da00d89c2f18b226c9207d96edbeb79316a1819" + integrity sha512-uR7NWq2VNFnDi7EYqiRz2Jv/VQIu38tu64Zy8TX2nQFQ6etJ9V/Rr2msW8BS132mum2rL645qpDrLtAJtVpuow== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1843,7 +2182,7 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@^3.0.3": +"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== @@ -1861,11 +2200,19 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/trace-mapping@^0.3.17": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/trace-mapping@^0.3.9": version "0.3.15" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" @@ -2010,6 +2357,16 @@ schema-utils "^2.6.5" source-map "^0.7.3" +"@reduxjs/toolkit@^1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.8.1.tgz#94ee1981b8cf9227cda40163a04704a9544c9a9f" + integrity sha512-Q6mzbTpO9nOYRnkwpDlFOAbQnd3g7zj7CtHAZWz5SzE5lcV97Tf8f3SzOO8BoPOMYBFgfZaqTUZqgGu+a0+Fng== + dependencies: + immer "^9.0.7" + redux "^4.1.2" + redux-thunk "^2.4.1" + reselect "^4.1.5" + "@rollup/plugin-node-resolve@^7.1.1": version "7.1.3" resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca" @@ -2216,6 +2573,14 @@ lodash "^4.17.15" redent "^3.0.0" +"@testing-library/react-hooks@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" + integrity sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-boundary "^3.1.0" + "@testing-library/react@^12.1.3": version "12.1.5" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" @@ -2243,7 +2608,7 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": +"@types/babel__core@^7.0.0": version "7.1.19" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== @@ -2254,6 +2619,17 @@ "@types/babel__template" "*" "@types/babel__traverse" "*" +"@types/babel__core@^7.1.7": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891" + integrity sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + "@types/babel__generator@*": version "7.6.4" resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" @@ -2328,7 +2704,7 @@ dependencies: "@types/node" "*" -"@types/hoist-non-react-statics@*": +"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.1": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== @@ -2427,7 +2803,12 @@ dependencies: "@types/node" "*" -"@types/prettier@^2.0.0", "@types/prettier@^2.1.1": +"@types/prettier@^2.0.0": + version "2.7.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" + integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== + +"@types/prettier@^2.1.1": version "2.7.0" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.0.tgz#ea03e9f0376a4446f44797ca19d9c46c36e352dc" integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== @@ -2546,12 +2927,17 @@ "@types/jest" "*" "@types/uglify-js@*": - version "3.17.0" - resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.17.0.tgz#95271e7abe0bf7094c60284f76ee43232aef43b9" - integrity sha512-3HO6rm0y+/cqvOyA8xcYLweF0TKXlAxmQASjbOi49Co51A1N4nR4bEwBgRoD9kNM+rqFGArjKr654SLp2CoGmQ== + version "3.17.1" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.17.1.tgz#e0ffcef756476410e5bce2cb01384ed878a195b5" + integrity sha512-GkewRA4i5oXacU/n4MA9+bLgt5/L3F1mKrYvFGm7r2ouLXhRKjuWwo9XHNnbx6WF3vlGW21S3fCvgqxvxXXc5g== dependencies: source-map "^0.6.1" +"@types/use-sync-external-store@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" + integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== + "@types/webpack-sources@*": version "3.2.0" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b" @@ -2562,9 +2948,9 @@ source-map "^0.7.3" "@types/webpack@^4.41.8": - version "4.41.32" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.32.tgz#a7bab03b72904070162b2f169415492209e94212" - integrity sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg== + version "4.41.33" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.33.tgz#16164845a5be6a306bcbe554a8e67f9cac215ffc" + integrity sha512-PPajH64Ft2vWevkerISMtnZ8rTs4YmRbs+23c402J0INmxDKCrhZNvwZYtzx96gY2wAtXdrK1BS2fiC8MlLr3g== dependencies: "@types/node" "*" "@types/tapable" "^1" @@ -2579,9 +2965,9 @@ integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@^15.0.0": - version "15.0.14" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" - integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== + version "15.0.15" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.15.tgz#e609a2b1ef9e05d90489c2f5f45bbfb2be092158" + integrity sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg== dependencies: "@types/yargs-parser" "*" @@ -3051,9 +3437,9 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5: uri-js "^4.2.2" ajv@^8.0.1: - version "8.11.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" - integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -3129,7 +3515,15 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.2: +anymatch@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -3162,6 +3556,13 @@ aria-query@^5.0.0: resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.2.tgz#0b8a744295271861e1d933f8feca13f9b70cfdc1" integrity sha512-eigU3vhqSO+Z8BKDnVLN/ompjhf3pYzecKXz8+whRy+9gZu8n1TCGfwzQUUPnqdHl9ax1Hr9031orZ+UOEYr7Q== +aria-query@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" + integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== + dependencies: + deep-equal "^2.0.5" + arity-n@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/arity-n/-/arity-n-1.0.4.tgz#d9e76b11733e08569c0847ae7b39b2860b30b745" @@ -3254,6 +3655,16 @@ array.prototype.flat@^1.2.5: es-abstract "^1.19.2" es-shim-unscopables "^1.0.0" +array.prototype.flat@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" + integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" + array.prototype.flatmap@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" @@ -3275,6 +3686,17 @@ array.prototype.reduce@^1.0.4: es-array-method-boxes-properly "^1.0.0" is-string "^1.0.7" +array.prototype.reduce@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz#6b20b0daa9d9734dd6bc7ea66b5bbce395471eac" + integrity sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.7" + array.prototype.tosorted@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz#ccf44738aa2b5ac56578ffda97c03fd3e23dd532" @@ -3335,9 +3757,9 @@ astral-regex@^2.0.0: integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + version "1.0.6" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.6.tgz#52f1d9403818c179b7561e11a5d1b77eb2160e77" + integrity sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg== async-limiter@~1.0.0: version "1.0.1" @@ -3394,11 +3816,23 @@ axe-core@^4.4.3: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f" integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w== +axe-core@^4.6.2: + version "4.6.3" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.6.3.tgz#fc0db6fdb65cc7a80ccf85286d91d64ababa3ece" + integrity sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg== + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== +axobject-query@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1" + integrity sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg== + dependencies: + deep-equal "^2.0.5" + babel-eslint@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" @@ -3706,10 +4140,10 @@ bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -body-parser@1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" - integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== dependencies: bytes "3.1.2" content-type "~1.0.4" @@ -3719,7 +4153,7 @@ body-parser@1.20.0: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.10.3" + qs "6.11.0" raw-body "2.5.1" type-is "~1.6.18" unpipe "1.0.0" @@ -3853,7 +4287,7 @@ browserslist@4.14.2: escalade "^3.0.2" node-releases "^1.1.61" -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4.6.2, browserslist@^4.6.4: +browserslist@^4.0.0, browserslist@^4.21.3, browserslist@^4.21.4: version "4.21.4" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== @@ -3863,6 +4297,16 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.21.3, browserslist@^4 node-releases "^2.0.6" update-browserslist-db "^1.0.9" +browserslist@^4.12.0, browserslist@^4.6.2, browserslist@^4.6.4: + version "4.21.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" + integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== + dependencies: + caniuse-lite "^1.0.30001449" + electron-to-chromium "^1.4.284" + node-releases "^2.0.8" + update-browserslist-db "^1.0.10" + bs58@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -4055,11 +4499,16 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001400: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001400: version "1.0.30001410" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001410.tgz#b5a86366fbbf439d75dd3db1d21137a73e829f44" integrity sha512-QoblBnuE+rG0lc3Ur9ltP5q47lbguipa/ncNMyyGuqPk44FxbScWAeEO+k5fSQ8WekdAK4mWqNs1rADDAiN5xQ== +caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001449: + version "1.0.30001457" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz#6af34bb5d720074e2099432aa522c21555a18301" + integrity sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA== + capture-exit@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" @@ -4495,9 +4944,9 @@ core-js@^2.4.0: integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== core-js@^3.6.5: - version "3.25.2" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.2.tgz#2d3670c1455432b53fa780300a6fc1bd8304932c" - integrity sha512-YB4IAT1bjEfxTJ1XYy11hJAKskO+qmhuDBM8/guIfMz4JvdsAQAqvyb97zXX7JgSrfPLG5mRGFWJwJD39ruq2A== + version "3.28.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.28.0.tgz#ed8b9e99c273879fdfff0edfc77ee709a5800e4a" + integrity sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw== core-util-is@~1.0.0: version "1.0.3" @@ -4951,6 +5400,29 @@ deep-equal@^1.0.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" +deep-equal@^2.0.5: + version "2.2.0" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.0.tgz#5caeace9c781028b9ff459f33b779346637c43e6" + integrity sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw== + dependencies: + call-bind "^1.0.2" + es-get-iterator "^1.1.2" + get-intrinsic "^1.1.3" + is-arguments "^1.1.1" + is-array-buffer "^3.0.1" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.4.3" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -4969,7 +5441,15 @@ default-gateway@^4.2.0: execa "^1.0.0" ip-regex "^2.1.0" -define-properties@^1.1.2, define-properties@^1.1.3, define-properties@^1.1.4: +define-properties@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" + integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +define-properties@^1.1.3, define-properties@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== @@ -5256,7 +5736,12 @@ ejs@^2.6.1: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== -electron-to-chromium@^1.3.564, electron-to-chromium@^1.4.251: +electron-to-chromium@^1.3.564, electron-to-chromium@^1.4.284: + version "1.4.303" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.303.tgz#6c598653c52b2554f768d98374803236b8f022a8" + integrity sha512-XaqiQhVsGO5ymf/Lg6XEGpv2h8b5AFqQDQ9fQckolNP2VtD2VL1pn1TIx1SSYsf0srfXVi2Sm7n/K3slJSX3ig== + +electron-to-chromium@^1.4.251: version "1.4.258" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.258.tgz#44c5456f487be082f038282fbcfd7b06ae99720d" integrity sha512-vutF4q0dTUXoAFI7Vbtdwen/BJVwPgj8GRg/SElOodfH7VTX+svUe62A5BG41QRQGk5HsZPB0M++KH1lAlOt0A== @@ -5294,11 +5779,6 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - integrity sha512-knHEZMgs8BB+MInokmNTg/OyPlAddghe1YBgNwJBc5zsJi/uyIcXoSDsL/W9ymOsBoBGdPIHXYJ9+qKFwRwDng== - emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" @@ -5432,6 +5912,21 @@ es-array-method-boxes-properly@^1.0.0: resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== +es-get-iterator@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" + es-set-tostringtag@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" @@ -5560,7 +6055,16 @@ eslint-import-resolver-node@^0.3.6: debug "^3.2.7" resolve "^1.20.0" -eslint-module-utils@^2.7.3: +eslint-import-resolver-node@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" + integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== + dependencies: + debug "^3.2.7" + is-core-module "^2.11.0" + resolve "^1.22.1" + +eslint-module-utils@^2.7.3, eslint-module-utils@^2.7.4: version "2.7.4" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== @@ -5583,7 +6087,28 @@ eslint-plugin-flowtype@^8.0.3: lodash "^4.17.21" string-natural-compare "^3.0.1" -eslint-plugin-import@^2.22.1, eslint-plugin-import@^2.25.3, eslint-plugin-import@^2.25.4: +eslint-plugin-import@^2.22.1: + version "2.27.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65" + integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + array.prototype.flatmap "^1.3.1" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.7" + eslint-module-utils "^2.7.4" + has "^1.0.3" + is-core-module "^2.11.0" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.values "^1.1.6" + resolve "^1.22.1" + semver "^6.3.0" + tsconfig-paths "^3.14.1" + +eslint-plugin-import@^2.25.3, eslint-plugin-import@^2.25.4: version "2.26.0" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== @@ -5616,7 +6141,29 @@ eslint-plugin-jest@^25.3.0: dependencies: "@typescript-eslint/experimental-utils" "^5.0.0" -eslint-plugin-jsx-a11y@^6.3.1, eslint-plugin-jsx-a11y@^6.5.1: +eslint-plugin-jsx-a11y@^6.3.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz#fca5e02d115f48c9a597a6894d5bcec2f7a76976" + integrity sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA== + dependencies: + "@babel/runtime" "^7.20.7" + aria-query "^5.1.3" + array-includes "^3.1.6" + array.prototype.flatmap "^1.3.1" + ast-types-flow "^0.0.7" + axe-core "^4.6.2" + axobject-query "^3.1.1" + damerau-levenshtein "^1.0.8" + emoji-regex "^9.2.2" + has "^1.0.3" + jsx-ast-utils "^3.3.3" + language-tags "=1.0.5" + minimatch "^3.1.2" + object.entries "^1.1.6" + object.fromentries "^2.0.6" + semver "^6.3.0" + +eslint-plugin-jsx-a11y@^6.5.1: version "6.6.1" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz#93736fc91b83fdc38cc8d115deedfc3091aef1ff" integrity sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q== @@ -6024,13 +6571,13 @@ expect@^29.0.0: jest-util "^29.0.3" express@^4.17.1: - version "4.18.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" - integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.0" + body-parser "1.20.1" content-disposition "0.5.4" content-type "~1.0.4" cookie "0.5.0" @@ -6049,7 +6596,7 @@ express@^4.17.1: parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.10.3" + qs "6.11.0" range-parser "~1.2.1" safe-buffer "5.2.1" send "0.18.0" @@ -6544,9 +7091,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.6.0, globals@^13.9.0: - version "13.17.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" - integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== + version "13.20.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" + integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== dependencies: type-fest "^0.20.2" @@ -6960,16 +7507,26 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.4, ignore@^5.1.8, ignore@^5.2.0: +ignore@^5.1.4, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +ignore@^5.1.8: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + immer@8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656" integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA== +immer@^9.0.7: + version "9.0.12" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20" + integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA== + import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -7136,7 +7693,7 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-arguments@^1.0.4: +is-arguments@^1.0.4, is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== @@ -7226,7 +7783,14 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" -is-core-module@^2.0.0, is-core-module@^2.8.1, is-core-module@^2.9.0: +is-core-module@^2.0.0, is-core-module@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + dependencies: + has "^1.0.3" + +is-core-module@^2.8.1, is-core-module@^2.9.0: version "2.10.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== @@ -7247,7 +7811,7 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-date-object@^1.0.1: +is-date-object@^1.0.1, is-date-object@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== @@ -7338,6 +7902,11 @@ is-in-browser@^1.0.2, is-in-browser@^1.1.3: resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" integrity sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g== +is-map@^2.0.1, is-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== + is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" @@ -7436,6 +8005,11 @@ is-root@2.1.0: resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== +is-set@^2.0.1, is-set@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== + is-shared-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" @@ -7483,6 +8057,11 @@ is-typedarray@^1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== +is-weakmap@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== + is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -7490,6 +8069,14 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" +is-weakset@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" + integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -7512,6 +8099,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -8182,6 +8774,11 @@ json5@^2.1.2, json5@^2.2.1: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -8268,7 +8865,7 @@ jss@10.9.2, jss@^10.5.1: is-in-browser "^1.1.3" tiny-warning "^1.0.2" -"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.2: +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.2, jsx-ast-utils@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea" integrity sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw== @@ -8329,7 +8926,7 @@ language-subtag-registry@~0.3.2: resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== -language-tags@^1.0.5: +language-tags@=1.0.5, language-tags@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ== @@ -8375,15 +8972,6 @@ loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" - integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== - dependencies: - big.js "^5.2.2" - emojis-list "^2.0.0" - json5 "^1.0.1" - loader-utils@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" @@ -8394,9 +8982,9 @@ loader-utils@2.0.0: json5 "^2.1.2" loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + version "1.4.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" + integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" @@ -8512,9 +9100,9 @@ lodash.uniq@^4.5.0: integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== loglevel@^1.6.8: - version "1.8.0" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114" - integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== + version "1.8.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4" + integrity sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg== loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" @@ -8768,7 +9356,12 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.6: +minimist@^1.1.1: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minimist@^1.2.0, minimist@^1.2.6: version "1.2.7" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== @@ -8795,12 +9388,17 @@ minipass-pipeline@^1.2.2: minipass "^3.0.0" minipass@^3.0.0, minipass@^3.1.1: - version "3.3.4" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" - integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== dependencies: yallist "^4.0.0" +minipass@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.0.3.tgz#00bfbaf1e16e35e804f4aa31a7c1f6b8d9f0ee72" + integrity sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw== + minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -8902,9 +9500,9 @@ multimatch@^4.0.0: minimatch "^3.0.4" nan@^2.12.1: - version "2.16.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916" - integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA== + version "2.17.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" + integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== nanoid@^3.3.4: version "3.3.4" @@ -9046,6 +9644,11 @@ node-releases@^2.0.6: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== +node-releases@^2.0.8: + version "2.0.10" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" + integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== + normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -9153,7 +9756,7 @@ object-inspect@^1.12.2, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== -object-is@^1.0.1: +object-is@^1.0.1, object-is@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== @@ -9201,7 +9804,17 @@ object.fromentries@^2.0.6: define-properties "^1.1.4" es-abstract "^1.20.4" -object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: +object.getownpropertydescriptors@^2.0.3: + version "2.1.5" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz#db5a9002489b64eef903df81d6623c07e5b4b4d3" + integrity sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw== + dependencies: + array.prototype.reduce "^1.0.5" + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + +object.getownpropertydescriptors@^2.1.0: version "2.1.4" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37" integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== @@ -10226,7 +10839,15 @@ postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: +postcss-selector-parser@^6.0.0: + version "6.0.11" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" + integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-selector-parser@^6.0.2: version "6.0.10" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== @@ -10289,9 +10910,9 @@ postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, po source-map "^0.6.1" postcss@^8.1.0: - version "8.4.16" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c" - integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ== + version "8.4.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" + integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== dependencies: nanoid "^3.3.4" picocolors "^1.0.0" @@ -10504,10 +11125,10 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== -qs@6.10.3: - version "6.10.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== dependencies: side-channel "^1.0.4" @@ -10666,6 +11287,13 @@ react-dropzone@^14.2.3: file-selector "^0.6.0" prop-types "^15.8.1" +react-error-boundary@^3.1.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" + integrity sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-overlay@^6.0.9: version "6.0.11" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" @@ -10696,6 +11324,18 @@ react-media@^1.10.0: json2mq "^0.2.0" prop-types "^15.5.10" +react-redux@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.1.tgz#2bc029f5ada9b443107914c373a2750f6bc0f40c" + integrity sha512-LMZMsPY4DYdZfLJgd7i79n5Kps5N9XVLCJJeWAaPYTV+Eah2zTuBjTxKtNEbjiyitbq80/eIkm55CYSLqAub3w== + dependencies: + "@babel/runtime" "^7.12.1" + "@types/hoist-non-react-statics" "^3.3.1" + "@types/use-sync-external-store" "^0.0.3" + hoist-non-react-statics "^3.3.2" + react-is "^18.0.0" + use-sync-external-store "^1.0.0" + react-refresh@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -10880,6 +11520,18 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +redux-thunk@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" + integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== + +redux@^4.1.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13" + integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA== + dependencies: + "@babel/runtime" "^7.9.2" + regenerate-unicode-properties@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" @@ -10897,7 +11549,12 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: +regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.7: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== @@ -11011,6 +11668,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== +reselect@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.5.tgz#852c361247198da6756d07d9296c2b51eddb79f6" + integrity sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -11041,16 +11703,16 @@ resolve-from@^5.0.0: integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve-url-loader@^3.1.2: - version "3.1.4" - resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.4.tgz#3c16caebe0b9faea9c7cc252fa49d2353c412320" - integrity sha512-D3sQ04o0eeQEySLrcz4DsX3saHfsr8/N6tfhblxgZKXxMT2Louargg12oGNfoTRLV09GXhVUe5/qgA5vdgNigg== + version "3.1.5" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.5.tgz#1dce0847d4a2ef43c51f63c9fd30bf6dfbf26716" + integrity sha512-mgFMCmrV/tA4738EsFmPFE5/MaqSgUMe8LK971kVEKA/RrNVb7+VqFsg/qmKyythf34eyq476qIobP/gfFBGSQ== dependencies: adjust-sourcemap-loader "3.0.0" camelcase "5.3.1" compose-function "3.0.3" convert-source-map "1.7.0" es6-iterator "2.0.3" - loader-utils "1.2.3" + loader-utils "^1.2.3" postcss "7.0.36" rework "1.0.1" rework-visit "1.0.0" @@ -11069,7 +11731,7 @@ resolve@1.18.1: is-core-module "^2.0.0" path-parse "^1.0.6" -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.3.2: +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@^1.3.2: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -11260,9 +11922,9 @@ sanitize.css@^10.0.0: integrity sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg== sass-loader@^10.0.5: - version "10.3.1" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.3.1.tgz#a45f0d1dd7ea90de7eb099239a18c83dea6e6341" - integrity sha512-y2aBdtYkbqorVavkC3fcJIUDGIegzDWPn3/LAFhsf3G+MzPKTJx37sROf5pXtUeggSVbNbmfj8TgRaSLMelXRA== + version "10.4.1" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.4.1.tgz#bea4e173ddf512c9d7f53e9ec686186146807cbf" + integrity sha512-aX/iJZTTpNUNx/OSYzo2KsjIUQHqvWsAhhUijFjAPdZTEhstjZI9zTNvkTTwsx+uNUJqUwOw5gacxQMx4hJxGQ== dependencies: klona "^2.0.4" loader-utils "^2.0.0" @@ -11358,7 +12020,14 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7: +semver@^7.2.1: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.2, semver@^7.3.5, semver@^7.3.7: version "7.3.7" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== @@ -11729,7 +12398,14 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -stack-utils@^2.0.2, stack-utils@^2.0.3: +stack-utils@^2.0.2: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +stack-utils@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== @@ -11759,6 +12435,13 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== +stop-iteration-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" + stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -12084,9 +12767,9 @@ symbol-tree@^3.2.4: integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== table@^6.0.9: - version "6.8.0" - resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca" - integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA== + version "6.8.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" + integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA== dependencies: ajv "^8.0.1" lodash.truncate "^4.4.2" @@ -12100,13 +12783,13 @@ tapable@^1.0.0, tapable@^1.1.3: integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== tar@^6.0.2: - version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + version "6.1.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" + integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" - minipass "^3.0.0" + minipass "^4.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" @@ -12173,9 +12856,9 @@ terser@^4.1.2, terser@^4.6.2, terser@^4.6.3: source-map-support "~0.5.12" terser@^5.3.4: - version "5.15.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.15.0.tgz#e16967894eeba6e1091509ec83f0c60e179f2425" - integrity sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA== + version "5.16.4" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.4.tgz#51284b440b93242291a98f2a9903c024cfb70e6e" + integrity sha512-5yEGuZ3DZradbogeYQ1NaGz7rXVBDWujWlx1PT8efXO6Txn+eWbfKqB2bTDVmFXmePFkoLU6XI8UektMIEA0ug== dependencies: "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0" @@ -12581,6 +13264,14 @@ upath@^1.1.1, upath@^1.1.2, upath@^1.2.0: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== +update-browserslist-db@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + update-browserslist-db@^1.0.9: version "1.0.9" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz#2924d3927367a38d5c555413a7ce138fc95fcb18" @@ -12626,6 +13317,11 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +use-sync-external-store@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.0.0.tgz#d98f4a9c2e73d0f958e7e2d2c2bfb5f618cbd8fd" + integrity sha512-AFVsxg5GkFg8GDcxnl+Z0lMAz9rE8DGJCc28qnBuQF7lac57B5smLcT37aXpXIIPz75rW4g3eXHPjhHwdGskOw== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -12968,6 +13664,16 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-collection@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" + integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== + dependencies: + is-map "^2.0.1" + is-set "^2.0.1" + is-weakmap "^2.0.1" + is-weakset "^2.0.1" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"