diff --git a/contracts/contracts/FleekERC721.sol b/contracts/contracts/FleekERC721.sol index d42bf78..ca501c5 100644 --- a/contracts/contracts/FleekERC721.sol +++ b/contracts/contracts/FleekERC721.sol @@ -113,7 +113,9 @@ contract FleekERC721 is bool accessPointAutoApproval, address verifier ) public payable requirePayment(Billing.Mint) returns (uint256) { - FleekENS.requireENSOwner(ens); + if (!hasCollectionRole(CollectionRoles.Verifier, verifier)) + revert MustHaveCollectionRole(uint8(CollectionRoles.Verifier)); + if (bytes(ens).length > 0) FleekENS.requireENSOwner(ens); uint256 tokenId = _appIds; _mint(to, tokenId); diff --git a/contracts/package.json b/contracts/package.json index d450d3e..4fbc8cb 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -45,7 +45,7 @@ "colorthief": "^2.3.2", "dotenv": "^16.0.2", "ethers": "^5.7.2", - "hardhat": "^2.11.2", + "hardhat": "^2.14.0", "hardhat-contract-sizer": "^2.6.1", "hardhat-gas-reporter": "^1.0.9", "lint-staged": "^13.0.4", diff --git a/contracts/test/foundry/FleekERC721/Mint.t.sol b/contracts/test/foundry/FleekERC721/Mint.t.sol index e66d637..be0c178 100644 --- a/contracts/test/foundry/FleekERC721/Mint.t.sol +++ b/contracts/test/foundry/FleekERC721/Mint.t.sol @@ -103,4 +103,39 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base { assertEq(tokenId, 0); assertEq(CuT.ownerOf(tokenId), to); } + + function testFuzz_shouldNotAllowMintWithInvalidVerifier(address verifier) public { + vm.assume(!CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, verifier)); + expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier); + CuT.mint( + deployer, + TestConstants.APP_NAME, + TestConstants.APP_DESCRIPTION, + TestConstants.APP_EXTERNAL_URL, + TestConstants.APP_ENS, + TestConstants.APP_COMMIT_HASH, + TestConstants.APP_GIT_REPOSITORY, + TestConstants.LOGO_0, + TestConstants.APP_COLOR, + false, + verifier + ); + } + + function test_shouldAllowMintWithEmptyENS() public { + uint256 tokenId = CuT.mint( + deployer, + TestConstants.APP_NAME, + TestConstants.APP_DESCRIPTION, + TestConstants.APP_EXTERNAL_URL, + "", + TestConstants.APP_COMMIT_HASH, + TestConstants.APP_GIT_REPOSITORY, + TestConstants.LOGO_0, + TestConstants.APP_COLOR, + false, + deployer + ); + assertEq(tokenId, 0); + } } diff --git a/contracts/test/hardhat/contracts/FleekERC721/minting.t.ts b/contracts/test/hardhat/contracts/FleekERC721/minting.t.ts index 6441ca6..a21a94e 100644 --- a/contracts/test/hardhat/contracts/FleekERC721/minting.t.ts +++ b/contracts/test/hardhat/contracts/FleekERC721/minting.t.ts @@ -1,9 +1,9 @@ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; -import { TestConstants, Fixtures } from './helpers'; +import { TestConstants, Fixtures, Errors } from './helpers'; import { ethers } from 'hardhat'; -const { MintParams } = TestConstants; +const { MintParams, CollectionRoles } = TestConstants; describe('FleekERC721.Minting', () => { it('should be able to mint a new token', async () => { @@ -51,4 +51,49 @@ describe('FleekERC721.Minting', () => { expect(await contract.ownerOf(tokenId)).to.equal(owner.address); expect(await contract.ownerOf(tokenId)).not.to.equal(otherAccount.address); }); + + it('should not allow minting with non verifier account param', async () => { + const { owner, otherAccount, contract } = await loadFixture( + Fixtures.default + ); + + await expect( + contract.mint( + owner.address, + MintParams.name, + MintParams.description, + MintParams.externalUrl, + MintParams.ens, + MintParams.commitHash, + MintParams.gitRepository, + MintParams.logo, + MintParams.color, + MintParams.accessPointAutoApprovalSettings, + otherAccount.address + ) + ) + .to.be.revertedWithCustomError(contract, Errors.MustHaveCollectionRole) + .withArgs(CollectionRoles.Verifier); + }); + + it('should allow minting with empty ens', async () => { + const { owner, contract } = await loadFixture(Fixtures.default); + + const response = await contract.mint( + owner.address, + MintParams.name, + MintParams.description, + MintParams.externalUrl, + '', + MintParams.commitHash, + MintParams.gitRepository, + MintParams.logo, + MintParams.color, + MintParams.accessPointAutoApprovalSettings, + owner.address + ); + + expect(response.value).to.be.instanceOf(ethers.BigNumber); + expect(response.value.toNumber()).to.equal(0); + }); }); diff --git a/contracts/test/hardhat/scripts/deploy.ts b/contracts/test/hardhat/scripts/deploy.ts index 74e5dd9..d04ba3f 100644 --- a/contracts/test/hardhat/scripts/deploy.ts +++ b/contracts/test/hardhat/scripts/deploy.ts @@ -104,6 +104,6 @@ describe('Deploy', () => { TestConstants.MintParams.accessPointAutoApprovalSettings, owner.address ) - ).to.be.revertedWithCustomError(implementation, Errors.ContractIsPaused); + ).to.be.reverted; }); }); diff --git a/contracts/yarn.lock b/contracts/yarn.lock index f201b03..321468c 100644 --- a/contracts/yarn.lock +++ b/contracts/yarn.lock @@ -2,6 +2,42 @@ # yarn lockfile v1 +"@chainsafe/as-sha256@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz#3639df0e1435cab03f4d9870cc3ac079e57a6fc9" + integrity sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg== + +"@chainsafe/persistent-merkle-tree@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz#4c9ee80cc57cd3be7208d98c40014ad38f36f7ff" + integrity sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ== + dependencies: + "@chainsafe/as-sha256" "^0.3.1" + +"@chainsafe/persistent-merkle-tree@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz#2b4a62c9489a5739dedd197250d8d2f5427e9f63" + integrity sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw== + dependencies: + "@chainsafe/as-sha256" "^0.3.1" + +"@chainsafe/ssz@^0.10.0": + version "0.10.2" + resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.10.2.tgz#c782929e1bb25fec66ba72e75934b31fd087579e" + integrity sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg== + dependencies: + "@chainsafe/as-sha256" "^0.3.1" + "@chainsafe/persistent-merkle-tree" "^0.5.0" + +"@chainsafe/ssz@^0.9.2": + version "0.9.4" + resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.9.4.tgz#696a8db46d6975b600f8309ad3a12f7c0e310497" + integrity sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ== + dependencies: + "@chainsafe/as-sha256" "^0.3.1" + "@chainsafe/persistent-merkle-tree" "^0.4.2" + case "^1.6.3" + "@colors/colors@1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" @@ -256,7 +292,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2": +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.1", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -477,29 +513,31 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@nomicfoundation/ethereumjs-block@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz#fdd5c045e7baa5169abeed0e1202bf94e4481c49" - integrity sha512-bk8uP8VuexLgyIZAHExH1QEovqx0Lzhc9Ntm63nCRKLHXIZkobaFaeCVwTESV7YkPKUk7NiK11s8ryed4CS9yA== +"@nomicfoundation/ethereumjs-block@5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.1.tgz#6f89664f55febbd723195b6d0974773d29ee133d" + integrity sha512-u1Yioemi6Ckj3xspygu/SfFvm8vZEO8/Yx5a1QLzi6nVU0jz3Pg2OmHKJ5w+D9Ogk1vhwRiqEBAqcb0GVhCyHw== dependencies: - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-trie" "^5.0.0" - "@nomicfoundation/ethereumjs-tx" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@nomicfoundation/ethereumjs-common" "4.0.1" + "@nomicfoundation/ethereumjs-rlp" "5.0.1" + "@nomicfoundation/ethereumjs-trie" "6.0.1" + "@nomicfoundation/ethereumjs-tx" "5.0.1" + "@nomicfoundation/ethereumjs-util" "9.0.1" ethereum-cryptography "0.1.3" + ethers "^5.7.1" -"@nomicfoundation/ethereumjs-blockchain@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz#1a8c243a46d4d3691631f139bfb3a4a157187b0c" - integrity sha512-pLFEoea6MWd81QQYSReLlLfH7N9v7lH66JC/NMPN848ySPPQA5renWnE7wPByfQFzNrPBuDDRFFULMDmj1C0xw== +"@nomicfoundation/ethereumjs-blockchain@7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.1.tgz#80e0bd3535bfeb9baa29836b6f25123dab06a726" + integrity sha512-NhzndlGg829XXbqJEYrF1VeZhAwSPgsK/OB7TVrdzft3y918hW5KNd7gIZ85sn6peDZOdjBsAXIpXZ38oBYE5A== dependencies: - "@nomicfoundation/ethereumjs-block" "^4.0.0" - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-ethash" "^2.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-trie" "^5.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@nomicfoundation/ethereumjs-block" "5.0.1" + "@nomicfoundation/ethereumjs-common" "4.0.1" + "@nomicfoundation/ethereumjs-ethash" "3.0.1" + "@nomicfoundation/ethereumjs-rlp" "5.0.1" + "@nomicfoundation/ethereumjs-trie" "6.0.1" + "@nomicfoundation/ethereumjs-tx" "5.0.1" + "@nomicfoundation/ethereumjs-util" "9.0.1" abstract-level "^1.0.3" debug "^4.3.3" ethereum-cryptography "0.1.3" @@ -507,105 +545,105 @@ lru-cache "^5.1.1" memory-level "^1.0.0" -"@nomicfoundation/ethereumjs-common@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz#f6bcc7753994555e49ab3aa517fc8bcf89c280b9" - integrity sha512-WS7qSshQfxoZOpHG/XqlHEGRG1zmyjYrvmATvc4c62+gZXgre1ymYP8ZNgx/3FyZY0TWe9OjFlKOfLqmgOeYwA== +"@nomicfoundation/ethereumjs-common@4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.1.tgz#4702d82df35b07b5407583b54a45bf728e46a2f0" + integrity sha512-OBErlkfp54GpeiE06brBW/TTbtbuBJV5YI5Nz/aB2evTDo+KawyEzPjBlSr84z/8MFfj8wS2wxzQX1o32cev5g== dependencies: - "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@nomicfoundation/ethereumjs-util" "9.0.1" crc-32 "^1.2.0" -"@nomicfoundation/ethereumjs-ethash@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz#11539c32fe0990e1122ff987d1b84cfa34774e81" - integrity sha512-WpDvnRncfDUuXdsAXlI4lXbqUDOA+adYRQaEezIkxqDkc+LDyYDbd/xairmY98GnQzo1zIqsIL6GB5MoMSJDew== +"@nomicfoundation/ethereumjs-ethash@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.1.tgz#65ca494d53e71e8415c9a49ef48bc921c538fc41" + integrity sha512-KDjGIB5igzWOp8Ik5I6QiRH5DH+XgILlplsHR7TEuWANZA759G6krQ6o8bvj+tRUz08YygMQu/sGd9mJ1DYT8w== dependencies: - "@nomicfoundation/ethereumjs-block" "^4.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@nomicfoundation/ethereumjs-block" "5.0.1" + "@nomicfoundation/ethereumjs-rlp" "5.0.1" + "@nomicfoundation/ethereumjs-util" "9.0.1" abstract-level "^1.0.3" bigint-crypto-utils "^3.0.23" ethereum-cryptography "0.1.3" -"@nomicfoundation/ethereumjs-evm@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz#99cd173c03b59107c156a69c5e215409098a370b" - integrity sha512-hVS6qRo3V1PLKCO210UfcEQHvlG7GqR8iFzp0yyjTg2TmJQizcChKgWo8KFsdMw6AyoLgLhHGHw4HdlP8a4i+Q== +"@nomicfoundation/ethereumjs-evm@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.1.tgz#f35681e203363f69ce2b3d3bf9f44d4e883ca1f1" + integrity sha512-oL8vJcnk0Bx/onl+TgQOQ1t/534GKFaEG17fZmwtPFeH8S5soiBYPCLUrvANOl4sCp9elYxIMzIiTtMtNNN8EQ== dependencies: - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" - "@types/async-eventemitter" "^0.2.1" - async-eventemitter "^0.2.4" + "@ethersproject/providers" "^5.7.1" + "@nomicfoundation/ethereumjs-common" "4.0.1" + "@nomicfoundation/ethereumjs-tx" "5.0.1" + "@nomicfoundation/ethereumjs-util" "9.0.1" debug "^4.3.3" ethereum-cryptography "0.1.3" mcl-wasm "^0.7.1" rustbn.js "~0.2.0" -"@nomicfoundation/ethereumjs-rlp@^4.0.0", "@nomicfoundation/ethereumjs-rlp@^4.0.0-beta.2": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz#d9a9c5f0f10310c8849b6525101de455a53e771d" - integrity sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw== +"@nomicfoundation/ethereumjs-rlp@5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.1.tgz#0b30c1cf77d125d390408e391c4bb5291ef43c28" + integrity sha512-xtxrMGa8kP4zF5ApBQBtjlSbN5E2HI8m8FYgVSYAnO6ssUoY5pVPGy2H8+xdf/bmMa22Ce8nWMH3aEW8CcqMeQ== -"@nomicfoundation/ethereumjs-statemanager@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz#14a9d4e1c828230368f7ab520c144c34d8721e4b" - integrity sha512-jCtqFjcd2QejtuAMjQzbil/4NHf5aAWxUc+CvS0JclQpl+7M0bxMofR2AJdtz+P3u0ke2euhYREDiE7iSO31vQ== +"@nomicfoundation/ethereumjs-statemanager@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.1.tgz#8824a97938db4471911e2d2f140f79195def5935" + integrity sha512-B5ApMOnlruVOR7gisBaYwFX+L/AP7i/2oAahatssjPIBVDF6wTX1K7Qpa39E/nzsH8iYuL3krkYeUFIdO3EMUQ== dependencies: - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-trie" "^5.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@nomicfoundation/ethereumjs-common" "4.0.1" + "@nomicfoundation/ethereumjs-rlp" "5.0.1" debug "^4.3.3" ethereum-cryptography "0.1.3" - functional-red-black-tree "^1.0.1" + ethers "^5.7.1" + js-sdsl "^4.1.4" -"@nomicfoundation/ethereumjs-trie@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz#dcfbe3be53a94bc061c9767a396c16702bc2f5b7" - integrity sha512-LIj5XdE+s+t6WSuq/ttegJzZ1vliwg6wlb+Y9f4RlBpuK35B9K02bO7xU+E6Rgg9RGptkWd6TVLdedTI4eNc2A== +"@nomicfoundation/ethereumjs-trie@6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.1.tgz#662c55f6b50659fd4b22ea9f806a7401cafb7717" + integrity sha512-A64It/IMpDVODzCgxDgAAla8jNjNtsoQZIzZUfIV5AY6Coi4nvn7+VReBn5itlxMiL2yaTlQr9TRWp3CSI6VoA== dependencies: - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@nomicfoundation/ethereumjs-rlp" "5.0.1" + "@nomicfoundation/ethereumjs-util" "9.0.1" + "@types/readable-stream" "^2.3.13" ethereum-cryptography "0.1.3" readable-stream "^3.6.0" -"@nomicfoundation/ethereumjs-tx@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz#59dc7452b0862b30342966f7052ab9a1f7802f52" - integrity sha512-Gg3Lir2lNUck43Kp/3x6TfBNwcWC9Z1wYue9Nz3v4xjdcv6oDW9QSMJxqsKw9QEGoBBZ+gqwpW7+F05/rs/g1w== +"@nomicfoundation/ethereumjs-tx@5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.1.tgz#7629dc2036b4a33c34e9f0a592b43227ef4f0c7d" + integrity sha512-0HwxUF2u2hrsIM1fsasjXvlbDOq1ZHFV2dd1yGq8CA+MEYhaxZr8OTScpVkkxqMwBcc5y83FyPl0J9MZn3kY0w== dependencies: - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" + "@chainsafe/ssz" "^0.9.2" + "@ethersproject/providers" "^5.7.2" + "@nomicfoundation/ethereumjs-common" "4.0.1" + "@nomicfoundation/ethereumjs-rlp" "5.0.1" + "@nomicfoundation/ethereumjs-util" "9.0.1" ethereum-cryptography "0.1.3" -"@nomicfoundation/ethereumjs-util@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz#deb2b15d2c308a731e82977aefc4e61ca0ece6c5" - integrity sha512-2emi0NJ/HmTG+CGY58fa+DQuAoroFeSH9gKu9O6JnwTtlzJtgfTixuoOqLEgyyzZVvwfIpRueuePb8TonL1y+A== +"@nomicfoundation/ethereumjs-util@9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.1.tgz#530cda8bae33f8b5020a8f199ed1d0a2ce48ec89" + integrity sha512-TwbhOWQ8QoSCFhV/DDfSmyfFIHjPjFBj957219+V3jTZYZ2rf9PmDtNOeZWAE3p3vlp8xb02XGpd0v6nTUPbsA== dependencies: - "@nomicfoundation/ethereumjs-rlp" "^4.0.0-beta.2" + "@chainsafe/ssz" "^0.10.0" + "@nomicfoundation/ethereumjs-rlp" "5.0.1" ethereum-cryptography "0.1.3" -"@nomicfoundation/ethereumjs-vm@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz#2bb50d332bf41790b01a3767ffec3987585d1de6" - integrity sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w== +"@nomicfoundation/ethereumjs-vm@7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.1.tgz#7d035e0993bcad10716c8b36e61dfb87fa3ca05f" + integrity sha512-rArhyn0jPsS/D+ApFsz3yVJMQ29+pVzNZ0VJgkzAZ+7FqXSRtThl1C1prhmlVr3YNUlfpZ69Ak+RUT4g7VoOuQ== dependencies: - "@nomicfoundation/ethereumjs-block" "^4.0.0" - "@nomicfoundation/ethereumjs-blockchain" "^6.0.0" - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-evm" "^1.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-statemanager" "^1.0.0" - "@nomicfoundation/ethereumjs-trie" "^5.0.0" - "@nomicfoundation/ethereumjs-tx" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" - "@types/async-eventemitter" "^0.2.1" - async-eventemitter "^0.2.4" + "@nomicfoundation/ethereumjs-block" "5.0.1" + "@nomicfoundation/ethereumjs-blockchain" "7.0.1" + "@nomicfoundation/ethereumjs-common" "4.0.1" + "@nomicfoundation/ethereumjs-evm" "2.0.1" + "@nomicfoundation/ethereumjs-rlp" "5.0.1" + "@nomicfoundation/ethereumjs-statemanager" "2.0.1" + "@nomicfoundation/ethereumjs-trie" "6.0.1" + "@nomicfoundation/ethereumjs-tx" "5.0.1" + "@nomicfoundation/ethereumjs-util" "9.0.1" debug "^4.3.3" ethereum-cryptography "0.1.3" - functional-red-black-tree "^1.0.1" mcl-wasm "^0.7.1" rustbn.js "~0.2.0" @@ -901,11 +939,6 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== -"@types/async-eventemitter@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz#f8e6280e87e8c60b2b938624b0a3530fb3e24712" - integrity sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg== - "@types/bignumber.js@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@types/bignumber.js/-/bignumber.js-5.0.0.tgz#d9f1a378509f3010a3255e9cc822ad0eeb4ab969" @@ -1030,6 +1063,14 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/readable-stream@^2.3.13": + version "2.3.15" + resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-2.3.15.tgz#3d79c9ceb1b6a57d5f6e6976f489b9b5384321ae" + integrity sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ== + dependencies: + "@types/node" "*" + safe-buffer "~5.1.1" + "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" @@ -1296,13 +1337,6 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== -async-eventemitter@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/async-eventemitter/-/async-eventemitter-0.2.4.tgz#f5e7c8ca7d3e46aab9ec40a292baf686a0bafaca" - integrity sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw== - dependencies: - async "^2.4.0" - async-limiter@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" @@ -1313,13 +1347,6 @@ async@1.x: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w== -async@^2.4.0: - version "2.6.4" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" - integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== - dependencies: - lodash "^4.17.14" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1604,6 +1631,11 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +case@^1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9" + integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== + caseless@^0.12.0, caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -2564,7 +2596,7 @@ ethers@^4.0.40: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@^5.7.2: +ethers@^5.7.1, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -3230,23 +3262,23 @@ hardhat-gas-reporter@^1.0.9: eth-gas-reporter "^0.2.25" sha1 "^1.1.1" -hardhat@^2.11.2: - version "2.12.6" - resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.12.6.tgz#ea3c058bbd81850867389d10f76037cfa52a0019" - integrity sha512-0Ent1O5DsPgvaVb5sxEgsQ3bJRt/Ex92tsoO+xjoNH2Qc4bFmhI5/CHVlFikulalxOPjNmw5XQ2vJFuVQFESAA== +hardhat@^2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.14.0.tgz#b60c74861494aeb1b50803cf04cc47865a42b87a" + integrity sha512-73jsInY4zZahMSVFurSK+5TNCJTXMv+vemvGia0Ac34Mm19fYp6vEPVGF3sucbumszsYxiTT2TbS8Ii2dsDSoQ== dependencies: "@ethersproject/abi" "^5.1.2" "@metamask/eth-sig-util" "^4.0.0" - "@nomicfoundation/ethereumjs-block" "^4.0.0" - "@nomicfoundation/ethereumjs-blockchain" "^6.0.0" - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-evm" "^1.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-statemanager" "^1.0.0" - "@nomicfoundation/ethereumjs-trie" "^5.0.0" - "@nomicfoundation/ethereumjs-tx" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" - "@nomicfoundation/ethereumjs-vm" "^6.0.0" + "@nomicfoundation/ethereumjs-block" "5.0.1" + "@nomicfoundation/ethereumjs-blockchain" "7.0.1" + "@nomicfoundation/ethereumjs-common" "4.0.1" + "@nomicfoundation/ethereumjs-evm" "2.0.1" + "@nomicfoundation/ethereumjs-rlp" "5.0.1" + "@nomicfoundation/ethereumjs-statemanager" "2.0.1" + "@nomicfoundation/ethereumjs-trie" "6.0.1" + "@nomicfoundation/ethereumjs-tx" "5.0.1" + "@nomicfoundation/ethereumjs-util" "9.0.1" + "@nomicfoundation/ethereumjs-vm" "7.0.1" "@nomicfoundation/solidity-analyzer" "^0.1.0" "@sentry/node" "^5.18.1" "@types/bn.js" "^5.1.0" @@ -3766,6 +3798,11 @@ jpeg-js@^0.4.1: resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== +js-sdsl@^4.1.4: + version "4.4.0" + resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.0.tgz#8b437dbe642daa95760400b602378ed8ffea8430" + integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== + js-sha3@0.5.7, js-sha3@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" diff --git a/serverless/.gitignore b/serverless/.gitignore index af4cbef..d52362e 100644 --- a/serverless/.gitignore +++ b/serverless/.gitignore @@ -5,6 +5,8 @@ jspm_packages # Serverless directories .serverless +yarn-error.log + # esbuild directories .esbuild diff --git a/serverless/serverless.yaml b/serverless/serverless.yaml index 8e43740..a0904ff 100644 --- a/serverless/serverless.yaml +++ b/serverless/serverless.yaml @@ -36,4 +36,12 @@ functions: - http: path: build method: post + cors: true + + submitMintInfo: + handler: dist/functions/mints/handler.submitMintInfo + events: + - http: + path: mint + method: post cors: true \ No newline at end of file diff --git a/serverless/src/functions/mints/handler.ts b/serverless/src/functions/mints/handler.ts new file mode 100644 index 0000000..b691f31 --- /dev/null +++ b/serverless/src/functions/mints/handler.ts @@ -0,0 +1,45 @@ +import { + APIGatewayProxyResult, + APIGatewayEvent, + ///APIGatewayEventRequestContext, + } from 'aws-lambda'; + import { formatJSONResponse } from '@libs/api-gateway'; + + import { v4 } from 'uuid'; + + export const submitMintInfo = async ( + event: APIGatewayEvent, + ///context: APIGatewayEventRequestContext + ): Promise => { + try { + const id = v4(); + + /**if (!verifyAlchemySig(event.headers.xalchemywork)) { + throw new Error('Invalid sig'); + }**/ + + if (event.body == undefined) { + throw new Error('Undefined data'); + } + + const mintInfo = { + buildId: id, + createdAt: new Date().toISOString(), + body: JSON.parse(event.body), + }; + + // check if we have it in mongo + // if so, trigger verification call + // if not, add to mongo + + return formatJSONResponse({ + mintInfo, + }); + } catch (e) { + return formatJSONResponse({ + status: 500, + message: e, + }); + } + }; + \ No newline at end of file diff --git a/serverless/src/functions/mints/index.ts b/serverless/src/functions/mints/index.ts new file mode 100644 index 0000000..f8d0528 --- /dev/null +++ b/serverless/src/functions/mints/index.ts @@ -0,0 +1,13 @@ +import { handlerPath } from '@libs/handler-resolver'; + +export const newMint = { + handler: `${handlerPath(__dirname)}/handler.newMint`, + events: [ + { + http: { + method: 'post', + path: 'mint', + }, + }, + ], +}; \ No newline at end of file diff --git a/ui/graphql/queries.graphql b/ui/graphql/queries.graphql index 3e1f0c5..a7cac93 100644 --- a/ui/graphql/queries.graphql +++ b/ui/graphql/queries.graphql @@ -41,7 +41,15 @@ query getLatestNFAs { query getNFA($id: ID!) { token(id: $id) { tokenId + owner { + id + } name + description + ENS + externalURL + logo + color } } diff --git a/ui/src/app.tsx b/ui/src/app.tsx index 16fc6fb..634b2b2 100644 --- a/ui/src/app.tsx +++ b/ui/src/app.tsx @@ -3,7 +3,13 @@ import { HashRouter, Navigate, Route, Routes } from 'react-router-dom'; import { themeGlobals } from '@/theme/globals'; import { AppPage, ToastProvider } from './components'; -import { ComponentsTest, CreateAP, Explore, Home, Mint } from './views'; +import { + ComponentsTest, + CreateAP, + ExploreView, + IndexedNFAView, + Mint, +} from './views'; export const App: React.FC = () => { themeGlobals(); @@ -13,10 +19,10 @@ export const App: React.FC = () => { - } /> + } /> } /> - } /> } /> + } /> {/** TODO remove for release */} } /> } /> diff --git a/ui/src/components/card-tag/card-tag.ts b/ui/src/components/card-tag/card-tag.ts new file mode 100644 index 0000000..62e340c --- /dev/null +++ b/ui/src/components/card-tag/card-tag.ts @@ -0,0 +1,10 @@ +import { styled } from '@/theme'; + +export const CardTag = styled('span', { + fontSize: '$sm', + backgroundColor: '$slate4', + color: '$slate11', + p: '$1 $3h', + height: '$7', + borderRadius: '$md', +}); diff --git a/ui/src/components/card-tag/index.ts b/ui/src/components/card-tag/index.ts new file mode 100644 index 0000000..602b85e --- /dev/null +++ b/ui/src/components/card-tag/index.ts @@ -0,0 +1 @@ +export * from './card-tag'; diff --git a/ui/src/components/core/combobox/dropdown.tsx b/ui/src/components/core/combobox/dropdown.tsx index c3d5baf..abf80ed 100644 --- a/ui/src/components/core/combobox/dropdown.tsx +++ b/ui/src/components/core/combobox/dropdown.tsx @@ -96,7 +96,7 @@ const DropdownButton: React.FC = ({ {selectedValue && selectedValue.label ? selectedValue.label : 'Select'} @@ -172,7 +172,7 @@ export const Dropdown: React.FC = ({ leaveTo="opacity-0" > {items.map((option: DropdownItem) => ( diff --git a/ui/src/components/core/icon/icon-library.tsx b/ui/src/components/core/icon/icon-library.tsx index a8c481b..70d5473 100644 --- a/ui/src/components/core/icon/icon-library.tsx +++ b/ui/src/components/core/icon/icon-library.tsx @@ -4,12 +4,15 @@ import { AiOutlineTwitter } from '@react-icons/all-files/ai/AiOutlineTwitter'; import { BiGitBranch } from '@react-icons/all-files/bi/BiGitBranch'; import { BiSearch } from '@react-icons/all-files/bi/BiSearch'; import { BsFillSquareFill } from '@react-icons/all-files/bs/BsFillSquareFill'; +import { FaChevronRight } from '@react-icons/all-files/fa/FaChevronRight'; +import { FaExternalLinkAlt } from '@react-icons/all-files/fa/FaExternalLinkAlt'; import { IoArrowBackCircleSharp } from '@react-icons/all-files/io5/IoArrowBackCircleSharp'; import { IoCheckmarkCircleSharp } from '@react-icons/all-files/io5/IoCheckmarkCircleSharp'; import { IoClose } from '@react-icons/all-files/io5/IoClose'; import { IoCloudUploadSharp } from '@react-icons/all-files/io5/IoCloudUploadSharp'; import { IoInformationCircleSharp } from '@react-icons/all-files/io5/IoInformationCircleSharp'; import { IoLogoGithub } from '@react-icons/all-files/io5/IoLogoGithub'; +import { MdVerifiedUser } from '@react-icons/all-files/md/MdVerifiedUser'; import { BetaTag, @@ -28,19 +31,22 @@ export const IconLibrary = Object.freeze({ check: AiOutlineCheck, 'check-circle': IoCheckmarkCircleSharp, 'chevron-down': ChevronDownIcon, + 'chevron-right': FaChevronRight, close: IoClose, error: ErrorIcon, ethereum: EthereumIcon, + 'external-link': FaExternalLinkAlt, fleekLogo: FleekLogo, fleekName: FleekName, github: IoLogoGithub, info: IoInformationCircleSharp, - upload: IoCloudUploadSharp, metamask: MetamaskIcon, //remove if not used search: BiSearch, square: BsFillSquareFill, success: AiFillCheckCircle, twitter: AiOutlineTwitter, + upload: IoCloudUploadSharp, + verified: MdVerifiedUser, }); export type IconName = keyof typeof IconLibrary; diff --git a/ui/src/components/core/input/input-file.styles.ts b/ui/src/components/core/input/input-file.styles.ts index d246812..7427094 100644 --- a/ui/src/components/core/input/input-file.styles.ts +++ b/ui/src/components/core/input/input-file.styles.ts @@ -24,6 +24,5 @@ export abstract class InputFileStyles { '&[aria-invalid=true], &[data-invalid]': { borderColor: '$red9', }, - //TODO add error state }); } diff --git a/ui/src/components/index.ts b/ui/src/components/index.ts index 8e58b16..f877380 100644 --- a/ui/src/components/index.ts +++ b/ui/src/components/index.ts @@ -7,4 +7,5 @@ export * from './toast'; export * from './step'; export * from './nfa-card'; export * from './nfa-preview'; +export * from './card-tag'; export * from './resolved-address'; diff --git a/ui/src/components/layout/nav-bar/connect-wallet-button.tsx b/ui/src/components/layout/nav-bar/connect-wallet-button.tsx index 9f5c29c..0002553 100644 --- a/ui/src/components/layout/nav-bar/connect-wallet-button.tsx +++ b/ui/src/components/layout/nav-bar/connect-wallet-button.tsx @@ -1,11 +1,29 @@ import { Avatar, ConnectKitButton } from 'connectkit'; import { Button, Flex } from '@/components'; +import { ENSActions, useAppDispatch, useENSStore } from '@/store'; export const ConnectWalletButton: React.FC = () => { + const { addressMap } = useENSStore(); + const dispatch = useAppDispatch(); + + const setEnsNameStore = (ensName: string, address: string): void => { + const stored = addressMap[address] || {}; + if (typeof stored.state !== 'undefined') return; + + dispatch( + ENSActions.setAddress({ + key: address, + value: { state: 'success', value: ensName }, + }) + ); + }; + return ( {({ isConnected, show, truncatedAddress, address, ensName }) => { + if (ensName && address) setEnsNameStore(ensName, address); + return ( + + ); +}; diff --git a/ui/src/views/access-point/create-ap-form.tsx b/ui/src/views/access-point/ap-form-step/create-ap-form.tsx similarity index 73% rename from ui/src/views/access-point/create-ap-form.tsx rename to ui/src/views/access-point/ap-form-step/create-ap-form.tsx index 643f38c..0ff5e55 100644 --- a/ui/src/views/access-point/create-ap-form.tsx +++ b/ui/src/views/access-point/ap-form-step/create-ap-form.tsx @@ -1,6 +1,6 @@ -import { Card, Grid, Icon, IconButton, Stepper } from '@/components'; +import { Card, Flex, Icon, IconButton, Stepper } from '@/components'; -import { CreateAccessPointFormBody } from './create-ap.form-body'; +import { CreateAccessPointFormBody } from './create-ap-form-body'; export const CreateAccessPointForm: React.FC = () => { const { prevStep } = Stepper.useContext(); @@ -8,7 +8,7 @@ export const CreateAccessPointForm: React.FC = () => { return ( { } /> - - + ); diff --git a/ui/src/views/access-point/create-ap.form.context.ts b/ui/src/views/access-point/ap-form-step/create-ap.form.context.ts similarity index 83% rename from ui/src/views/access-point/create-ap.form.context.ts rename to ui/src/views/access-point/ap-form-step/create-ap.form.context.ts index fea18bf..60228a0 100644 --- a/ui/src/views/access-point/create-ap.form.context.ts +++ b/ui/src/views/access-point/ap-form-step/create-ap.form.context.ts @@ -5,7 +5,7 @@ import { createContext, StringValidators } from '@/utils'; export type CreateAccessPointFormContext = { form: { - appName: FormField; + domain: FormField; isValid: ReactState; }; }; @@ -20,9 +20,9 @@ export const [CreateAccessPointFormProvider, useAccessPointFormContext] = export const useAccessPointFormContextInit = (): CreateAccessPointFormContext => ({ form: { - appName: useFormField('appName', [ + domain: useFormField('domain', [ StringValidators.required, - StringValidators.maxLength(50), + StringValidators.isValidDomain, ]), isValid: useState(false), }, diff --git a/ui/src/views/access-point/ap-form-step/index.ts b/ui/src/views/access-point/ap-form-step/index.ts new file mode 100644 index 0000000..8b94558 --- /dev/null +++ b/ui/src/views/access-point/ap-form-step/index.ts @@ -0,0 +1,2 @@ +export * from './create-ap.form.context'; +export * from './create-ap-form'; diff --git a/ui/src/views/access-point/ap-record-step/ap-record-body.tsx b/ui/src/views/access-point/ap-record-step/ap-record-body.tsx new file mode 100644 index 0000000..e14bd96 --- /dev/null +++ b/ui/src/views/access-point/ap-record-step/ap-record-body.tsx @@ -0,0 +1,80 @@ +import { useEffect, useMemo } from 'react'; + +import { Button, Card, Grid, SpinnerDot, Stepper, Text } from '@/components'; +import { bunnyCDNActions, useAppDispatch, useBunnyCDNStore } from '@/store'; + +import { useAccessPointFormContext } from '../ap-form-step'; +import { CreateAccessPoint } from '../create-ap.context'; +import { DisplayText } from '../display-text'; +import { isSubdomain } from './record-step.utils'; + +export const APRecordCardBody: React.FC = () => { + const dispatch = useAppDispatch(); + const { bunnyURL, state } = useBunnyCDNStore(); + const { + nfa: { domain: nfaDomain }, + } = CreateAccessPoint.useContext(); + const { + form: { + domain: { + value: [accesPointDomain], + }, + }, + } = useAccessPointFormContext(); + const { nextStep } = Stepper.useContext(); + + const isSudomain = useMemo( + () => isSubdomain(accesPointDomain), + [accesPointDomain] + ); + + useEffect(() => { + if (state === 'success') { + dispatch(bunnyCDNActions.setState(undefined)); + nextStep(); + } + }, [state, nextStep, dispatch]); + + const handleContinueClick = (): void => { + dispatch(bunnyCDNActions.verifyBunnyPullzone(nfaDomain)); + }; + + return ( + + {state === 'loading' ? ( + + + + Waiting for DNS propagation, allow a few minutes. + + + ) : ( + + + {`Create a ${ + isSudomain ? 'CNAME' : 'ANAME' + } record in your DNS provider pointing to our CDN + endpoint.`} + + + + + + + )} + + ); +}; diff --git a/ui/src/views/access-point/ap-record-step/ap-record-header.tsx b/ui/src/views/access-point/ap-record-step/ap-record-header.tsx new file mode 100644 index 0000000..02b6bfd --- /dev/null +++ b/ui/src/views/access-point/ap-record-step/ap-record-header.tsx @@ -0,0 +1,29 @@ +import { Card, Icon, IconButton, Stepper } from '@/components'; + +export const APRecordCardHeader: React.FC = () => { + const { prevStep } = Stepper.useContext(); + + return ( + } + css={{ mr: '$2' }} + onClick={prevStep} + /> + } + rightIcon={ + } + /> + } + /> + ); +}; diff --git a/ui/src/views/access-point/ap-record-step/ap-record-step.tsx b/ui/src/views/access-point/ap-record-step/ap-record-step.tsx new file mode 100644 index 0000000..a103fb6 --- /dev/null +++ b/ui/src/views/access-point/ap-record-step/ap-record-step.tsx @@ -0,0 +1,13 @@ +import { Card } from '@/components'; + +import { APRecordCardBody } from './ap-record-body'; +import { APRecordCardHeader } from './ap-record-header'; + +export const APRecordStep: React.FC = () => { + return ( + + + + + ); +}; diff --git a/ui/src/views/access-point/ap-record-step/index.ts b/ui/src/views/access-point/ap-record-step/index.ts new file mode 100644 index 0000000..431877c --- /dev/null +++ b/ui/src/views/access-point/ap-record-step/index.ts @@ -0,0 +1 @@ +export * from './ap-record-step'; diff --git a/ui/src/views/access-point/ap-record-step/record-step.utils.ts b/ui/src/views/access-point/ap-record-step/record-step.utils.ts new file mode 100644 index 0000000..3e9370b --- /dev/null +++ b/ui/src/views/access-point/ap-record-step/record-step.utils.ts @@ -0,0 +1,5 @@ +export const isSubdomain = (url: string): boolean => { + const urlParts = url.split('.'); + + return urlParts.length > 2; +}; diff --git a/ui/src/views/access-point/create-ap-preview.tsx b/ui/src/views/access-point/create-ap-preview.tsx index d14330b..6ac9159 100644 --- a/ui/src/views/access-point/create-ap-preview.tsx +++ b/ui/src/views/access-point/create-ap-preview.tsx @@ -1,36 +1,71 @@ import { ethers } from 'ethers'; -import { useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; +import { useAccount } from 'wagmi'; import { Button, Card, Flex, - Grid, Icon, IconButton, + ResolvedAddress, Stepper, + Text, } from '@/components'; import { useTransactionCost } from '@/hooks'; import { FleekERC721 } from '@/integrations'; +import { AppLog } from '@/utils'; +import { useAccessPointFormContext } from './ap-form-step/create-ap.form.context'; +import { SelectedNFA } from './ap-form-step/create-ap-form-body'; import { CreateAccessPoint } from './create-ap.context'; -import { useAccessPointFormContext } from './create-ap.form.context'; +import { DisplayText } from './display-text'; + +export const AccessPointDataFragment: React.FC = () => { + const { address, status } = useAccount(); + const { + form: { + domain: { + value: [domain], + }, + }, + } = useAccessPointFormContext(); + + if (status === 'connecting') return
Loading...
; //TODO replace with spinner + + return ( + <> + + {address || ''} + ) : ( + 'Please connect to wallet' + ) + } + /> + + + ); +}; export const CreateAccessPointPreview: React.FC = () => { const { prevStep } = Stepper.useContext(); + const { address } = useAccount(); + const { prepare: { status: prepareStatus, data: prepareData, error: prepareError }, write: { status: writeStatus, write }, transaction: { status: transactionStatus }, } = CreateAccessPoint.useTransactionContext(); + const { form: { - appName: { - value: [appName], - }, + isValid: [isValid], }, } = useAccessPointFormContext(); - const { nfa } = CreateAccessPoint.useContext(); const [cost, currency, isCostLoading] = useTransactionCost( prepareData?.request.value, @@ -66,10 +101,19 @@ export const CreateAccessPointPreview: React.FC = () => { [prepareStatus, writeStatus, transactionStatus] ); + useEffect(() => { + const error = [writeStatus, transactionStatus].some( + (status) => status === 'error' + ); + if (error) { + AppLog.errorToast('An error occurred while minting the NFA'); + } + }, [writeStatus, transactionStatus]); + return ( { } /> - - - NFA: {nfa.value} - {appName} - {message} - + + + {message} - + ); diff --git a/ui/src/views/access-point/create-ap-success.tsx b/ui/src/views/access-point/create-ap-success.tsx new file mode 100644 index 0000000..6758e67 --- /dev/null +++ b/ui/src/views/access-point/create-ap-success.tsx @@ -0,0 +1,47 @@ +import { Button, Card, Flex, Icon, IconButton, Text } from '@/components'; + +import { CreateAccessPoint } from './create-ap.context'; +import { AccessPointDataFragment } from './create-ap-preview'; + +export const CreateAccessPointSuccess: React.FC = () => { + const { nfa } = CreateAccessPoint.useContext(); + return ( + + + } + rightIcon={ + } + /> + } + /> + + + + {`You have successfully hosted a ${nfa.name} frontend on your own domain.`} + + + + + + + + + + ); +}; diff --git a/ui/src/views/access-point/create-ap.context.tsx b/ui/src/views/access-point/create-ap.context.tsx index 6dcf9bb..119f29c 100644 --- a/ui/src/views/access-point/create-ap.context.tsx +++ b/ui/src/views/access-point/create-ap.context.tsx @@ -1,16 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { useState } from 'react'; -import { Token } from '@/graphclient'; import { EthereumHooks } from '@/integrations'; import { useFleekERC721Billing } from '@/store'; -import { AppLog, createContext, pushToast } from '@/utils'; +import { AppLog, createContext } from '@/utils'; -type NFA = Pick; +export type NFA = { + tokenId: string; + name: string; + logo: string; + color: number; + domain: string; +}; export type AccessPointContext = { billing: string | undefined; - nfa: NFA | undefined; - setNfa: ReactState[1]; + nfa: NFA; + setNfa: (nfa: NFA) => void; }; const [CreateAPProvider, useContext] = createContext({ @@ -31,7 +38,13 @@ export abstract class CreateAccessPoint { children, }) => { const [billing] = useFleekERC721Billing('AddAccessPoint'); - const [nfa, setNfa] = useState(); + const [nfa, setNfa] = useState({ + tokenId: '', + name: '', + logo: '', + color: 0, + domain: '', + }); const value = { billing, @@ -44,12 +57,10 @@ export abstract class CreateAccessPoint { { + onSuccess: (data: any) => { AppLog.info('Transaction:', data); - pushToast('success', 'Your transaction was successful!'); }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onError: (error) => { + onError: (error: any) => { AppLog.errorToast( 'There was an error trying to create the Access Point. Please try again' ); diff --git a/ui/src/views/access-point/create-ap.form-body.tsx b/ui/src/views/access-point/create-ap.form-body.tsx deleted file mode 100644 index 1180c3c..0000000 --- a/ui/src/views/access-point/create-ap.form-body.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useQuery } from '@apollo/client'; -import { ethers } from 'ethers'; -import { useEffect } from 'react'; -import { useParams } from 'react-router-dom'; - -import { Button, Flex, Form, Spinner, Stepper } from '@/components'; -import { getNFADocument } from '@/graphclient'; -import { AppLog } from '@/utils'; - -import { CreateAccessPoint } from './create-ap.context'; -import { useAccessPointFormContext } from './create-ap.form.context'; -import { NfaPicker } from './nfa-picker'; - -export const CreateAccessPointFormBody: React.FC = () => { - const { id } = useParams(); - const { nextStep } = Stepper.useContext(); - const { nfa, setNfa, billing } = CreateAccessPoint.useContext(); - const { setArgs } = CreateAccessPoint.useTransactionContext(); - - const { - form: { - appName: { - value: [appName], - }, - isValid: [isValid], - }, - } = useAccessPointFormContext(); - - const { - form: { appName: appNameContext }, - } = useAccessPointFormContext(); - - const { - data: nfaData, - error: nfaError, - loading: nfaLoading, - } = useQuery(getNFADocument, { - skip: id === undefined, - variables: { - id: ethers.utils.hexlify(Number(id)), - }, - }); - - useEffect(() => { - if (nfaError) { - AppLog.errorToast('Error fetching NFA'); - } - }, [nfaError]); - - useEffect(() => { - if (nfaData) { - if (nfaData.token && id) { - const { name } = nfaData.token; - setNfa({ value: id, label: name }); - } else { - AppLog.errorToast("We couldn't find the NFA you are looking for"); - } - } - }, [nfaData, id, setNfa]); - - if (nfaLoading) { - return ( - - - - ); - } - - const handleContinueClick = (): void => { - if (nfa && appName) { - setArgs([Number(nfa.value), appName, { value: billing }]); - nextStep(); - } - }; - - return ( - <> - {/* TODO will have to do some changes on the Form.Combobox if we use this component for the NFA picker */} - {id === undefined && } - - App Name - - - - - ); -}; diff --git a/ui/src/views/access-point/create-ap.stepper.tsx b/ui/src/views/access-point/create-ap.stepper.tsx index bdc3f3f..89c7355 100644 --- a/ui/src/views/access-point/create-ap.stepper.tsx +++ b/ui/src/views/access-point/create-ap.stepper.tsx @@ -1,16 +1,29 @@ import { Form, Step, Stepper } from '@/components'; import { WalletStep } from '../mint/wallet-step'; -import { useAccessPointFormContext } from './create-ap.form.context'; -import { CreateAccessPointForm } from './create-ap-form'; +import { useAccessPointFormContext } from './ap-form-step/create-ap.form.context'; +import { CreateAccessPointForm } from './ap-form-step/create-ap-form'; +import { APRecordStep } from './ap-record-step/ap-record-step'; +import { isSubdomain } from './ap-record-step/record-step.utils'; +import { CreateAccessPoint } from './create-ap.context'; import { CreateAccessPointPreview } from './create-ap-preview'; +import { CreateAccessPointSuccess } from './create-ap-success'; export const CreateApStepper: React.FC = () => { + const { + transaction: { isSuccess }, + } = CreateAccessPoint.useTransactionContext(); const { form: { + domain: { + value: [accesPointDomain], + }, isValid: [, setIsValid], }, } = useAccessPointFormContext(); + + if (isSuccess) return ; + return ( @@ -22,13 +35,23 @@ export const CreateApStepper: React.FC = () => { - + - + + + + + + + diff --git a/ui/src/views/access-point/create-ap.tsx b/ui/src/views/access-point/create-ap.tsx index 446ee0b..3240bdc 100644 --- a/ui/src/views/access-point/create-ap.tsx +++ b/ui/src/views/access-point/create-ap.tsx @@ -1,10 +1,10 @@ import { Flex } from '@/components'; -import { CreateAccessPoint } from './create-ap.context'; import { CreateAccessPointFormProvider, useAccessPointFormContextInit, -} from './create-ap.form.context'; +} from './ap-form-step/create-ap.form.context'; +import { CreateAccessPoint } from './create-ap.context'; import { CreateApStepper } from './create-ap.stepper'; export const CreateAP: React.FC = () => { diff --git a/ui/src/views/access-point/display-text/display-text.styles.ts b/ui/src/views/access-point/display-text/display-text.styles.ts new file mode 100644 index 0000000..644be97 --- /dev/null +++ b/ui/src/views/access-point/display-text/display-text.styles.ts @@ -0,0 +1,25 @@ +import { styled } from '@/theme'; + +import { Flex } from '../../../components/layout'; + +export const DisplayTextStyles = { + Container: styled(Flex, { + flexDirection: 'column', + }), + Label: styled('label', { + color: '$slate11', + mb: '$1h', + + fontSize: '$xs', + //TODO add variants + }), + Input: styled('span', { + backgroundColor: '$slate1', + borderColor: '$slate1', + color: '$slate12', + borderRadius: '$lg', + fontSize: '$sm', + height: '$11', + p: '$3 $3h', + }), +}; diff --git a/ui/src/views/access-point/display-text/display-text.tsx b/ui/src/views/access-point/display-text/display-text.tsx new file mode 100644 index 0000000..5c6f7de --- /dev/null +++ b/ui/src/views/access-point/display-text/display-text.tsx @@ -0,0 +1,17 @@ +import { DisplayTextStyles as S } from './display-text.styles'; +type DisplayTextProps = { + label: string; + value: string | React.ReactNode; +}; + +export const DisplayText: React.FC = ({ + label, + value, +}: DisplayTextProps) => { + return ( + + {label} + {value} + + ); +}; diff --git a/ui/src/views/access-point/display-text/index.ts b/ui/src/views/access-point/display-text/index.ts new file mode 100644 index 0000000..8b8941a --- /dev/null +++ b/ui/src/views/access-point/display-text/index.ts @@ -0,0 +1 @@ +export * from './display-text'; diff --git a/ui/src/views/access-point/nfa-icon/index.ts b/ui/src/views/access-point/nfa-icon/index.ts new file mode 100644 index 0000000..c4e6ccc --- /dev/null +++ b/ui/src/views/access-point/nfa-icon/index.ts @@ -0,0 +1 @@ +export * from './nfa-icon'; diff --git a/ui/src/views/access-point/nfa-icon/nfa-icon.styles.ts b/ui/src/views/access-point/nfa-icon/nfa-icon.styles.ts new file mode 100644 index 0000000..39e91c7 --- /dev/null +++ b/ui/src/views/access-point/nfa-icon/nfa-icon.styles.ts @@ -0,0 +1,15 @@ +import { styled } from '@/theme'; + +export const NFAIconStyles = { + Container: styled('span', { + p: '$1h', + borderRadius: '$full', + width: '$7', + height: '$7', + mr: '$2', + }), + Image: styled('img', { + width: '100%', + height: '100%', + }), +}; diff --git a/ui/src/views/access-point/nfa-icon/nfa-icon.tsx b/ui/src/views/access-point/nfa-icon/nfa-icon.tsx new file mode 100644 index 0000000..3caddb1 --- /dev/null +++ b/ui/src/views/access-point/nfa-icon/nfa-icon.tsx @@ -0,0 +1,21 @@ +import { parseNumberToHexColor } from '@/utils/color'; + +import { NFAIconStyles as NS } from './nfa-icon.styles'; + +type NFAIconProps = { + image: string; + color: number; +}; + +export const NFAIconFragment: React.FC = ({ + image, + color, +}: NFAIconProps) => { + return ( + + + + ); +}; diff --git a/ui/src/views/access-point/nfa-picker.tsx b/ui/src/views/access-point/nfa-picker.tsx deleted file mode 100644 index e25b520..0000000 --- a/ui/src/views/access-point/nfa-picker.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useQuery } from '@apollo/client'; -import { useMemo } from 'react'; - -import { Combobox } from '@/components'; -import { getLatestNFAsDocument } from '@/graphclient'; -import { AppLog } from '@/utils'; - -import { CreateAccessPoint } from './create-ap.context'; - -export const NfaPicker: React.FC = () => { - const { nfa, setNfa } = CreateAccessPoint.useContext(); - const { data, loading, error } = useQuery(getLatestNFAsDocument); - - const items = useMemo(() => data?.tokens || [], [data]); - - if (error) { - AppLog.errorToast('Error loading NFA list', error); - } - - return ( - - {({ Field, Options }) => ( - <> - {(selected) => selected?.name || 'Select NFA'} - - {(item) => item.name} - - )} - - ); -}; diff --git a/ui/src/views/components-test/components-test.tsx b/ui/src/views/components-test/components-test.tsx index ec9655f..d20a2c1 100644 --- a/ui/src/views/components-test/components-test.tsx +++ b/ui/src/views/components-test/components-test.tsx @@ -2,13 +2,15 @@ import { Flex, ResolvedAddress } from '@/components'; import { ColorPickerTest } from './color-picker'; import { ComboboxTest } from './combobox-test'; +import { SpinnerTest } from './spinner-test'; import { ToastTest } from './toast-test'; export const ComponentsTest: React.FC = () => { return ( + - {'0x7ed735b7095c05d78df169f991f2b7f1a1f1a049'} + {'0x7ed735b7095c05d78df169f991f2b7f1a1f1a049a'} diff --git a/ui/src/views/components-test/spinner-test.tsx b/ui/src/views/components-test/spinner-test.tsx new file mode 100644 index 0000000..d24b6a8 --- /dev/null +++ b/ui/src/views/components-test/spinner-test.tsx @@ -0,0 +1,18 @@ +import { Flex, Spinner, SpinnerDot } from '@/components'; + +export const SpinnerTest: React.FC = () => { + return ( + <> + + + + + + + + + + + + ); +}; diff --git a/ui/src/views/explore/explore-header.tsx b/ui/src/views/explore/explore-header/explore-header.fragment.tsx similarity index 89% rename from ui/src/views/explore/explore-header.tsx rename to ui/src/views/explore/explore-header/explore-header.fragment.tsx index bd3665a..af30cf9 100644 --- a/ui/src/views/explore/explore-header.tsx +++ b/ui/src/views/explore/explore-header/explore-header.fragment.tsx @@ -4,7 +4,7 @@ import { Button } from '@/components'; import { ExploreHeaderStyles as S } from './explore-header.styles'; -export const ExploreHeader: React.FC = () => ( +export const ExploreHeaderFragment: React.FC = () => ( diff --git a/ui/src/views/explore/explore-header.styles.ts b/ui/src/views/explore/explore-header/explore-header.styles.ts similarity index 100% rename from ui/src/views/explore/explore-header.styles.ts rename to ui/src/views/explore/explore-header/explore-header.styles.ts diff --git a/ui/src/views/explore/explore-header/index.ts b/ui/src/views/explore/explore-header/index.ts new file mode 100644 index 0000000..2348fde --- /dev/null +++ b/ui/src/views/explore/explore-header/index.ts @@ -0,0 +1 @@ +export * from './explore-header.fragment'; diff --git a/ui/src/views/explore/explore-list/explore-list-container.tsx b/ui/src/views/explore/explore-list/explore-list-container.tsx deleted file mode 100644 index da1ea25..0000000 --- a/ui/src/views/explore/explore-list/explore-list-container.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Flex } from '@/components'; - -import { Explore } from '../explore.context'; -import { NFAListFragment } from './nfa-list'; -import { NFASearchFragment } from './nfa-search'; - -export const ExploreListContainer: React.FC = () => { - return ( - - - - - - - ); -}; diff --git a/ui/src/views/explore/explore-list/explore-list.fragment.tsx b/ui/src/views/explore/explore-list/explore-list.fragment.tsx new file mode 100644 index 0000000..a76b0c2 --- /dev/null +++ b/ui/src/views/explore/explore-list/explore-list.fragment.tsx @@ -0,0 +1,12 @@ +import { Explore } from '../explore.context'; +import { NFAListFragment } from './nfa-list.fragment'; +import { NFASearchFragment } from './nfa-search.fragment'; + +export const ExploreListFragment: React.FC = () => { + return ( + + + + + ); +}; diff --git a/ui/src/views/explore/explore-list/index.ts b/ui/src/views/explore/explore-list/index.ts index da6013c..b29f9af 100644 --- a/ui/src/views/explore/explore-list/index.ts +++ b/ui/src/views/explore/explore-list/index.ts @@ -1 +1 @@ -export * from './explore-list-container'; +export * from './explore-list.fragment'; diff --git a/ui/src/views/explore/explore-list/nfa-list/nfa-list.tsx b/ui/src/views/explore/explore-list/nfa-list.fragment.tsx similarity index 61% rename from ui/src/views/explore/explore-list/nfa-list/nfa-list.tsx rename to ui/src/views/explore/explore-list/nfa-list.fragment.tsx index 8d29999..22593cf 100644 --- a/ui/src/views/explore/explore-list/nfa-list/nfa-list.tsx +++ b/ui/src/views/explore/explore-list/nfa-list.fragment.tsx @@ -1,11 +1,12 @@ import { useQuery } from '@apollo/client'; import { useEffect } from 'react'; -import { Flex, NFACard, NFACardSkeleton } from '@/components'; +import { NFACard, NFACardSkeleton } from '@/components'; import { lastNFAsPaginatedDocument } from '@/graphclient'; import { useWindowScrollEnd } from '@/hooks'; -import { Explore } from '../../explore.context'; +import { Explore } from '../explore.context'; +import { NFAListFragmentStyles as S } from './nfa-list.styles'; const pageSize = 10; //Set this size to test pagination @@ -15,6 +16,8 @@ const LoadingSkeletons: React.FC = () => ( + + ); @@ -61,29 +64,16 @@ export const NFAListFragment: React.FC = () => { if (queryError) return
Error
; //TODO handle error return ( - - - {tokens.map((token) => ( - - ))} - {isLoading && } - {!isLoading && tokens.length === 0 && ( - // TODO: update this after designs are done -
- Nothing found. -
- )} -
-
+ + {tokens.map((token) => ( + + ))} + + {isLoading && } + + {!isLoading && tokens.length === 0 && ( + Nothing found. + )} + ); }; diff --git a/ui/src/views/explore/explore-list/nfa-list.styles.ts b/ui/src/views/explore/explore-list/nfa-list.styles.ts new file mode 100644 index 0000000..192ebdf --- /dev/null +++ b/ui/src/views/explore/explore-list/nfa-list.styles.ts @@ -0,0 +1,24 @@ +import { styled } from '@/theme'; + +export const NFAListFragmentStyles = { + Container: styled('div', { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(12.5rem, 1fr))', + alignItems: 'flex-start', + flexWrap: 'wrap', + gap: '$6', + my: '$6', + minHeight: '50vh', + marginBottom: '30vh', // TODO: remove this if we add page footer + + '@media (min-width: 1080px)': { + gridTemplateColumns: 'repeat(auto-fill, minmax(15rem, 1fr))', + }, + }), + EmptyMessage: styled('span', { + padding: '$2 $3 $4 $3', + textAlign: 'center', + color: '$slate11', + width: '100%', + }), +}; diff --git a/ui/src/views/explore/explore-list/nfa-list/index.ts b/ui/src/views/explore/explore-list/nfa-list/index.ts deleted file mode 100644 index 6d5668c..0000000 --- a/ui/src/views/explore/explore-list/nfa-list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './nfa-list'; diff --git a/ui/src/views/explore/explore-list/nfa-search.tsx b/ui/src/views/explore/explore-list/nfa-search.fragment.tsx similarity index 74% rename from ui/src/views/explore/explore-list/nfa-search.tsx rename to ui/src/views/explore/explore-list/nfa-search.fragment.tsx index d68fe85..91b9c82 100644 --- a/ui/src/views/explore/explore-list/nfa-search.tsx +++ b/ui/src/views/explore/explore-list/nfa-search.fragment.tsx @@ -1,16 +1,16 @@ import { useState } from 'react'; -import { Dropdown, DropdownItem, Flex, Input } from '@/components'; +import { Dropdown, DropdownItem, Input } from '@/components'; import { useDebounce } from '@/hooks'; import { Explore } from '../explore.context'; -import { ResultsContainer, ResultsNumber, ResultsText } from './results.styles'; +import { NFASearchFragmentStyles as S } from './nfa-search.styles'; const orderResults: DropdownItem[] = [ { value: 'newest', label: 'Newest' }, { value: 'oldest', label: 'Oldest' }, - { value: 'a-z', label: 'Name A-Z' }, - { value: 'z-a', label: 'Name Z-A' }, + { value: 'a-z', label: 'Sort A-Z' }, + { value: 'z-a', label: 'Sort Z-A' }, ]; export const NFASearchFragment: React.FC = () => { @@ -62,17 +62,18 @@ export const NFASearchFragment: React.FC = () => { }; return ( - - - All NFAs - (3,271) - - + + + All NFAs  + (3,271) + + + { textColor="slate11" optionsWidth="40" /> - - + +
); }; diff --git a/ui/src/views/explore/explore-list/nfa-search.styles.ts b/ui/src/views/explore/explore-list/nfa-search.styles.ts new file mode 100644 index 0000000..a30dcff --- /dev/null +++ b/ui/src/views/explore/explore-list/nfa-search.styles.ts @@ -0,0 +1,40 @@ +import { Flex } from '@/components'; +import { styled } from '@/theme'; + +export const NFASearchFragmentStyles = { + Container: styled(Flex, { + justifyContent: 'space-between', + flexWrap: 'wrap', + gap: '$3', + }), + + Data: { + Wrapper: styled('div', { + fontSize: '$xl', + fontWeight: '$bold', + display: 'flex', + alignItems: 'center', + }), + + Text: styled('span', { + color: '$slate12', + }), + + Number: styled('span', { + color: '$slate11', + }), + }, + + Input: { + Wrapper: styled(Flex, { + gap: '$3', + width: '100%', + maxWidth: '30rem', + justifySelf: 'center', + + button: { + minWidth: '$28', + }, + }), + }, +}; diff --git a/ui/src/views/explore/explore-list/results.styles.ts b/ui/src/views/explore/explore-list/results.styles.ts deleted file mode 100644 index ef48426..0000000 --- a/ui/src/views/explore/explore-list/results.styles.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { styled } from '@/theme'; - -export const ResultsContainer = styled('div', { - fontSize: '$xl', - fontWeight: '$bold', - display: 'flex', - alignItems: 'center', -}); - -export const ResultsText = styled('span', { - color: '$slate12', -}); - -export const ResultsNumber = styled('span', { - color: '$slate11', -}); diff --git a/ui/src/views/explore/explore.styles.ts b/ui/src/views/explore/explore.styles.ts index a539975..08714a1 100644 --- a/ui/src/views/explore/explore.styles.ts +++ b/ui/src/views/explore/explore.styles.ts @@ -4,7 +4,5 @@ import { styled } from '@/theme'; export abstract class Explore { static readonly Container = styled(Flex, { flexDirection: 'column', - width: '64.75rem', //TODO replace for max-width - margin: '0 auto', }); } diff --git a/ui/src/views/explore/explore.tsx b/ui/src/views/explore/explore.tsx index 49451a0..d593b1e 100644 --- a/ui/src/views/explore/explore.tsx +++ b/ui/src/views/explore/explore.tsx @@ -1,12 +1,12 @@ import { Explore as ES } from './explore.styles'; -import { ExploreHeader } from './explore-header'; -import { ExploreListContainer } from './explore-list'; +import { ExploreHeaderFragment } from './explore-header'; +import { ExploreListFragment } from './explore-list'; -export const Explore: React.FC = () => { +export const ExploreView: React.FC = () => { return ( - - + + ); }; diff --git a/ui/src/views/index.ts b/ui/src/views/index.ts index 129b412..acf11b6 100644 --- a/ui/src/views/index.ts +++ b/ui/src/views/index.ts @@ -3,3 +3,4 @@ export * from './mint'; export * from './components-test'; export * from './explore'; export * from './access-point'; +export * from './indexed-nfa'; diff --git a/ui/src/views/indexed-nfa/fragments/aside.fragment.tsx b/ui/src/views/indexed-nfa/fragments/aside.fragment.tsx new file mode 100644 index 0000000..69f07c0 --- /dev/null +++ b/ui/src/views/indexed-nfa/fragments/aside.fragment.tsx @@ -0,0 +1,75 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; +import { Link } from 'react-router-dom'; + +import { Button, Flex, Icon, NFAPreview } from '@/components'; + +import { IndexedNFA } from '../indexed-nfa.context'; +import { IndexedNFAStyles as S } from '../indexed-nfa.styles'; + +const Preview: React.FC = () => { + const { nfa } = IndexedNFA.useContext(); + + const color = useMemo( + // TODO: replace with util function + () => `#${`000000${nfa.color.toString(16)}`.slice(-6)}`, + [nfa] + ); + + return ( + + ); +}; + +const CreateAccessPoint: React.FC = () => { + const { nfa } = IndexedNFA.useContext(); + return ( + + + Host NFA Frontend + + {/* TODO: replace with correct text */} + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vitae + ante erat. Sed quis finibus diam. + + + + + + {/* TODO: place correct href */} + Learn more + + + + + ); +}; + +export const IndexedNFAAsideFragment: React.FC = () => { + const ref = useRef(null); + const [top, setTop] = useState(); + + useEffect(() => { + setTop(ref.current?.getBoundingClientRect().top); + }, [ref]); + + return ( + + + + + ); +}; diff --git a/ui/src/views/indexed-nfa/fragments/index.ts b/ui/src/views/indexed-nfa/fragments/index.ts new file mode 100644 index 0000000..d90e1fb --- /dev/null +++ b/ui/src/views/indexed-nfa/fragments/index.ts @@ -0,0 +1,3 @@ +export * from './aside.fragment'; +export * from './main.fragment'; +export * from './skeleton.fragment'; diff --git a/ui/src/views/indexed-nfa/fragments/main.fragment.tsx b/ui/src/views/indexed-nfa/fragments/main.fragment.tsx new file mode 100644 index 0000000..286273e --- /dev/null +++ b/ui/src/views/indexed-nfa/fragments/main.fragment.tsx @@ -0,0 +1,285 @@ +import React, { useMemo } from 'react'; + +import { Flex, Icon, IconName, ResolvedAddress, Text } from '@/components'; + +import { IndexedNFA } from '../indexed-nfa.context'; +import { IndexedNFAStyles as S } from '../indexed-nfa.styles'; + +type HeaderDataProps = { + label: string; + children: React.ReactNode; +}; + +const HeaderData: React.FC = ({ + label, + children, +}: HeaderDataProps) => ( + + {label} + {children} + +); + +const Header: React.FC = () => { + const { nfa } = IndexedNFA.useContext(); + + return ( + <> + {nfa.name} + + + {nfa.owner.id} + + + + + + {/* TODO: place correct data */} + 12/12/22 + + + + + + {nfa.accessPoints?.length ?? 0} + + + + + ); +}; + +const Description: React.FC = () => { + const { nfa } = IndexedNFA.useContext(); + + return ( + <> + + Description + + + {nfa.description} + + + ); +}; + +type DataWrapperProps = React.PropsWithChildren<{ + label: string | number; +}>; + +const DataWrapper: React.FC = ({ + children, + label, +}: DataWrapperProps) => ( + + {children || '-'} + {label} + +); + +const Traits: React.FC = () => { + const { nfa } = IndexedNFA.useContext(); + + // TODO: place correct data + const traitsToShow = useMemo(() => { + return [ + [nfa.ENS, 'ENS'], + [nfa.gitRepository.id, 'Repository'], + [10, 'Version'], + [nfa.externalURL, 'Domain'], + [nfa.externalURL, 'Domain 2'], + ]; + }, [nfa]); + + return ( + <> + Traits + + {traitsToShow.map(([value, label]) => ( + + {value} + + ))} + + + ); +}; + +type VerificationBannerProps = { + verified: boolean; +}; + +const VerificationBanner: React.FC = ({ + verified, +}: VerificationBannerProps) => { + const [text, icon] = useMemo<[string, IconName]>(() => { + if (verified) + return ['This Non Fungible Application is Verified.', 'verified']; + return ['This Non Fungible Application is not Verified.', 'error']; + }, [verified]); + + return ( + + {text} + + + ); +}; + +const Verification: React.FC = () => { + return ( + <> + Verification + {/* TODO: Get verified from context */} + 0.5} /> + + {/* TODO: place correct data */} + polygon.eth + polygon/fe + + + ); +}; + +// TODO: replace mocks with fetched data +const apMocks = new Array(10).fill(0).map((_, index) => ({ + approved: Math.random() > 0.5, + domain: `domain${index}.com`, + owner: '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049', + createdAt: `${Math.floor(Math.random() * 30)}m ago`, +})); + +const AccessPoints: React.FC = () => { + return ( + <> + Frontends + + + + + + + + + + + + + + + Domain + Owner + Created + + + + + {apMocks.map((item) => ( + + + + + {item.domain} + + {item.owner} + + {item.createdAt} + + + + + ))} + + + + + ); +}; + +// TODO: replace mocks with fetched data +const versionsMock = new Array(10).fill(0).map((_, index) => ({ + live: index === 0, + commit: (Math.random() * 0xfffffffff).toString(16), + preview: `test: subgraph matchstick tests for access points and acl refactor (#150 + ) + + * fix: errors from deprecated entities.`, + time: `${Math.floor(Math.random() * 30)}m ago`, +})); + +const Versions: React.FC = () => { + return ( + <> + Versions + + + + + + + + + + + + + + + Commit + Preview + Time + + + + + {versionsMock.map((item) => ( + + + + {item.live && 'Live'} + + + {item.commit.slice(0, 6)} + + {item.preview} + + {item.time} + + + + + ))} + + + + + ); +}; + +export const IndexedNFAMainFragment: React.FC = () => { + return ( + +
+ + + + + + + ); +}; diff --git a/ui/src/views/indexed-nfa/fragments/skeleton.fragment.tsx b/ui/src/views/indexed-nfa/fragments/skeleton.fragment.tsx new file mode 100644 index 0000000..e1e05a4 --- /dev/null +++ b/ui/src/views/indexed-nfa/fragments/skeleton.fragment.tsx @@ -0,0 +1,16 @@ +import { IndexedNFAStyles as S } from '../indexed-nfa.styles'; + +export const IndexedNFASkeletonFragment: React.FC = () => ( + + + + + + + + + + + + +); diff --git a/ui/src/views/indexed-nfa/index.ts b/ui/src/views/indexed-nfa/index.ts new file mode 100644 index 0000000..c044971 --- /dev/null +++ b/ui/src/views/indexed-nfa/index.ts @@ -0,0 +1 @@ +export * from './indexed-nfa'; diff --git a/ui/src/views/indexed-nfa/indexed-nfa.context.tsx b/ui/src/views/indexed-nfa/indexed-nfa.context.tsx new file mode 100644 index 0000000..fc99dac --- /dev/null +++ b/ui/src/views/indexed-nfa/indexed-nfa.context.tsx @@ -0,0 +1,28 @@ +import { Owner, Token } from '@/graphclient'; +import { createContext } from '@/utils'; + +const [Provider, useContext] = createContext({ + name: 'IndexedNFA.Context', + hookName: 'IndexedNFA.useContext', + providerName: 'IndexedNFA.Provider', +}); + +export const IndexedNFA = { + useContext, + Provider: ({ children, nfa }: IndexedNFA.ProviderProps): JSX.Element => { + return {children}; + }, +}; + +export namespace IndexedNFA { + export type Context = { + nfa: Omit & { + owner: Pick; + }; + }; + + export type ProviderProps = { + children: React.ReactNode | React.ReactNode[]; + nfa: Context['nfa']; + }; +} diff --git a/ui/src/views/indexed-nfa/indexed-nfa.styles.ts b/ui/src/views/indexed-nfa/indexed-nfa.styles.ts new file mode 100644 index 0000000..1e1b2c4 --- /dev/null +++ b/ui/src/views/indexed-nfa/indexed-nfa.styles.ts @@ -0,0 +1,241 @@ +import { Skeleton } from '@/components'; +import { styled } from '@/theme'; + +const Spacing = '$5'; + +export const IndexedNFAStyles = { + Grid: styled('div', { + display: 'grid', + gridTemplateAreas: '"aside main"', + gridTemplateColumns: '24.0625rem 1fr', + gridTemplateRows: 'fit-content', + gap: `calc(2 * ${Spacing})`, + padding: Spacing, + + '@media (max-width: 1080px)': { + gridTemplateColumns: '20rem 1fr', + }, + + '@media (max-width: 580px)': { + gridTemplateAreas: '"aside" "main"', + gridTemplateColumns: '1fr', + }, + }), + + Aside: { + Container: styled('aside', { + gridArea: 'aside', + position: 'sticky', + + display: 'flex', + flexDirection: 'column', + gap: Spacing, + height: 'fit-content', + + '@media (max-width: 580px)': { + position: 'static', + }, + }), + + CreateAccessPoint: { + Container: styled('div', { + display: 'flex', + flexDirection: 'column', + gap: Spacing, + padding: Spacing, + backgroundColor: '$blue1', + borderRadius: '$lg', + }), + Heading: styled('h2', { + fontSize: '$md', + color: '$slate12', + }), + Text: styled('p', { + fontSize: '$sm', + color: '$slate11', + }), + Extra: styled('a', { + display: 'flex', + alignItems: 'center', + color: '$slate11', + fontSize: '$sm', + gap: '$2', + }), + }, + }, + + Main: { + Container: styled('main', { + gridArea: 'main', + display: 'flex', + flexDirection: 'column', + gap: Spacing, + }), + Heading: styled('h1', { + fontSize: '2.125rem', + lineHeight: 1.35, + fontWeight: 700, + }), + SectionHeading: styled('h2', { + fontSize: '$xl', + lineHeight: 1.2, + fontWeight: 700, + marginTop: Spacing, + }), + Divider: { + Line: styled('span', { + width: '100%', + borderBottom: '1px solid $slate6', + }), + Elipse: styled('span', { + width: '0.375rem', + height: '0.375rem', + backgroundColor: '$slate4', + borderRadius: '100%', + }), + }, + Paragraph: styled('p', { + color: '$slate11', + lineHeight: 1.43, + }), + DataContainer: styled('div', { + display: 'flex', + flexDirection: 'column', + border: '1px solid $slate6', + borderRadius: '$lg', + padding: Spacing, + gap: `$1`, + }), + DataList: styled('div', { + display: 'flex', + flexWrap: 'wrap', + gap: '$5', + }), + VerificationBanner: styled('div', { + position: 'relative', + display: 'flex', + alignItems: 'center', + border: '1px solid $slate6', + borderRadius: '$lg', + padding: '$8 $5', + fontWeight: 700, + overflow: 'hidden', + + '&:after': { + content: '""', + position: 'absolute', + right: '-$5', + top: '-$10', + bottom: '-$10', + left: '84%', + borderRadius: '80% 0 0 80%', + }, + + variants: { + verified: { + true: { + borderColor: '$green11', + color: '$green11', + '&:after': { + backgroundColor: '$green11', + }, + }, + false: { + borderColor: '$red11', + color: '$red11', + '&:after': { + backgroundColor: '$red11', + }, + }, + }, + }, + }), + + Table: { + Container: styled('div', { + border: '1px solid $slate6', + borderRadius: '10px', + padding: '0 $5', + + maxHeight: '15.125rem', + overflow: 'auto', + }), + Root: styled('table', { + width: 'calc(100% + 2 * $space$5)', + margin: '0 -$5', + }), + Head: styled('thead', { + position: 'sticky', + top: 0, + backgroundColor: '$black', + + '&:after': { + position: 'absolute', + content: '""', + bottom: 0, + left: 0, + right: 0, + borderBottom: '1px solid $slate6', + }, + }), + Row: styled('tr'), + Data: styled('td', { + padding: '$3', + maxWidth: '10rem', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }), + Body: styled('tbody', { + tr: { + '&:hover': { + backgroundColor: '$slate6', + cursor: 'pointer', + }, + }, + }), + Marker: styled('span', { + display: 'block', + margin: 'auto', + width: '0.5625rem', + height: '0.5625rem', + borderRadius: '$full', + backgroundColor: '$slate6', + + variants: { + variant: { + active: { + backgroundColor: '$green11', + }, + inactive: { + backgroundColor: '$slate8', + }, + }, + text: { + true: { + fontSize: '$xs', + padding: '0 $2', + width: 'fit-content', + height: 'fit-content', + }, + }, + }, + + compoundVariants: [ + { + variant: 'active', + text: true, + css: { + color: '$green11', + backgroundColor: '$green3', + }, + }, + ], + }), + }, + }, + + Skeleton: styled(Skeleton, { + borderRadius: '$lg', + }), +}; diff --git a/ui/src/views/indexed-nfa/indexed-nfa.tsx b/ui/src/views/indexed-nfa/indexed-nfa.tsx new file mode 100644 index 0000000..88cb844 --- /dev/null +++ b/ui/src/views/indexed-nfa/indexed-nfa.tsx @@ -0,0 +1,55 @@ +import { useQuery } from '@apollo/client'; +import { ethers } from 'ethers'; +import { useNavigate, useParams } from 'react-router-dom'; + +import { getNFADocument } from '@/graphclient'; +import { NFAMock } from '@/mocks'; +import { AppLog } from '@/utils'; + +import { + IndexedNFAAsideFragment, + IndexedNFAMainFragment, + IndexedNFASkeletonFragment, +} from './fragments'; +import { IndexedNFA } from './indexed-nfa.context'; +import { IndexedNFAStyles as S } from './indexed-nfa.styles'; + +export const IndexedNFAView: React.FC = () => { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + + const handleError = (error: unknown): void => { + AppLog.errorToast( + `It was not possible to find the NFA with id "${id}"`, + error + ); + navigate('/', { replace: true }); + }; + + const { loading, data = { token: {} } } = useQuery(getNFADocument, { + skip: id === undefined, + variables: { + id: ethers.utils.hexlify(Number(id)), + }, + onCompleted(data) { + if (!data.token) handleError(new Error('Token not found')); + }, + onError(error) { + handleError(error); + }, + }); + + if (loading) { + return ; + } + + // TODO: replace NFAMock with real data from useQuery + return ( + + + + + + + ); +}; diff --git a/ui/src/views/mint/github-step/steps/github-repo-configuration/repo-configuration-body/repo-configuration-body.tsx b/ui/src/views/mint/github-step/steps/github-repo-configuration/repo-configuration-body/repo-configuration-body.tsx index 5943ca7..26569a9 100644 --- a/ui/src/views/mint/github-step/steps/github-repo-configuration/repo-configuration-body/repo-configuration-body.tsx +++ b/ui/src/views/mint/github-step/steps/github-repo-configuration/repo-configuration-body/repo-configuration-body.tsx @@ -1,4 +1,4 @@ -import { Button, Card, Flex, Stepper } from '@/components'; +import { Button, Card, CardTag, Flex, Stepper } from '@/components'; import { Mint } from '@/views/mint/mint.context'; import { useMintFormContext } from '@/views/mint/nfa-step/form-step'; @@ -25,17 +25,8 @@ export const RepoConfigurationBody: React.FC = () => { - Use for NFA - - } + css={{ mb: '0', cursor: 'default' }} + button={Use for NFA} />