Merge pull request #260 from fleekxyz/release/release-v0.0.7
Release v0.0.7 to main
This commit is contained in:
commit
ebffbaff0f
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -104,6 +104,6 @@ describe('Deploy', () => {
|
|||
TestConstants.MintParams.accessPointAutoApprovalSettings,
|
||||
owner.address
|
||||
)
|
||||
).to.be.revertedWithCustomError(implementation, Errors.ContractIsPaused);
|
||||
).to.be.reverted;
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ jspm_packages
|
|||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
yarn-error.log
|
||||
|
||||
# esbuild directories
|
||||
.esbuild
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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<APIGatewayProxyResult> => {
|
||||
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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { handlerPath } from '@libs/handler-resolver';
|
||||
|
||||
export const newMint = {
|
||||
handler: `${handlerPath(__dirname)}/handler.newMint`,
|
||||
events: [
|
||||
{
|
||||
http: {
|
||||
method: 'post',
|
||||
path: 'mint',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -41,7 +41,15 @@ query getLatestNFAs {
|
|||
query getNFA($id: ID!) {
|
||||
token(id: $id) {
|
||||
tokenId
|
||||
owner {
|
||||
id
|
||||
}
|
||||
name
|
||||
description
|
||||
ENS
|
||||
externalURL
|
||||
logo
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
|||
<ToastProvider />
|
||||
<AppPage>
|
||||
<Routes>
|
||||
<Route path="/" element={<Explore />} />
|
||||
<Route path="/" element={<ExploreView />} />
|
||||
<Route path="/mint" element={<Mint />} />
|
||||
<Route path="/create-ap" element={<CreateAP />} />
|
||||
<Route path="/create-ap/:id" element={<CreateAP />} />
|
||||
<Route path="/nfa/:id" element={<IndexedNFAView />} />
|
||||
{/** TODO remove for release */}
|
||||
<Route path="/components-test" element={<ComponentsTest />} />
|
||||
<Route path="*" element={<Navigate to="/" />} />
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './card-tag';
|
||||
|
|
@ -96,7 +96,7 @@ const DropdownButton: React.FC<DropdownButtonProps> = ({
|
|||
{selectedValue && selectedValue.label ? selectedValue.label : 'Select'}
|
||||
</span>
|
||||
<span
|
||||
className={`pointer-events-none absolute inset-y-0 right-0 flex items-center pr-4 ${textColorCss}`}
|
||||
className={`pointer-events-none absolute top-1 bottom-0 right-0 flex items-center pr-4 ${textColorCss}`}
|
||||
>
|
||||
<Icon name="chevron-down" />
|
||||
</span>
|
||||
|
|
@ -172,7 +172,7 @@ export const Dropdown: React.FC<DropdownProps> = ({
|
|||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options
|
||||
className={`absolute mt-1 max-h-32 ${width} right-0 z-10 overflow-auto rounded-xl bg-black px-3 pt-2 border-solid border-slate6 border text-base focus:outline-none sm:text-sm`}
|
||||
className={`absolute mt-1 max-h-36 ${width} right-0 z-10 overflow-auto rounded-xl bg-black px-3 pt-2 border-solid border-slate6 border text-base focus:outline-none sm:text-sm`}
|
||||
>
|
||||
{items.map((option: DropdownItem) => (
|
||||
<DropdownOption key={option.value} option={option} />
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,5 @@ export abstract class InputFileStyles {
|
|||
'&[aria-invalid=true], &[data-invalid]': {
|
||||
borderColor: '$red9',
|
||||
},
|
||||
//TODO add error state
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<ConnectKitButton.Custom>
|
||||
{({ isConnected, show, truncatedAddress, address, ensName }) => {
|
||||
if (ensName && address) setEnsNameStore(ensName, address);
|
||||
|
||||
return (
|
||||
<Button onClick={show}>
|
||||
{isConnected && !!address && !!truncatedAddress ? (
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export abstract class NavBarStyles {
|
|||
backgroundColor: '$black',
|
||||
zIndex: '$sticky',
|
||||
height: '$22',
|
||||
overflow: 'hidden', // TODO: this must be worked on for responsive layout
|
||||
});
|
||||
|
||||
static readonly Content = styled('div', {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ export abstract class PageStyles {
|
|||
public static readonly Content = styled('div', {
|
||||
width: '100%',
|
||||
minHeight: '85vh',
|
||||
maxWidth: '$7xl',
|
||||
maxWidth: '$6xl',
|
||||
padding: '0 $6',
|
||||
margin: '0 auto',
|
||||
display: 'grid',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export const NFACardStyles = {
|
|||
Container: styled(Link, {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '14.6875rem',
|
||||
minWidth: '12.5rem',
|
||||
padding: 0,
|
||||
overflow: 'hidden',
|
||||
cursor: 'pointer',
|
||||
|
|
@ -60,6 +60,11 @@ export const NFACardStyles = {
|
|||
fontSize: '$xl',
|
||||
fontWeight: '$medium',
|
||||
lineHeight: 1.4,
|
||||
|
||||
maxWidth: 'auto',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}),
|
||||
Content: styled('span', {
|
||||
all: 'unset',
|
||||
|
|
@ -79,7 +84,7 @@ export const NFACardStyles = {
|
|||
Skeleton: {
|
||||
Preview: styled(Skeleton, {
|
||||
width: '100%',
|
||||
height: 'calc(14.6875rem - 2px)',
|
||||
aspectRatio: 1,
|
||||
}),
|
||||
|
||||
Title: styled(Skeleton, {
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ export type NFACardProps = Omit<
|
|||
export const NFACard: React.FC<NFACardProps> = forwardStyledRef<
|
||||
HTMLAnchorElement,
|
||||
NFACardProps
|
||||
// TODO: Set default path to NFA page
|
||||
>(({ data, to = `/create-ap/${data.tokenId}`, ...props }, ref) => {
|
||||
>(({ data, to = `/nfa/${data.tokenId}`, ...props }, ref) => {
|
||||
const { name, color, ENS, logo, accessPoints } = data;
|
||||
|
||||
const apCounter = useMemo(() => accessPoints?.length ?? 0, [accessPoints]);
|
||||
|
|
@ -61,8 +60,7 @@ export const NFACard: React.FC<NFACardProps> = forwardStyledRef<
|
|||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{/* TODO: treat names bigger than space in layout when designs are done */}
|
||||
<S.Title>{data.name}</S.Title>
|
||||
<S.Title title={data.name}>{data.name}</S.Title>
|
||||
{/* TODO: set correct value when it gets available on contract side */}
|
||||
<Badge verified={Math.random() > 0.5} />
|
||||
</Flex>
|
||||
|
|
|
|||
|
|
@ -9,19 +9,20 @@ export type ResolvedAddressProps = React.ComponentPropsWithRef<
|
|||
typeof RAS.Container
|
||||
> & {
|
||||
children: string;
|
||||
truncated?: boolean;
|
||||
};
|
||||
|
||||
export const ResolvedAddress = forwardStyledRef<
|
||||
HTMLSpanElement,
|
||||
ResolvedAddressProps
|
||||
>(({ children, ...props }, ref) => {
|
||||
>(({ children, truncated = false, ...props }, ref) => {
|
||||
const [resolvedAddress, loading] = useResolvedAddress(children);
|
||||
|
||||
const text = useMemo(() => {
|
||||
if (!resolvedAddress.endsWith('.eth'))
|
||||
if (!resolvedAddress.endsWith('.eth') && truncated)
|
||||
return `${resolvedAddress.slice(0, 6)}...${resolvedAddress.slice(-4)}`;
|
||||
return resolvedAddress;
|
||||
}, [resolvedAddress]);
|
||||
}, [resolvedAddress, truncated]);
|
||||
|
||||
return (
|
||||
<RAS.Container {...props} ref={ref} data-loading={loading}>
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
export * from './spinner';
|
||||
export * from './spinner-dot';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { SpinnerStyles } from './spinner.styles';
|
||||
|
||||
export const SpinnerDot: React.FC<SpinnerStyles.ContainerProps> = (props) => {
|
||||
return (
|
||||
<SpinnerStyles.Container
|
||||
{...props}
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g>
|
||||
<circle cx="12" cy="2.5" r="1" opacity=".14" fill="#FFFFFF" />
|
||||
<circle cx="16.75" cy="3.77" r="1" opacity=".29" fill="#FFFFFF" />
|
||||
<circle cx="20.23" cy="7.25" r="1" opacity=".43" fill="#FFFFFF" />
|
||||
<circle cx="21.50" cy="12.00" r="1" opacity=".57" fill="#FFFFFF" />
|
||||
<circle cx="20.23" cy="16.75" r="1" opacity=".71" fill="#FFFFFF" />
|
||||
<circle cx="16.75" cy="20.23" r="1" opacity=".86" fill="#FFFFFF" />
|
||||
<circle cx="12" cy="21.5" r="1" fill="#FFFFFF" />
|
||||
</g>
|
||||
</SpinnerStyles.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,12 +1,57 @@
|
|||
import { styled } from '@/theme';
|
||||
import { keyframes, styled } from '@/theme';
|
||||
|
||||
export abstract class SpinnerStyles {
|
||||
static readonly Container = styled('svg', {
|
||||
const DotSpinner = keyframes({
|
||||
'8.3%': {
|
||||
transform: 'rotate(30deg)',
|
||||
},
|
||||
'16.6%': {
|
||||
transform: 'rotate(60deg)',
|
||||
},
|
||||
'25%': {
|
||||
transform: 'rotate(90deg)',
|
||||
},
|
||||
'33.3%': {
|
||||
transform: 'rotate(120deg)',
|
||||
},
|
||||
'41.6%': {
|
||||
transform: 'rotate(150deg)',
|
||||
},
|
||||
'50%': {
|
||||
transform: 'rotate(180deg)',
|
||||
},
|
||||
'58.3%': {
|
||||
transform: 'rotate(210deg)',
|
||||
},
|
||||
'66.6%': {
|
||||
transform: 'rotate(240deg)',
|
||||
},
|
||||
'75%': {
|
||||
transform: 'rotate(270deg)',
|
||||
},
|
||||
'83.3%': {
|
||||
transform: 'rotate(300deg)',
|
||||
},
|
||||
'91.6%': {
|
||||
transform: 'rotate(330deg)',
|
||||
},
|
||||
'100%': {
|
||||
transform: 'rotate(360deg)',
|
||||
},
|
||||
});
|
||||
|
||||
export const SpinnerStyles = {
|
||||
KeyFrames: {},
|
||||
Container: styled('svg', {
|
||||
fontSize: '1.5rem',
|
||||
width: '1em',
|
||||
height: '1em',
|
||||
});
|
||||
}
|
||||
|
||||
g: {
|
||||
transformOrigin: 'center',
|
||||
animation: `${DotSpinner} 0.75s step-end infinite`,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export namespace SpinnerStyles {
|
||||
export type ContainerProps = React.ComponentProps<
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
export const createBunnyCDNMock = async (
|
||||
domain: string,
|
||||
targetDomain: string
|
||||
): Promise<{ bunnyURL: string }> => {
|
||||
return new Promise((resolved, reject) => {
|
||||
setTimeout(() => {
|
||||
resolved({
|
||||
bunnyURL: '8c12c649402442d88b5f.b-cdn.net',
|
||||
});
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
|
||||
export const verifyBunnyCDNMock = async (domain: string): Promise<boolean> => {
|
||||
return new Promise((resolved, reject) => {
|
||||
setTimeout(() => {
|
||||
resolved(true);
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
export * from './mint-site';
|
||||
export * from './detail';
|
||||
export * from './list';
|
||||
export * from './bunny-cdn';
|
||||
export * from './nfa';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
export const NFAMock = {
|
||||
id: '6',
|
||||
tokenId: '6',
|
||||
name: 'Polygon',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sit amet velit dolor. Praesent dapibus euismod molestie. Duis maximus porttitor odio. Duis quis lorem id lacus cursus commodo vel vehicula mauris.',
|
||||
externalURL: 'https://polygon.com',
|
||||
ENS: 'polygon.eth',
|
||||
logo: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzOC40IDMzLjUiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDM4LjQgMzMuNTsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiM4MjQ3RTU7fQo8L3N0eWxlPgo8Zz4KCTxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0yOSwxMC4yYy0wLjctMC40LTEuNi0wLjQtMi40LDBMMjEsMTMuNWwtMy44LDIuMWwtNS41LDMuM2MtMC43LDAuNC0xLjYsMC40LTIuNCwwTDUsMTYuMwoJCWMtMC43LTAuNC0xLjItMS4yLTEuMi0yLjF2LTVjMC0wLjgsMC40LTEuNiwxLjItMi4xbDQuMy0yLjVjMC43LTAuNCwxLjYtMC40LDIuNCwwTDE2LDcuMmMwLjcsMC40LDEuMiwxLjIsMS4yLDIuMXYzLjNsMy44LTIuMlY3CgkJYzAtMC44LTAuNC0xLjYtMS4yLTIuMWwtOC00LjdjLTAuNy0wLjQtMS42LTAuNC0yLjQsMEwxLjIsNUMwLjQsNS40LDAsNi4yLDAsN3Y5LjRjMCwwLjgsMC40LDEuNiwxLjIsMi4xbDguMSw0LjcKCQljMC43LDAuNCwxLjYsMC40LDIuNCwwbDUuNS0zLjJsMy44LTIuMmw1LjUtMy4yYzAuNy0wLjQsMS42LTAuNCwyLjQsMGw0LjMsMi41YzAuNywwLjQsMS4yLDEuMiwxLjIsMi4xdjVjMCwwLjgtMC40LDEuNi0xLjIsMi4xCgkJTDI5LDI4LjhjLTAuNywwLjQtMS42LDAuNC0yLjQsMGwtNC4zLTIuNWMtMC43LTAuNC0xLjItMS4yLTEuMi0yLjFWMjFsLTMuOCwyLjJ2My4zYzAsMC44LDAuNCwxLjYsMS4yLDIuMWw4LjEsNC43CgkJYzAuNywwLjQsMS42LDAuNCwyLjQsMGw4LjEtNC43YzAuNy0wLjQsMS4yLTEuMiwxLjItMi4xVjE3YzAtMC44LTAuNC0xLjYtMS4yLTIuMUwyOSwxMC4yeiIvPgo8L2c+Cjwvc3ZnPgo=',
|
||||
color: 8668388,
|
||||
accessPointAutoApproval: true,
|
||||
owner: {
|
||||
id: '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049',
|
||||
collection: true,
|
||||
},
|
||||
mintedBy: '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049',
|
||||
controllers: [],
|
||||
gitRepository: {
|
||||
id: '',
|
||||
},
|
||||
commitHash: '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049',
|
||||
accessPoints: [],
|
||||
verifier: {
|
||||
id: '0x7ED735b7095C05d78dF169F991f2b7f1A1F1A049',
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
|
||||
import { createBunnyCDNMock } from '@/mocks';
|
||||
import { RootState } from '@/store';
|
||||
import { AppLog } from '@/utils';
|
||||
|
||||
import { bunnyCDNActions } from '../bunny-cdn-slice';
|
||||
|
||||
type CNAMERecord = {
|
||||
domain: string;
|
||||
targetDomain: string;
|
||||
};
|
||||
|
||||
export const createBunnyCDN = createAsyncThunk<void, CNAMERecord>(
|
||||
'BunnyCDN/CreateCDN',
|
||||
async ({ domain, targetDomain }, { dispatch, getState }) => {
|
||||
const { state } = (getState() as RootState).bunnyCDN;
|
||||
|
||||
if (state === 'loading') return;
|
||||
|
||||
try {
|
||||
dispatch(bunnyCDNActions.setState('loading'));
|
||||
|
||||
const CDNRecord = await createBunnyCDNMock(domain, targetDomain);
|
||||
|
||||
dispatch(bunnyCDNActions.setCDNRecordData(CDNRecord.bunnyURL));
|
||||
} catch (error) {
|
||||
AppLog.errorToast(
|
||||
'Failed to create the CDN record. Please, try again',
|
||||
error
|
||||
);
|
||||
dispatch(bunnyCDNActions.setState('failed'));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export * from './create-cdn';
|
||||
export * from './verify-pullzone';
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
|
||||
import { verifyBunnyCDNMock } from '@/mocks';
|
||||
import { RootState } from '@/store';
|
||||
import { AppLog } from '@/utils';
|
||||
|
||||
import { bunnyCDNActions } from '../bunny-cdn-slice';
|
||||
|
||||
export const verifyBunnyPullzone = createAsyncThunk<void, string>(
|
||||
'BunnyCDN/VerifyPullzone',
|
||||
async (domain, { dispatch, getState }): Promise<void> => {
|
||||
const { state } = (getState() as RootState).bunnyCDN;
|
||||
|
||||
if (state === 'loading') return;
|
||||
|
||||
try {
|
||||
dispatch(bunnyCDNActions.setState('loading'));
|
||||
|
||||
const verifyAPState = await verifyBunnyCDNMock(domain);
|
||||
|
||||
if (verifyAPState) dispatch(bunnyCDNActions.setState('success'));
|
||||
else throw new Error('Invalid AP state');
|
||||
} catch (error) {
|
||||
AppLog.errorToast(
|
||||
'There was an error trying to verify the domain. Please, try again',
|
||||
error
|
||||
);
|
||||
dispatch(bunnyCDNActions.setState('failed'));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
import { RootState } from '@/store';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
|
||||
import * as asyncThunk from './async-thunk';
|
||||
|
||||
export namespace BunnyCDNState {
|
||||
export type CreateCDNState =
|
||||
| undefined
|
||||
| 'loading'
|
||||
| 'unferified'
|
||||
| 'failed'
|
||||
| 'success';
|
||||
}
|
||||
|
||||
export interface BunnyCDNState {
|
||||
state: BunnyCDNState.CreateCDNState;
|
||||
bunnyURL: string;
|
||||
}
|
||||
|
||||
const initialState: BunnyCDNState = {
|
||||
state: undefined,
|
||||
bunnyURL: '',
|
||||
};
|
||||
|
||||
export const bunnyCDNSlice = createSlice({
|
||||
name: 'BunnyCDNSlice',
|
||||
initialState,
|
||||
reducers: {
|
||||
setState: (state, action: PayloadAction<BunnyCDNState.CreateCDNState>) => {
|
||||
state.state = action.payload;
|
||||
},
|
||||
setCDNRecordData: (state, action: PayloadAction<string>) => {
|
||||
state.bunnyURL = action.payload;
|
||||
state.state = 'success';
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const bunnyCDNActions = {
|
||||
...bunnyCDNSlice.actions,
|
||||
...asyncThunk,
|
||||
};
|
||||
|
||||
const selectENSState = (state: RootState): BunnyCDNState => state.bunnyCDN;
|
||||
|
||||
export const useBunnyCDNStore = (): BunnyCDNState =>
|
||||
useAppSelector(selectENSState);
|
||||
|
||||
export default bunnyCDNSlice.reducer;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './bunny-cdn-slice';
|
||||
|
|
@ -2,3 +2,4 @@ export * from './fleek-erc721';
|
|||
export * from './github';
|
||||
export * from './toasts';
|
||||
export * from './ens';
|
||||
export * from './bunny-cdn';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { configureStore } from '@reduxjs/toolkit';
|
||||
|
||||
import bunnyCDNReducer from './features/bunny-cdn/bunny-cdn-slice';
|
||||
import ENSReducer from './features/ens/ens-slice';
|
||||
import fleekERC721Reducer from './features/fleek-erc721/fleek-erc721-slice';
|
||||
import githubReducer from './features/github/github-slice';
|
||||
|
|
@ -7,10 +8,11 @@ import toastsReducer from './features/toasts/toasts-slice';
|
|||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
bunnyCDN: bunnyCDNReducer,
|
||||
ENS: ENSReducer,
|
||||
fleekERC721: fleekERC721Reducer,
|
||||
github: githubReducer,
|
||||
toasts: toastsReducer,
|
||||
ENS: ENSReducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Converts a hex color string to a number.
|
||||
*/
|
||||
export const parseColorToNumber = (color: string): number => {
|
||||
const hexColor = color.replace('#', '');
|
||||
return parseInt(hexColor, 16);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts string number to hex color string.
|
||||
*/
|
||||
export const parseNumberToHexColor = (color: number): string => {
|
||||
const hexColor = color.toString(16);
|
||||
return hexColor;
|
||||
};
|
||||
|
|
@ -54,12 +54,23 @@ const hasSpecialCharacters: StringValidator = {
|
|||
message: 'This field has special characters',
|
||||
};
|
||||
|
||||
const isValidDomain: StringValidator = {
|
||||
name: 'isValidDomain',
|
||||
validate: (value = '') => {
|
||||
const regex =
|
||||
/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/;
|
||||
return regex.test(value);
|
||||
},
|
||||
message: 'This field is not a valid domain',
|
||||
};
|
||||
|
||||
export const StringValidators = {
|
||||
required,
|
||||
maxLength,
|
||||
isUrl,
|
||||
maxFileSize,
|
||||
hasSpecialCharacters,
|
||||
isValidDomain,
|
||||
};
|
||||
|
||||
export const hasValidator = <
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
import { useQuery } from '@apollo/client';
|
||||
import { ethers } from 'ethers';
|
||||
import { useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useAccount } from 'wagmi';
|
||||
|
||||
import {
|
||||
Button,
|
||||
CardTag,
|
||||
Flex,
|
||||
Form,
|
||||
Spinner,
|
||||
Stepper,
|
||||
Text,
|
||||
} from '@/components';
|
||||
import { getNFADocument } from '@/graphclient';
|
||||
import { useAppDispatch } from '@/store';
|
||||
import { bunnyCDNActions, useBunnyCDNStore } from '@/store/features/bunny-cdn';
|
||||
import { AppLog } from '@/utils';
|
||||
|
||||
import { CreateAccessPoint } from '../create-ap.context';
|
||||
import { NFAIconFragment } from '../nfa-icon';
|
||||
import { useAccessPointFormContext } from './create-ap.form.context';
|
||||
|
||||
export const SelectedNFA: React.FC = () => {
|
||||
const { nfa } = CreateAccessPoint.useContext();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
css={{
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Flex css={{ alignItems: 'center', maxWidth: '65%' }}>
|
||||
<NFAIconFragment image={nfa.logo} color={nfa.color} />
|
||||
<Text
|
||||
css={{
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{nfa.name}
|
||||
</Text>
|
||||
</Flex>
|
||||
<CardTag css={{ minWidth: '$28' }}>Selected NFA</CardTag>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const CreateAccessPointFormBody: React.FC = () => {
|
||||
const { id } = useParams();
|
||||
const { address } = useAccount();
|
||||
const { nextStep } = Stepper.useContext();
|
||||
const { nfa, setNfa, billing } = CreateAccessPoint.useContext();
|
||||
const { setArgs } = CreateAccessPoint.useTransactionContext();
|
||||
const { state } = useBunnyCDNStore();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const {
|
||||
form: {
|
||||
domain: {
|
||||
value: [domain],
|
||||
},
|
||||
isValid: [isValid],
|
||||
},
|
||||
} = useAccessPointFormContext();
|
||||
|
||||
const {
|
||||
form: { domain: domainContext },
|
||||
} = useAccessPointFormContext();
|
||||
|
||||
const { loading: nfaLoading } = useQuery(getNFADocument, {
|
||||
skip: id === undefined,
|
||||
variables: {
|
||||
id: ethers.utils.hexlify(Number(id)),
|
||||
},
|
||||
onCompleted(data) {
|
||||
if (data.token && id) {
|
||||
const { name, tokenId, logo, color, externalURL: domain } = data.token;
|
||||
setNfa({ name, tokenId, logo, color, domain });
|
||||
} else {
|
||||
AppLog.errorToast("We couldn't find the NFA you are looking for");
|
||||
}
|
||||
},
|
||||
onError(error) {
|
||||
AppLog.errorToast('Error fetching NFA', error);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (state === 'success') {
|
||||
nextStep();
|
||||
dispatch(bunnyCDNActions.setState(undefined));
|
||||
}
|
||||
}, [state, nextStep, dispatch]);
|
||||
|
||||
if (nfaLoading) {
|
||||
return (
|
||||
<Flex
|
||||
css={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '$48',
|
||||
}}
|
||||
>
|
||||
<Spinner />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
const handleContinueClick = (): void => {
|
||||
if (!address) {
|
||||
AppLog.errorToast('No address found. Please connect your wallet.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (nfa && domain) {
|
||||
try {
|
||||
setArgs([Number(nfa.tokenId), domain, { value: billing }]);
|
||||
dispatch(
|
||||
bunnyCDNActions.createBunnyCDN({
|
||||
domain: 'domain',
|
||||
targetDomain: domain,
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
AppLog.errorToast('Error setting transaction arguments');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex css={{ flexDirection: 'column', gap: '$6' }}>
|
||||
<SelectedNFA />
|
||||
<Text css={{ fontSize: '$sm', color: '$slate11' }}>
|
||||
Enter the domain you want to host the NFA. You will need access to the
|
||||
DNS settings in the next step.
|
||||
</Text>
|
||||
<Form.Field context={domainContext}>
|
||||
<Form.Label>Domain</Form.Label>
|
||||
<Form.Input placeholder="mydomain.com" />
|
||||
<Form.Overline />
|
||||
</Form.Field>
|
||||
<Button
|
||||
disabled={!isValid || nfa.tokenId === ''}
|
||||
isLoading={state === 'loading'}
|
||||
colorScheme="blue"
|
||||
variant="solid"
|
||||
onClick={handleContinueClick}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<Card.Container css={{ width: '$107h' }}>
|
||||
<Card.Heading
|
||||
title="Create Access Point"
|
||||
title="Enter Domain"
|
||||
leftIcon={
|
||||
<IconButton
|
||||
aria-label="Add"
|
||||
|
|
@ -29,13 +29,14 @@ export const CreateAccessPointForm: React.FC = () => {
|
|||
}
|
||||
/>
|
||||
<Card.Body>
|
||||
<Grid
|
||||
<Flex
|
||||
css={{
|
||||
rowGap: '$6',
|
||||
flexDirection: 'column',
|
||||
gap: '$6',
|
||||
}}
|
||||
>
|
||||
<CreateAccessPointFormBody />
|
||||
</Grid>
|
||||
</Flex>
|
||||
</Card.Body>
|
||||
</Card.Container>
|
||||
);
|
||||
|
|
@ -5,7 +5,7 @@ import { createContext, StringValidators } from '@/utils';
|
|||
|
||||
export type CreateAccessPointFormContext = {
|
||||
form: {
|
||||
appName: FormField;
|
||||
domain: FormField;
|
||||
isValid: ReactState<boolean>;
|
||||
};
|
||||
};
|
||||
|
|
@ -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),
|
||||
},
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export * from './create-ap.form.context';
|
||||
export * from './create-ap-form';
|
||||
|
|
@ -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 (
|
||||
<Card.Body>
|
||||
{state === 'loading' ? (
|
||||
<Card.Text css={{ p: '$12 $10', gap: '$7' }}>
|
||||
<SpinnerDot css={{ fontSize: '$7xl' }} />
|
||||
<Text css={{ fontSize: '$md' }}>
|
||||
Waiting for DNS propagation, allow a few minutes.
|
||||
</Text>
|
||||
</Card.Text>
|
||||
) : (
|
||||
<Grid
|
||||
css={{
|
||||
rowGap: '$6',
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
{`Create a ${
|
||||
isSudomain ? 'CNAME' : 'ANAME'
|
||||
} record in your DNS provider pointing to our CDN
|
||||
endpoint.`}
|
||||
</Text>
|
||||
<DisplayText
|
||||
label="Record Type"
|
||||
value={isSudomain ? 'CNAME' : 'ANAME'}
|
||||
/>
|
||||
<DisplayText label="Host" value={isSudomain ? 'App' : '@'} />
|
||||
<DisplayText label="Data (Points to)" value={bunnyURL} />
|
||||
<Button
|
||||
colorScheme="blue"
|
||||
variant="solid"
|
||||
onClick={handleContinueClick}
|
||||
>
|
||||
I added the record
|
||||
</Button>
|
||||
</Grid>
|
||||
)}
|
||||
</Card.Body>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { Card, Icon, IconButton, Stepper } from '@/components';
|
||||
|
||||
export const APRecordCardHeader: React.FC = () => {
|
||||
const { prevStep } = Stepper.useContext();
|
||||
|
||||
return (
|
||||
<Card.Heading
|
||||
title="Create Record"
|
||||
leftIcon={
|
||||
<IconButton
|
||||
aria-label="Add"
|
||||
colorScheme="gray"
|
||||
variant="link"
|
||||
icon={<Icon name="back" />}
|
||||
css={{ mr: '$2' }}
|
||||
onClick={prevStep}
|
||||
/>
|
||||
}
|
||||
rightIcon={
|
||||
<IconButton
|
||||
aria-label="Add"
|
||||
colorScheme="gray"
|
||||
variant="link"
|
||||
icon={<Icon name="info" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<Card.Container css={{ width: '$107h' }}>
|
||||
<APRecordCardHeader />
|
||||
<APRecordCardBody />
|
||||
</Card.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './ap-record-step';
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export const isSubdomain = (url: string): boolean => {
|
||||
const urlParts = url.split('.');
|
||||
|
||||
return urlParts.length > 2;
|
||||
};
|
||||
|
|
@ -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 <div>Loading...</div>; //TODO replace with spinner
|
||||
|
||||
return (
|
||||
<>
|
||||
<SelectedNFA />
|
||||
<DisplayText
|
||||
label="Owner"
|
||||
value={
|
||||
address ? (
|
||||
<ResolvedAddress truncated={false}>{address || ''}</ResolvedAddress>
|
||||
) : (
|
||||
'Please connect to wallet'
|
||||
)
|
||||
}
|
||||
/>
|
||||
<DisplayText label="Frontend URL" value={domain} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<Card.Container css={{ width: '$107h' }}>
|
||||
<Card.Heading
|
||||
title={`Create Access Point ${nfa.label || ''}`}
|
||||
title="Review Details"
|
||||
leftIcon={
|
||||
<IconButton
|
||||
aria-label="Add"
|
||||
|
|
@ -90,26 +134,19 @@ export const CreateAccessPointPreview: React.FC = () => {
|
|||
}
|
||||
/>
|
||||
<Card.Body>
|
||||
<Grid
|
||||
css={{
|
||||
rowGap: '$6',
|
||||
}}
|
||||
>
|
||||
<Flex css={{ flexDirection: 'column' }}>
|
||||
<span>NFA: {nfa.value}</span>
|
||||
<span>{appName}</span>
|
||||
<span className="text-slate11 text-sm">{message}</span>
|
||||
</Flex>
|
||||
<Flex css={{ flexDirection: 'column', gap: '$6' }}>
|
||||
<AccessPointDataFragment />
|
||||
<Text>{message}</Text>
|
||||
<Button
|
||||
disabled={!!prepareError || !nfa}
|
||||
isLoading={isLoading}
|
||||
isDisabled={isLoading || !isValid || !address}
|
||||
colorScheme="blue"
|
||||
variant="solid"
|
||||
onClick={write}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</Grid>
|
||||
</Flex>
|
||||
</Card.Body>
|
||||
</Card.Container>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Card.Container css={{ width: '$107h' }}>
|
||||
<Card.Heading
|
||||
title="Hosting Successful"
|
||||
leftIcon={
|
||||
<Icon
|
||||
name="check-circle"
|
||||
css={{ color: '$green11', fontSize: '$xl', mr: '$2' }}
|
||||
/>
|
||||
}
|
||||
rightIcon={
|
||||
<IconButton
|
||||
aria-label="Add"
|
||||
colorScheme="gray"
|
||||
variant="link"
|
||||
icon={<Icon name="info" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Card.Body>
|
||||
<Flex css={{ flexDirection: 'column', gap: '$6' }}>
|
||||
<Text css={{ fontSize: '$sm', color: '$slate11' }}>
|
||||
{`You have successfully hosted a ${nfa.name} frontend on your own domain.`}
|
||||
</Text>
|
||||
<AccessPointDataFragment />
|
||||
<Flex css={{ flexDirection: 'column', gap: '$4' }}>
|
||||
<Button
|
||||
colorScheme="blue"
|
||||
variant="solid"
|
||||
leftIcon={<Icon name="twitter" />}
|
||||
>
|
||||
Tweet about your frontend!
|
||||
</Button>
|
||||
<Button>Manage Frontend</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card.Body>
|
||||
</Card.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -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<Token, 'id' | 'name'>;
|
||||
export type NFA = {
|
||||
tokenId: string;
|
||||
name: string;
|
||||
logo: string;
|
||||
color: number;
|
||||
domain: string;
|
||||
};
|
||||
|
||||
export type AccessPointContext = {
|
||||
billing: string | undefined;
|
||||
nfa: NFA | undefined;
|
||||
setNfa: ReactState<NFA | undefined>[1];
|
||||
nfa: NFA;
|
||||
setNfa: (nfa: NFA) => void;
|
||||
};
|
||||
|
||||
const [CreateAPProvider, useContext] = createContext<AccessPointContext>({
|
||||
|
|
@ -31,7 +38,13 @@ export abstract class CreateAccessPoint {
|
|||
children,
|
||||
}) => {
|
||||
const [billing] = useFleekERC721Billing('AddAccessPoint');
|
||||
const [nfa, setNfa] = useState<NFA>();
|
||||
const [nfa, setNfa] = useState<NFA>({
|
||||
tokenId: '',
|
||||
name: '',
|
||||
logo: '',
|
||||
color: 0,
|
||||
domain: '',
|
||||
});
|
||||
|
||||
const value = {
|
||||
billing,
|
||||
|
|
@ -44,12 +57,10 @@ export abstract class CreateAccessPoint {
|
|||
<TransactionProvider
|
||||
config={{
|
||||
transaction: {
|
||||
onSuccess: (data) => {
|
||||
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'
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Flex
|
||||
css={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '$48',
|
||||
}}
|
||||
>
|
||||
<Spinner />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
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 && <NfaPicker />}
|
||||
<Form.Field context={appNameContext}>
|
||||
<Form.Label>App Name</Form.Label>
|
||||
<Form.Input />
|
||||
</Form.Field>
|
||||
<Button
|
||||
disabled={!isValid}
|
||||
colorScheme="blue"
|
||||
variant="solid"
|
||||
onClick={handleContinueClick}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 <CreateAccessPointSuccess />;
|
||||
|
||||
return (
|
||||
<Stepper.Root initialStep={1}>
|
||||
<Form.Root onValidationChange={setIsValid}>
|
||||
|
|
@ -22,13 +35,23 @@ export const CreateApStepper: React.FC = () => {
|
|||
</Stepper.Step>
|
||||
|
||||
<Stepper.Step>
|
||||
<Step header="Set Access Point">
|
||||
<Step header="Enter the domain you want to host the NFA">
|
||||
<CreateAccessPointForm />
|
||||
</Step>
|
||||
</Stepper.Step>
|
||||
|
||||
<Stepper.Step>
|
||||
<Step header="Create Access Point">
|
||||
<Step
|
||||
header={`Add a ${
|
||||
isSubdomain(accesPointDomain) ? 'CNAME' : 'ANAME'
|
||||
} record to your DNS provider`}
|
||||
>
|
||||
<APRecordStep />
|
||||
</Step>
|
||||
</Stepper.Step>
|
||||
|
||||
<Stepper.Step>
|
||||
<Step header="Review your hosted frontend and confirm">
|
||||
<CreateAccessPointPreview />
|
||||
</Step>
|
||||
</Stepper.Step>
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
};
|
||||
|
|
@ -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<DisplayTextProps> = ({
|
||||
label,
|
||||
value,
|
||||
}: DisplayTextProps) => {
|
||||
return (
|
||||
<S.Container>
|
||||
<S.Label>{label}</S.Label>
|
||||
<S.Input>{value}</S.Input>
|
||||
</S.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './display-text';
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './nfa-icon';
|
||||
|
|
@ -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%',
|
||||
}),
|
||||
};
|
||||
|
|
@ -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<NFAIconProps> = ({
|
||||
image,
|
||||
color,
|
||||
}: NFAIconProps) => {
|
||||
return (
|
||||
<NS.Container
|
||||
css={{ backgroundColor: `#${parseNumberToHexColor(color)}57` }}
|
||||
>
|
||||
<NS.Image src={image} />
|
||||
</NS.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<Combobox
|
||||
isLoading={loading}
|
||||
items={items}
|
||||
selected={[nfa, setNfa]}
|
||||
queryKey={['name', 'id']}
|
||||
>
|
||||
{({ Field, Options }) => (
|
||||
<>
|
||||
<Field>{(selected) => selected?.name || 'Select NFA'}</Field>
|
||||
|
||||
<Options>{(item) => item.name}</Options>
|
||||
</>
|
||||
)}
|
||||
</Combobox>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<Flex css={{ flexDirection: 'column' }}>
|
||||
<SpinnerTest />
|
||||
<ResolvedAddress css={{ alignSelf: 'center' }}>
|
||||
{'0x7ed735b7095c05d78df169f991f2b7f1a1f1a049'}
|
||||
{'0x7ed735b7095c05d78df169f991f2b7f1a1f1a049a'}
|
||||
</ResolvedAddress>
|
||||
<ComboboxTest />
|
||||
<ColorPickerTest />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { Flex, Spinner, SpinnerDot } from '@/components';
|
||||
|
||||
export const SpinnerTest: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Flex css={{ alignItems: 'center' }}>
|
||||
<SpinnerDot css={{ fontSize: '$6xl' }} />
|
||||
<SpinnerDot css={{ fontSize: '$4xl' }} />
|
||||
<SpinnerDot css={{ fontSize: '$lg' }} />
|
||||
</Flex>
|
||||
<Flex css={{ alignItems: 'center' }}>
|
||||
<Spinner css={{ fontSize: '$6xl' }} />
|
||||
<Spinner css={{ fontSize: '$4xl' }} />
|
||||
<Spinner css={{ fontSize: '$lg' }} />
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 = () => (
|
||||
<S.Container>
|
||||
<S.Text>
|
||||
<S.GrayText>
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './explore-header.fragment';
|
||||
|
|
@ -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 (
|
||||
<Flex css={{ flexDirection: 'column' }}>
|
||||
<Explore.Provider>
|
||||
<NFASearchFragment />
|
||||
<NFAListFragment />
|
||||
</Explore.Provider>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<Explore.Provider>
|
||||
<NFASearchFragment />
|
||||
<NFAListFragment />
|
||||
</Explore.Provider>
|
||||
);
|
||||
};
|
||||
|
|
@ -1 +1 @@
|
|||
export * from './explore-list-container';
|
||||
export * from './explore-list.fragment';
|
||||
|
|
|
|||
|
|
@ -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 = () => (
|
|||
<NFACardSkeleton />
|
||||
<NFACardSkeleton />
|
||||
<NFACardSkeleton />
|
||||
<NFACardSkeleton />
|
||||
<NFACardSkeleton />
|
||||
</>
|
||||
);
|
||||
|
||||
|
|
@ -61,29 +64,16 @@ export const NFAListFragment: React.FC = () => {
|
|||
if (queryError) return <div>Error</div>; //TODO handle error
|
||||
|
||||
return (
|
||||
<Flex
|
||||
css={{
|
||||
flexDirection: 'column',
|
||||
gap: '$2',
|
||||
my: '$6',
|
||||
minHeight: '50vh',
|
||||
marginBottom: '30vh', // TODO: remove this if we add page footer
|
||||
}}
|
||||
>
|
||||
<Flex css={{ gap: '$6', flexWrap: 'wrap' }}>
|
||||
{tokens.map((token) => (
|
||||
<NFACard data={token} key={token.id} />
|
||||
))}
|
||||
{isLoading && <LoadingSkeletons />}
|
||||
{!isLoading && tokens.length === 0 && (
|
||||
// TODO: update this after designs are done
|
||||
<div
|
||||
className={`relative cursor-default select-none pt-2 px-3.5 pb-4 text-slate11 text-center`}
|
||||
>
|
||||
Nothing found.
|
||||
</div>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<S.Container>
|
||||
{tokens.map((token) => (
|
||||
<NFACard data={token} key={token.id} />
|
||||
))}
|
||||
|
||||
{isLoading && <LoadingSkeletons />}
|
||||
|
||||
{!isLoading && tokens.length === 0 && (
|
||||
<S.EmptyMessage>Nothing found.</S.EmptyMessage>
|
||||
)}
|
||||
</S.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -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%',
|
||||
}),
|
||||
};
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './nfa-list';
|
||||
|
|
@ -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 (
|
||||
<Flex css={{ justifyContent: 'space-between' }}>
|
||||
<ResultsContainer>
|
||||
<ResultsText>All NFAs </ResultsText>
|
||||
<ResultsNumber>(3,271)</ResultsNumber>
|
||||
</ResultsContainer>
|
||||
<Flex css={{ gap: '$3' }}>
|
||||
<S.Container>
|
||||
<S.Data.Wrapper>
|
||||
<S.Data.Text>All NFAs </S.Data.Text>
|
||||
<S.Data.Number>(3,271)</S.Data.Number>
|
||||
</S.Data.Wrapper>
|
||||
|
||||
<S.Input.Wrapper>
|
||||
<Input
|
||||
placeholder="Search"
|
||||
leftIcon="search"
|
||||
css={{ width: '23rem' }}
|
||||
onChange={handleSearchChange}
|
||||
wrapperClassName="flex-1"
|
||||
/>
|
||||
<Dropdown
|
||||
items={orderResults}
|
||||
|
|
@ -82,7 +83,7 @@ export const NFASearchFragment: React.FC = () => {
|
|||
textColor="slate11"
|
||||
optionsWidth="40"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</S.Input.Wrapper>
|
||||
</S.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -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',
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
|
@ -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',
|
||||
});
|
||||
|
|
@ -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',
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<ES.Container>
|
||||
<ExploreHeader />
|
||||
<ExploreListContainer />
|
||||
<ExploreHeaderFragment />
|
||||
<ExploreListFragment />
|
||||
</ES.Container>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@ export * from './mint';
|
|||
export * from './components-test';
|
||||
export * from './explore';
|
||||
export * from './access-point';
|
||||
export * from './indexed-nfa';
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<NFAPreview
|
||||
color={color}
|
||||
logo={nfa.logo}
|
||||
ens={nfa.ENS}
|
||||
name={nfa.name}
|
||||
size="100%"
|
||||
css={{
|
||||
borderRadius: '$lg',
|
||||
border: '1px solid $slate6',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const CreateAccessPoint: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
return (
|
||||
<S.Aside.CreateAccessPoint.Container>
|
||||
<S.Aside.CreateAccessPoint.Heading>
|
||||
Host NFA Frontend
|
||||
</S.Aside.CreateAccessPoint.Heading>
|
||||
{/* TODO: replace with correct text */}
|
||||
|
||||
<S.Aside.CreateAccessPoint.Text>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vitae
|
||||
ante erat. Sed quis finibus diam.
|
||||
</S.Aside.CreateAccessPoint.Text>
|
||||
|
||||
<Flex css={{ gap: '$3' }}>
|
||||
<Button as={Link} to={`/create-ap/${nfa.tokenId}`} colorScheme="blue">
|
||||
Host NFA Frontend
|
||||
</Button>
|
||||
<S.Aside.CreateAccessPoint.Extra href="">
|
||||
{/* TODO: place correct href */}
|
||||
Learn more
|
||||
<Icon name="chevron-right" />
|
||||
</S.Aside.CreateAccessPoint.Extra>
|
||||
</Flex>
|
||||
</S.Aside.CreateAccessPoint.Container>
|
||||
);
|
||||
};
|
||||
|
||||
export const IndexedNFAAsideFragment: React.FC = () => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [top, setTop] = useState<number>();
|
||||
|
||||
useEffect(() => {
|
||||
setTop(ref.current?.getBoundingClientRect().top);
|
||||
}, [ref]);
|
||||
|
||||
return (
|
||||
<S.Aside.Container ref={ref} css={{ top }}>
|
||||
<Preview />
|
||||
<CreateAccessPoint />
|
||||
</S.Aside.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export * from './aside.fragment';
|
||||
export * from './main.fragment';
|
||||
export * from './skeleton.fragment';
|
||||
|
|
@ -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<HeaderDataProps> = ({
|
||||
label,
|
||||
children,
|
||||
}: HeaderDataProps) => (
|
||||
<Flex css={{ gap: '$2' }}>
|
||||
<Text css={{ color: '$slate11' }}>{label}</Text>
|
||||
<Text css={{ color: '$slate12' }}>{children}</Text>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<S.Main.Heading>{nfa.name}</S.Main.Heading>
|
||||
<Flex css={{ justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<HeaderData label="Owner">
|
||||
<ResolvedAddress>{nfa.owner.id}</ResolvedAddress>
|
||||
</HeaderData>
|
||||
|
||||
<S.Main.Divider.Elipse />
|
||||
|
||||
<HeaderData label="Created">
|
||||
{/* TODO: place correct data */}
|
||||
12/12/22
|
||||
</HeaderData>
|
||||
|
||||
<S.Main.Divider.Elipse />
|
||||
|
||||
<HeaderData label="Access Points">
|
||||
{nfa.accessPoints?.length ?? 0}
|
||||
</HeaderData>
|
||||
</Flex>
|
||||
<S.Main.Divider.Line />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Description: React.FC = () => {
|
||||
const { nfa } = IndexedNFA.useContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<S.Main.SectionHeading css={{ marginTop: 0 }}>
|
||||
Description
|
||||
</S.Main.SectionHeading>
|
||||
<S.Main.DataContainer as={S.Main.Paragraph}>
|
||||
{nfa.description}
|
||||
</S.Main.DataContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type DataWrapperProps = React.PropsWithChildren<{
|
||||
label: string | number;
|
||||
}>;
|
||||
|
||||
const DataWrapper: React.FC<DataWrapperProps> = ({
|
||||
children,
|
||||
label,
|
||||
}: DataWrapperProps) => (
|
||||
<S.Main.DataContainer key={label} css={{ flex: 1, minWidth: '45%' }}>
|
||||
<Text css={{ color: '$slate12', fontWeight: 700 }}>{children || '-'}</Text>
|
||||
<Text css={{ color: '$slate11' }}>{label}</Text>
|
||||
</S.Main.DataContainer>
|
||||
);
|
||||
|
||||
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 (
|
||||
<>
|
||||
<S.Main.SectionHeading>Traits</S.Main.SectionHeading>
|
||||
<S.Main.DataList>
|
||||
{traitsToShow.map(([value, label]) => (
|
||||
<DataWrapper key={label} label={label}>
|
||||
{value}
|
||||
</DataWrapper>
|
||||
))}
|
||||
</S.Main.DataList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type VerificationBannerProps = {
|
||||
verified: boolean;
|
||||
};
|
||||
|
||||
const VerificationBanner: React.FC<VerificationBannerProps> = ({
|
||||
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 (
|
||||
<S.Main.VerificationBanner verified={verified}>
|
||||
{text}
|
||||
<Icon
|
||||
name={icon}
|
||||
css={{
|
||||
fontSize: '3.5rem',
|
||||
color: '$black',
|
||||
position: 'absolute',
|
||||
right: 'calc(8% - 1.75rem)',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
</S.Main.VerificationBanner>
|
||||
);
|
||||
};
|
||||
|
||||
const Verification: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<S.Main.SectionHeading>Verification</S.Main.SectionHeading>
|
||||
{/* TODO: Get verified from context */}
|
||||
<VerificationBanner verified={Math.random() > 0.5} />
|
||||
<S.Main.DataList>
|
||||
{/* TODO: place correct data */}
|
||||
<DataWrapper label="Verifier">polygon.eth</DataWrapper>
|
||||
<DataWrapper label="Repository">polygon/fe</DataWrapper>
|
||||
</S.Main.DataList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// 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 (
|
||||
<>
|
||||
<S.Main.SectionHeading>Frontends</S.Main.SectionHeading>
|
||||
<S.Main.Table.Container>
|
||||
<S.Main.Table.Root>
|
||||
<colgroup>
|
||||
<col span={1} style={{ width: '9.5%' }} />
|
||||
<col span={1} style={{ width: '32.5%' }} />
|
||||
<col span={1} style={{ width: '32.5%' }} />
|
||||
<col span={1} style={{ width: '16%' }} />
|
||||
<col span={1} style={{ width: '9.5%' }} />
|
||||
</colgroup>
|
||||
<S.Main.Table.Head>
|
||||
<S.Main.Table.Row>
|
||||
<S.Main.Table.Data>
|
||||
<S.Main.Table.Marker />
|
||||
</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>Domain</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>Owner</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>Created</S.Main.Table.Data>
|
||||
<S.Main.Table.Data />
|
||||
</S.Main.Table.Row>
|
||||
</S.Main.Table.Head>
|
||||
<S.Main.Table.Body>
|
||||
{apMocks.map((item) => (
|
||||
<S.Main.Table.Row key={item.domain}>
|
||||
<S.Main.Table.Data align="center">
|
||||
<S.Main.Table.Marker
|
||||
variant={item.approved ? 'active' : 'inactive'}
|
||||
/>
|
||||
</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>{item.domain}</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>
|
||||
<ResolvedAddress>{item.owner}</ResolvedAddress>
|
||||
</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>{item.createdAt}</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>
|
||||
<Icon name="external-link" />
|
||||
</S.Main.Table.Data>
|
||||
</S.Main.Table.Row>
|
||||
))}
|
||||
</S.Main.Table.Body>
|
||||
</S.Main.Table.Root>
|
||||
</S.Main.Table.Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// 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 (
|
||||
<>
|
||||
<S.Main.SectionHeading>Versions</S.Main.SectionHeading>
|
||||
<S.Main.Table.Container>
|
||||
<S.Main.Table.Root>
|
||||
<colgroup>
|
||||
<col span={1} style={{ width: '9.5%' }} />
|
||||
<col span={1} style={{ width: '15%' }} />
|
||||
<col span={1} style={{ width: '50%' }} />
|
||||
<col span={1} style={{ width: '16%' }} />
|
||||
<col span={1} style={{ width: '9.5%' }} />
|
||||
</colgroup>
|
||||
<S.Main.Table.Head>
|
||||
<S.Main.Table.Row>
|
||||
<S.Main.Table.Data>
|
||||
<S.Main.Table.Marker />
|
||||
</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>Commit</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>Preview</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>Time</S.Main.Table.Data>
|
||||
<S.Main.Table.Data />
|
||||
</S.Main.Table.Row>
|
||||
</S.Main.Table.Head>
|
||||
<S.Main.Table.Body>
|
||||
{versionsMock.map((item) => (
|
||||
<S.Main.Table.Row key={item.commit}>
|
||||
<S.Main.Table.Data>
|
||||
<S.Main.Table.Marker
|
||||
variant={item.live ? 'active' : 'inactive'}
|
||||
text={item.live}
|
||||
>
|
||||
{item.live && 'Live'}
|
||||
</S.Main.Table.Marker>
|
||||
</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>{item.commit.slice(0, 6)}</S.Main.Table.Data>
|
||||
<S.Main.Table.Data title={item.preview}>
|
||||
{item.preview}
|
||||
</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>{item.time}</S.Main.Table.Data>
|
||||
<S.Main.Table.Data>
|
||||
<Icon name="external-link" />
|
||||
</S.Main.Table.Data>
|
||||
</S.Main.Table.Row>
|
||||
))}
|
||||
</S.Main.Table.Body>
|
||||
</S.Main.Table.Root>
|
||||
</S.Main.Table.Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const IndexedNFAMainFragment: React.FC = () => {
|
||||
return (
|
||||
<S.Main.Container>
|
||||
<Header />
|
||||
<Description />
|
||||
<Traits />
|
||||
<Verification />
|
||||
<AccessPoints />
|
||||
<Versions />
|
||||
</S.Main.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { IndexedNFAStyles as S } from '../indexed-nfa.styles';
|
||||
|
||||
export const IndexedNFASkeletonFragment: React.FC = () => (
|
||||
<S.Grid>
|
||||
<S.Aside.Container>
|
||||
<S.Skeleton css={{ aspectRatio: 1, width: '100%' }} />
|
||||
</S.Aside.Container>
|
||||
<S.Main.Container css={{ justifyContent: 'stretch' }}>
|
||||
<S.Skeleton css={{ height: '2.875rem' }} />
|
||||
<S.Skeleton css={{ height: '1.5rem' }} />
|
||||
<S.Main.Divider.Line />
|
||||
<S.Skeleton css={{ height: '10rem' }} />
|
||||
<S.Skeleton css={{ height: '15rem' }} />
|
||||
</S.Main.Container>
|
||||
</S.Grid>
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './indexed-nfa';
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { Owner, Token } from '@/graphclient';
|
||||
import { createContext } from '@/utils';
|
||||
|
||||
const [Provider, useContext] = createContext<IndexedNFA.Context>({
|
||||
name: 'IndexedNFA.Context',
|
||||
hookName: 'IndexedNFA.useContext',
|
||||
providerName: 'IndexedNFA.Provider',
|
||||
});
|
||||
|
||||
export const IndexedNFA = {
|
||||
useContext,
|
||||
Provider: ({ children, nfa }: IndexedNFA.ProviderProps): JSX.Element => {
|
||||
return <Provider value={{ nfa }}>{children}</Provider>;
|
||||
},
|
||||
};
|
||||
|
||||
export namespace IndexedNFA {
|
||||
export type Context = {
|
||||
nfa: Omit<Token, 'mintTransaction' | 'id' | 'owner'> & {
|
||||
owner: Pick<Owner, 'id'>;
|
||||
};
|
||||
};
|
||||
|
||||
export type ProviderProps = {
|
||||
children: React.ReactNode | React.ReactNode[];
|
||||
nfa: Context['nfa'];
|
||||
};
|
||||
}
|
||||
|
|
@ -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',
|
||||
}),
|
||||
};
|
||||
|
|
@ -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 <IndexedNFASkeletonFragment />;
|
||||
}
|
||||
|
||||
// TODO: replace NFAMock with real data from useQuery
|
||||
return (
|
||||
<IndexedNFA.Provider nfa={{ ...NFAMock, ...data.token }}>
|
||||
<S.Grid>
|
||||
<IndexedNFAAsideFragment />
|
||||
<IndexedNFAMainFragment />
|
||||
</S.Grid>
|
||||
</IndexedNFA.Provider>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 = () => {
|
|||
<Flex css={{ rowGap: '$6', flexDirection: 'column' }}>
|
||||
<RepoRow
|
||||
repo={repositoryName.name}
|
||||
css={{ mb: '0' }}
|
||||
button={
|
||||
<Button
|
||||
colorScheme="gray"
|
||||
disabled
|
||||
variant="outline"
|
||||
css={{ py: '$1', height: '$5', borderRadius: '$md' }}
|
||||
>
|
||||
Use for NFA
|
||||
</Button>
|
||||
}
|
||||
css={{ mb: '0', cursor: 'default' }}
|
||||
button={<CardTag>Use for NFA</CardTag>}
|
||||
/>
|
||||
<RepoBranchCommitFields />
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export const Repository: React.FC<RepositoryProps> = ({
|
|||
<RepoRow
|
||||
onClick={handleSelectRepo}
|
||||
repo={repository.name}
|
||||
css={{ cursor: 'pointer' }}
|
||||
button={
|
||||
<Button
|
||||
colorScheme="blue"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ export const RepoRow = forwardRef<HTMLDivElement, RepoRowProps>(
|
|||
justifyContent: 'space-between',
|
||||
my: '$4',
|
||||
...props.css,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<Flex css={{ alignItems: 'center' }}>
|
||||
|
|
|
|||
|
|
@ -18,39 +18,37 @@ export const MintStepper: React.FC = () => {
|
|||
},
|
||||
} = useMintFormContext();
|
||||
|
||||
if (!isSuccess) {
|
||||
return (
|
||||
<Stepper.Root initialStep={1}>
|
||||
<Form.Root onValidationChange={setIsValid}>
|
||||
<Stepper.Container>
|
||||
<Stepper.Step>
|
||||
<Step header="Connect your Ethereum Wallet to mint an NFA">
|
||||
<WalletStep />
|
||||
</Step>
|
||||
</Stepper.Step>
|
||||
if (isSuccess) return <NftMinted />;
|
||||
|
||||
<Stepper.Step>
|
||||
<Step header="Connect GitHub and select repository">
|
||||
<GithubStep />
|
||||
</Step>
|
||||
</Stepper.Step>
|
||||
return (
|
||||
<Stepper.Root initialStep={1}>
|
||||
<Form.Root onValidationChange={setIsValid}>
|
||||
<Stepper.Container>
|
||||
<Stepper.Step>
|
||||
<Step header="Connect your Ethereum Wallet to mint an NFA">
|
||||
<WalletStep />
|
||||
</Step>
|
||||
</Stepper.Step>
|
||||
|
||||
<Stepper.Step>
|
||||
<Step header="Finalize a few key things for your NFA">
|
||||
<NFAStep />
|
||||
</Step>
|
||||
</Stepper.Step>
|
||||
<Stepper.Step>
|
||||
<Step header="Connect GitHub and select repository">
|
||||
<GithubStep />
|
||||
</Step>
|
||||
</Stepper.Step>
|
||||
|
||||
<Stepper.Step>
|
||||
<Step header="Review your NFA and mint it on Ethereum">
|
||||
<MintPreview />
|
||||
</Step>
|
||||
</Stepper.Step>
|
||||
</Stepper.Container>
|
||||
</Form.Root>
|
||||
</Stepper.Root>
|
||||
);
|
||||
} else {
|
||||
return <NftMinted />;
|
||||
}
|
||||
<Stepper.Step>
|
||||
<Step header="Finalize a few key things for your NFA">
|
||||
<NFAStep />
|
||||
</Step>
|
||||
</Stepper.Step>
|
||||
|
||||
<Stepper.Step>
|
||||
<Step header="Review your NFA and mint it on Ethereum">
|
||||
<MintPreview />
|
||||
</Step>
|
||||
</Stepper.Step>
|
||||
</Stepper.Container>
|
||||
</Form.Root>
|
||||
</Stepper.Root>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,11 +8,3 @@ export const fileToBase64 = (file: File): Promise<string> =>
|
|||
reader.onload = () => resolve(reader.result?.toString() || '');
|
||||
reader.onerror = reject;
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts a hex color string to a number.
|
||||
*/
|
||||
export const parseColorToNumber = (color: string): number => {
|
||||
const hexColor = color.replace('#', '');
|
||||
return parseInt(hexColor, 16);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useAccount } from 'wagmi';
|
|||
|
||||
import { Button, Card, Grid, Stepper } from '@/components';
|
||||
import { AppLog } from '@/utils';
|
||||
import { parseColorToNumber } from '@/utils/color';
|
||||
|
||||
import { Mint } from '../../mint.context';
|
||||
import { MintCardHeader } from '../../mint-card';
|
||||
|
|
@ -11,7 +12,6 @@ import {
|
|||
EnsDomainField,
|
||||
LogoField,
|
||||
} from './fields';
|
||||
import { parseColorToNumber } from './form.utils';
|
||||
import { useMintFormContext } from './mint-form.context';
|
||||
|
||||
export const MintFormStep: React.FC = () => {
|
||||
|
|
@ -54,7 +54,7 @@ export const MintFormStep: React.FC = () => {
|
|||
AppLog.errorToast('No address found. Please connect your wallet.');
|
||||
return;
|
||||
}
|
||||
// setting the args otherwise mint may fail
|
||||
|
||||
setArgs([
|
||||
address,
|
||||
appName,
|
||||
|
|
@ -62,7 +62,7 @@ export const MintFormStep: React.FC = () => {
|
|||
domainURL,
|
||||
ens,
|
||||
gitCommit,
|
||||
`${repositoryName.url}/tree/${gitBranch}`,
|
||||
`${repositoryName?.url}/tree/${gitBranch}`,
|
||||
appLogo,
|
||||
parseColorToNumber(logoColor),
|
||||
verifyNFA,
|
||||
|
|
|
|||
Loading…
Reference in New Issue