Merge pull request #258 from fleekxyz/release/release-v0.0.8
Release: release v0.0.8 to main
This commit is contained in:
commit
c32d4d5771
44
README.md
44
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# Fleek Non Fungible Apps
|
||||
# Fleek Non-Fungible Apps
|
||||
|
||||
**The repository for Fleek Non Fungible Apps project**
|
||||
**The repository for Fleek Non-Fungible Apps project**
|
||||
|
||||
> 🚧 IMPORTANT 🚧 - This initiative is under development, so this repo should be treated as a WIP. The goals and the roadmap might change as the project is shaped.
|
||||
|
||||
|
|
@ -21,44 +21,48 @@ You can find the wiki [here](https://github.com/fleekxyz/non-fungible-apps/wiki)
|
|||
### 📁 Project Structure
|
||||
|
||||
Inside the root folder you are going to find:
|
||||
- [contracts](./contracts): all the developed contracts
|
||||
- [contracts](./contracts): All the developed contracts
|
||||
- [subgraph](./subgraph): The Graph project related code
|
||||
- [ui](./ui): a web application to interact with deployed contracts
|
||||
- [serverless](./serverless): The serverless and Mongo/Prisma set-up
|
||||
- [ui](./ui): A web application to interact with deployed contracts
|
||||
|
||||
You can see breakdowns of other folders in the README within those folders.
|
||||
|
||||
### Contracts
|
||||
|
||||
Within the project is the contracts folder which houses the contracts, utils, tests and deployment scripts associated with the Solidity smart contracts. Check the [contracts readme](./contracts/README.md) for more info.
|
||||
|
||||
|
||||
### 🖥️ User Interface
|
||||
|
||||
Within the project is included a [React](https://reactjs.org/) web application to expose and test the interaction with deployed scripts. Check the [UI readme](./ui/README.md) for more info.
|
||||
Within the project is the contracts folder which houses the contracts, utils, tests, and deployment scripts associated with the Solidity smart contracts. Check the [contracts readme](./contracts/README.md) for more info.
|
||||
|
||||
### Subgraph
|
||||
|
||||
In order to index data offchain, we use TheGraph and this section is the code required for our subgraph. Check the [subgraph readme](./subgraph/README.md) for more info.
|
||||
To index data off-chain, we use TheGraph and this section is the code required for our subgraph. Check the [subgraph readme](./subgraph/README.md) for more info.
|
||||
|
||||
### Serverless
|
||||
|
||||
For verification purposes and our off-chain stack, we are using a MongoDB instance integrated with Prisma and serverless handlers. Check the [serverless readme](./serverless/README.md) for more info.
|
||||
|
||||
### User Interface (UI)
|
||||
|
||||
Within the project is included a [React](https://reactjs.org/) web application to expose and test the interaction with deployed scripts. Check the [UI readme](./ui/README.md) for more info.
|
||||
|
||||
### 💅 Code Styling
|
||||
|
||||
For code formatting we are using [Prettier](https://prettier.io/) and following the [styling guide from Solidity documentation](https://docs.soliditylang.org/en/v0.8.16/style-guide.html). For formatting the code you are able to run:
|
||||
For code formatting, we are using [Prettier](https://prettier.io/) and following the [styling guide from Solidity documentation](https://docs.soliditylang.org/en/v0.8.16/style-guide.html). For formatting the code you can run:
|
||||
|
||||
```
|
||||
$ yarn format
|
||||
```
|
||||
|
||||
> ⚠️ Please make sure you are following the code styling guid before pushing code
|
||||
> ⚠️ Please make sure you are following the code styling guide before pushing the code
|
||||
|
||||
## 🛣️ Roadmap
|
||||
|
||||
Our goal is to reach a point where trustable Solidity contracts can be used for identifying properly the data about web3 applications. Within that goal, we want to also provide ways for users to organize and list information about their application. To get at this we are currently starting with:
|
||||
Our goal is to reach a point where trustable Solidity contracts can be used for identifying properly the data about web3 applications. Within that goal, we want to also provide ways for users to organize and list information about their applications. To get at this we are currently starting with:
|
||||
|
||||
- Define trustable and extendable smart contracts and standards
|
||||
- Prove how the concept would be applicable using static sites
|
||||
- Prove community hosted apps via these contracts
|
||||
- Prove how the concept would be applied using static sites
|
||||
- Prove community-hosted apps via these contracts
|
||||
|
||||
Later on, when the initiative prove its value, a service will be added to Fleek's platform in a friendly way for anyone be able to get their applications onboard.
|
||||
Later on, when the initiative proves its value, a service will be added to Fleek's platform in a friendly way for anyone to be able to get their applications onboard.
|
||||
|
||||
## 💡 Proof of concept
|
||||
|
||||
|
|
@ -66,7 +70,7 @@ The proof of concept was concluded last year and you can reach more information
|
|||
|
||||
## 📚 Dependency Highlights
|
||||
|
||||
We use the following libraries to develop Fleek Non Fungible Apps
|
||||
We use the following libraries to develop Fleek Non-Fungible Apps
|
||||
|
||||
- [Eslint](https://eslint.org/) + [Prettier](https://prettier.io/)
|
||||
- [Ethers](https://docs.ethers.io/v5/)
|
||||
|
|
@ -79,11 +83,11 @@ We use the following libraries to develop Fleek Non Fungible Apps
|
|||
|
||||
## 🙏 Contributing
|
||||
|
||||
This is an open source initiative! Any new idea is welcome, if you want to help us to improve the project please checkout [the contributing guide](/CONTRIBUTING.md).
|
||||
This is an open-source initiative! Any new idea is welcome, if you want to help us to improve the project please check out [the contributing guide](/CONTRIBUTING.md).
|
||||
|
||||
## 📜 License
|
||||
|
||||
Fleek Non Fungible Apps is released under the [MIT License](LICENSE).
|
||||
Fleek Non-Fungible Apps is released under the [MIT License](LICENSE).
|
||||
|
||||
## 🐛 Bug reporting
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ contract FleekERC721 is
|
|||
string ENS,
|
||||
string commitHash,
|
||||
string gitRepository,
|
||||
string ipfsHash,
|
||||
string logo,
|
||||
uint24 color,
|
||||
bool accessPointAutoApproval,
|
||||
|
|
@ -108,6 +109,7 @@ contract FleekERC721 is
|
|||
string calldata ens,
|
||||
string memory commitHash,
|
||||
string memory gitRepository,
|
||||
string memory ipfsHash,
|
||||
string memory logo,
|
||||
uint24 color,
|
||||
bool accessPointAutoApproval,
|
||||
|
|
@ -131,7 +133,7 @@ contract FleekERC721 is
|
|||
|
||||
// The mint interaction is considered to be the first build of the site. Updates from now on all increment the currentBuild by one and update the mapping.
|
||||
app.currentBuild = 0;
|
||||
app.builds[0] = Build(commitHash, gitRepository);
|
||||
app.builds[0] = Build(commitHash, gitRepository, ipfsHash, externalURL);
|
||||
|
||||
emit NewMint(
|
||||
tokenId,
|
||||
|
|
@ -141,6 +143,7 @@ contract FleekERC721 is
|
|||
ens,
|
||||
commitHash,
|
||||
gitRepository,
|
||||
ipfsHash,
|
||||
logo,
|
||||
color,
|
||||
accessPointAutoApproval,
|
||||
|
|
@ -396,11 +399,14 @@ contract FleekERC721 is
|
|||
function setTokenBuild(
|
||||
uint256 tokenId,
|
||||
string memory _commitHash,
|
||||
string memory _gitRepository
|
||||
string memory _gitRepository,
|
||||
string memory _ipfsHash,
|
||||
string memory _domain
|
||||
) public virtual requireTokenRole(tokenId, TokenRoles.Controller) {
|
||||
_requireMinted(tokenId);
|
||||
_apps[tokenId].builds[++_apps[tokenId].currentBuild] = Build(_commitHash, _gitRepository);
|
||||
emit MetadataUpdate(tokenId, "build", [_commitHash, _gitRepository], msg.sender);
|
||||
_apps[tokenId].builds[++_apps[tokenId].currentBuild] = Build(_commitHash, _gitRepository, _ipfsHash, _domain);
|
||||
// Note from Nima: should we update the externalURL field with each new domain?
|
||||
emit MetadataUpdate(tokenId, "build", [_commitHash, _gitRepository, _ipfsHash, _domain], msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ interface IERCX {
|
|||
*/
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, string value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, uint24 value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, string[2] value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, string[4] value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, bool value, address indexed triggeredBy);
|
||||
|
||||
/**
|
||||
|
|
@ -33,6 +33,8 @@ interface IERCX {
|
|||
struct Build {
|
||||
string commitHash;
|
||||
string gitRepository;
|
||||
string ipfsHash;
|
||||
string domain;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -84,7 +86,13 @@ interface IERCX {
|
|||
/**
|
||||
* @dev Sets a minted token's build.
|
||||
*/
|
||||
function setTokenBuild(uint256 tokenId, string memory commitHash, string memory gitRepository) external;
|
||||
function setTokenBuild(
|
||||
uint256 tokenId,
|
||||
string memory commitHash,
|
||||
string memory gitRepository,
|
||||
string memory ipfsHash,
|
||||
string memory domain
|
||||
) external;
|
||||
|
||||
/**
|
||||
* @dev Returns the token metadata for a given tokenId.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const libraryDeployment = async (hre) => {
|
|||
const libContract = await hre.ethers.getContractFactory(lib);
|
||||
const libInstance = await libContract.deploy();
|
||||
await libInstance.deployed();
|
||||
deployStore(hre.network.name, lib, libInstance);
|
||||
await deployStore(hre.network.name, lib, libInstance, false);
|
||||
console.log(`Library "${lib}" deployed at ${libInstance.address}`);
|
||||
libraries[lib] = libInstance.address;
|
||||
}
|
||||
|
|
@ -70,7 +70,6 @@ module.exports = async (taskArgs, hre) => {
|
|||
`Contract ${CONTRACT_NAME} upgraded at "${deployResult.address}" by account "${deployResult.signer.address}"`
|
||||
);
|
||||
console.log('\x1b[0m');
|
||||
await deployStore(network, CONTRACT_NAME, deployResult);
|
||||
} catch (e) {
|
||||
if (
|
||||
e.message === 'new-proxy-instance' ||
|
||||
|
|
|
|||
|
|
@ -33,22 +33,16 @@ const getBuildData = async (contractName) => {
|
|||
};
|
||||
};
|
||||
|
||||
const deployStore = async (network, contractName, contract) => {
|
||||
const deployStore = async (network, contractName, contract, isProxy = true) => {
|
||||
const filePath = getDeployFilePath(network, contractName);
|
||||
|
||||
const { buildId, solcInput, abi, bytecode, metadata, storageLayout } =
|
||||
await getBuildData(contractName);
|
||||
|
||||
const implementationAddress = await getImplementationAddress(
|
||||
hre.network.provider,
|
||||
contract.address
|
||||
);
|
||||
|
||||
const data = {
|
||||
buildId,
|
||||
timestamp: new Date().toLocaleString('en-US'),
|
||||
address: contract.address,
|
||||
implementationAddress,
|
||||
transactionHash: contract.deployTransaction.hash,
|
||||
args: contract.deployTransaction.args,
|
||||
gasPrice: contract.deployTransaction.gasPrice.toNumber(),
|
||||
|
|
@ -58,6 +52,14 @@ const deployStore = async (network, contractName, contract) => {
|
|||
storageLayout,
|
||||
};
|
||||
|
||||
if (isProxy) {
|
||||
const implementationAddress = await getImplementationAddress(
|
||||
hre.network.provider,
|
||||
contract.address
|
||||
);
|
||||
data.implementationAddress = implementationAddress;
|
||||
}
|
||||
|
||||
try {
|
||||
const solcInputsFilePath =
|
||||
filePath.split('/').slice(0, -1).join('/') +
|
||||
|
|
|
|||
|
|
@ -369,29 +369,31 @@ contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC7
|
|||
function test_setTokenBuild() public {
|
||||
string memory commitHash = "commitHash";
|
||||
string memory gitRepository = "gitRepository";
|
||||
string memory ipfsHash = "ipfsHash";
|
||||
string memory domain = "domain";
|
||||
|
||||
// ColletionOwner
|
||||
vm.prank(collectionOwner);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository, ipfsHash, domain);
|
||||
|
||||
// CollectionVerifier
|
||||
vm.prank(collectionVerifier);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository, ipfsHash, domain);
|
||||
|
||||
// TokenOwner
|
||||
vm.prank(tokenOwner);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository, ipfsHash, domain);
|
||||
|
||||
// TokenController
|
||||
vm.prank(tokenController);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository, ipfsHash, domain);
|
||||
|
||||
// AnyAddress
|
||||
vm.prank(anyAddress);
|
||||
expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository);
|
||||
CuT.setTokenBuild(tokenId, commitHash, gitRepository, ipfsHash, domain);
|
||||
}
|
||||
|
||||
function test_burn() public {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ contract Test_FleekERC721_Billing is Test_FleekERC721_Base, Test_FleekERC721_Bil
|
|||
TestConstants.APP_ENS,
|
||||
TestConstants.APP_COMMIT_HASH,
|
||||
TestConstants.APP_GIT_REPOSITORY,
|
||||
TestConstants.APP_IPFS_HASH,
|
||||
TestConstants.LOGO_0,
|
||||
TestConstants.APP_COLOR,
|
||||
TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS,
|
||||
|
|
@ -75,6 +76,7 @@ contract Test_FleekERC721_Billing is Test_FleekERC721_Base, Test_FleekERC721_Bil
|
|||
TestConstants.APP_ENS,
|
||||
TestConstants.APP_COMMIT_HASH,
|
||||
TestConstants.APP_GIT_REPOSITORY,
|
||||
TestConstants.APP_IPFS_HASH,
|
||||
TestConstants.LOGO_0,
|
||||
TestConstants.APP_COLOR,
|
||||
TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS,
|
||||
|
|
@ -98,6 +100,7 @@ contract Test_FleekERC721_Billing is Test_FleekERC721_Base, Test_FleekERC721_Bil
|
|||
TestConstants.APP_ENS,
|
||||
TestConstants.APP_COMMIT_HASH,
|
||||
TestConstants.APP_GIT_REPOSITORY,
|
||||
TestConstants.APP_IPFS_HASH,
|
||||
TestConstants.LOGO_0,
|
||||
TestConstants.APP_COLOR,
|
||||
TestConstants.APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ library TestConstants {
|
|||
|
||||
string public constant APP_GIT_REPOSITORY = "https://github.com/fleekxyz/non-fungible-apps";
|
||||
|
||||
string public constant APP_IPFS_HASH = "mtwirsqawjuoloq2gvtyug2tc3jbf5htm2zeo4rsknfiv3fdp46a";
|
||||
|
||||
uint24 public constant APP_COLOR = 0x123456;
|
||||
|
||||
bool public constant APP_ACCESS_POINT_AUTO_APPROVAL_SETTINGS = true;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ contract Test_FleekERC721_GetToken is Test_FleekERC721_Base {
|
|||
string memory newENS,
|
||||
string memory newCommitHash,
|
||||
string memory newRepository,
|
||||
string memory newIpfsHash,
|
||||
string memory newDomain,
|
||||
string memory newLogo,
|
||||
uint24 newColor
|
||||
) public {
|
||||
|
|
@ -47,7 +49,7 @@ contract Test_FleekERC721_GetToken is Test_FleekERC721_Base {
|
|||
CuT.setTokenExternalURL(tokenId, newExternalURL);
|
||||
transferENS(newENS, deployer);
|
||||
CuT.setTokenENS(tokenId, newENS);
|
||||
CuT.setTokenBuild(tokenId, newCommitHash, newRepository);
|
||||
CuT.setTokenBuild(tokenId, newCommitHash, newRepository, newIpfsHash, newDomain);
|
||||
CuT.setTokenLogoAndColor(tokenId, newLogo, newColor);
|
||||
|
||||
(
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
|
|||
"fleek.eth",
|
||||
"94e8ba38568aea4fb277a37a4c472d94a6ce880a",
|
||||
"https://github.com/a-different/repository",
|
||||
"mtwirsqawjuoloq2gvtyug2tc3jbf5htm2zeo4rsknfiv3fdp46a",
|
||||
TestConstants.LOGO_1,
|
||||
0x654321,
|
||||
false,
|
||||
|
|
@ -56,6 +57,7 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
|
|||
"fleek.eth",
|
||||
"94e8ba38568aea4fb277a37a4c472d94a6ce880a",
|
||||
"https://github.com/a-different/repository",
|
||||
"mtwirsqawjuoloq2gvtyug2tc3jbf5htm2zeo4rsknfiv3fdp46a",
|
||||
TestConstants.LOGO_1,
|
||||
0x654321,
|
||||
true,
|
||||
|
|
@ -81,6 +83,7 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
|
|||
string memory ens,
|
||||
string memory commitHash,
|
||||
string memory gitRepository,
|
||||
string memory ipfsHash,
|
||||
string memory logo,
|
||||
uint24 color,
|
||||
bool autoApprovalAp
|
||||
|
|
@ -95,6 +98,7 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
|
|||
ens,
|
||||
commitHash,
|
||||
gitRepository,
|
||||
ipfsHash,
|
||||
logo,
|
||||
color,
|
||||
autoApprovalAp,
|
||||
|
|
@ -115,6 +119,7 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
|
|||
TestConstants.APP_ENS,
|
||||
TestConstants.APP_COMMIT_HASH,
|
||||
TestConstants.APP_GIT_REPOSITORY,
|
||||
TestConstants.APP_IPFS_HASH,
|
||||
TestConstants.LOGO_0,
|
||||
TestConstants.APP_COLOR,
|
||||
false,
|
||||
|
|
@ -131,6 +136,7 @@ contract Test_FleekERC721_Mint is Test_FleekERC721_Base {
|
|||
"",
|
||||
TestConstants.APP_COMMIT_HASH,
|
||||
TestConstants.APP_GIT_REPOSITORY,
|
||||
TestConstants.APP_IPFS_HASH,
|
||||
TestConstants.LOGO_0,
|
||||
TestConstants.APP_COLOR,
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ abstract contract Test_FleekERC721_Base is Test, Test_FleekERC721_Assertions {
|
|||
TestConstants.APP_ENS,
|
||||
TestConstants.APP_COMMIT_HASH,
|
||||
TestConstants.APP_GIT_REPOSITORY,
|
||||
TestConstants.APP_IPFS_HASH,
|
||||
TestConstants.LOGO_0,
|
||||
TestConstants.APP_COLOR,
|
||||
false, // Auto Approval Is OFF
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import "./TestBase.sol";
|
|||
|
||||
contract Test_FleekERC721_TokenURIAssertions is Test {
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, string value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, string[2] value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, string[4] value, address indexed triggeredBy);
|
||||
event MetadataUpdate(uint256 indexed _tokenId, string key, uint24 value, address indexed triggeredBy);
|
||||
|
||||
function expectMetadataUpdate(
|
||||
|
|
@ -22,7 +22,7 @@ contract Test_FleekERC721_TokenURIAssertions is Test {
|
|||
function expectMetadataUpdate(
|
||||
uint256 _tokenId,
|
||||
string memory key,
|
||||
string[2] memory value,
|
||||
string[4] memory value,
|
||||
address triggeredBy
|
||||
) public {
|
||||
vm.expectEmit(true, true, true, true);
|
||||
|
|
@ -57,7 +57,13 @@ contract Test_FleekERC721_TokenURI is Test_FleekERC721_Base, Test_FleekERC721_To
|
|||
CuT.setTokenExternalURL(tokenId, "https://new-url.com");
|
||||
transferENS("new-ens.eth", deployer);
|
||||
CuT.setTokenENS(tokenId, "new-ens.eth");
|
||||
CuT.setTokenBuild(tokenId, "ce1a3fc141e29f8e1d00a654e156c4982d7711bf", "https://github.com/other/repo");
|
||||
CuT.setTokenBuild(
|
||||
tokenId,
|
||||
"ce1a3fc141e29f8e1d00a654e156c4982d7711bf",
|
||||
"https://github.com/other/repo",
|
||||
"ipfsHash",
|
||||
"domain"
|
||||
);
|
||||
CuT.setTokenLogoAndColor(tokenId, TestConstants.LOGO_1, 0x654321);
|
||||
CuT.setTokenVerified(tokenId, true);
|
||||
|
||||
|
|
@ -96,10 +102,16 @@ contract Test_FleekERC721_TokenURI is Test_FleekERC721_Base, Test_FleekERC721_To
|
|||
expectMetadataUpdate(
|
||||
tokenId,
|
||||
"build",
|
||||
["ce1a3fc141e29f8e1d00a654e156c4982d7711bf", "https://github.com/other/repo"],
|
||||
["ce1a3fc141e29f8e1d00a654e156c4982d7711bf", "https://github.com/other/repo", "ipfshash", "domain"],
|
||||
deployer
|
||||
);
|
||||
CuT.setTokenBuild(tokenId, "ce1a3fc141e29f8e1d00a654e156c4982d7711bf", "https://github.com/other/repo");
|
||||
CuT.setTokenBuild(
|
||||
tokenId,
|
||||
"ce1a3fc141e29f8e1d00a654e156c4982d7711bf",
|
||||
"https://github.com/other/repo",
|
||||
"ipfshash",
|
||||
"domain"
|
||||
);
|
||||
|
||||
expectMetadataUpdate(tokenId, "logo", TestConstants.LOGO_1, deployer);
|
||||
CuT.setTokenLogo(tokenId, TestConstants.LOGO_1);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ describe('FleekERC721.Billing', () => {
|
|||
MintParams.ens,
|
||||
MintParams.commitHash,
|
||||
MintParams.gitRepository,
|
||||
MintParams.ipfsHash,
|
||||
MintParams.logo,
|
||||
MintParams.color,
|
||||
MintParams.accessPointAutoApprovalSettings,
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ describe('FleekERC721.CollectionRoles', () => {
|
|||
TestConstants.MintParams.ens,
|
||||
TestConstants.MintParams.commitHash,
|
||||
TestConstants.MintParams.gitRepository,
|
||||
TestConstants.MintParams.ipfsHash,
|
||||
TestConstants.MintParams.logo,
|
||||
TestConstants.MintParams.color,
|
||||
TestConstants.MintParams.accessPointAutoApprovalSettings,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ describe('FleekERC721.ENS', () => {
|
|||
'app.eth',
|
||||
MintParams.commitHash,
|
||||
MintParams.gitRepository,
|
||||
MintParams.ipfsHash,
|
||||
MintParams.logo,
|
||||
MintParams.color,
|
||||
MintParams.accessPointAutoApprovalSettings,
|
||||
|
|
@ -43,6 +44,7 @@ describe('FleekERC721.ENS', () => {
|
|||
'app.eth',
|
||||
MintParams.commitHash,
|
||||
MintParams.gitRepository,
|
||||
MintParams.ipfsHash,
|
||||
MintParams.logo,
|
||||
MintParams.color,
|
||||
MintParams.accessPointAutoApprovalSettings,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ describe('FleekERC721.GetLastTokenId', () => {
|
|||
TestConstants.MintParams.ens,
|
||||
TestConstants.MintParams.commitHash,
|
||||
TestConstants.MintParams.gitRepository,
|
||||
TestConstants.MintParams.ipfsHash,
|
||||
TestConstants.MintParams.logo,
|
||||
TestConstants.MintParams.color,
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export const TestConstants = Object.freeze({
|
|||
externalUrl: 'https://fleek.co',
|
||||
commitHash: 'b72e47171746b6a9e29b801af9cb655ecf4d665c',
|
||||
gitRepository: 'https://github.com/fleekxyz/non-fungible-apps',
|
||||
ipfsHash: 'mtwirsqawjuoloq2gvtyug2tc3jbf5htm2zeo4rsknfiv3fdp46a',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI1MDAiIHdpZHRoPSIyMTgzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjQgMTQxLjUzMTk5OTk5OTk5OTk4Ij48cGF0aCBkPSJNMTAuMzgzIDEyNi44OTRMMCAwbDEyNCAuMjU1LTEwLjk3OSAxMjYuNjM5LTUwLjU1MyAxNC42Mzh6IiBmaWxsPSIjZTM0ZjI2Ii8+PHBhdGggZD0iTTYyLjQ2OCAxMjkuMjc3VjEyLjA4NWw1MS4wNjQuMTctOS4xMDYgMTA0Ljg1MXoiIGZpbGw9IiNlZjY1MmEiLz48cGF0aCBkPSJNOTkuNDkgNDEuMzYybDEuNDQ2LTE1LjQ5SDIyLjM4M2w0LjM0IDQ3LjQ5aDU0LjIxM0w3OC44MSA5My42MTdsLTE3LjM2MiA0LjY4LTE3LjYxNy01LjEwNi0uOTM2LTEyLjA4NUgyNy4zMTlsMi4xMjggMjQuNjgxIDMyIDguOTM2IDMyLjI1NS04LjkzNiA0LjM0LTQ4LjE3SDQxLjEwN0wzOS40OSA0MS4zNjJ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+',
|
||||
color: 0xe34f26,
|
||||
accessPointAutoApprovalSettings: false,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@ export const Events = Object.freeze({
|
|||
MetadataUpdate: {
|
||||
string: 'MetadataUpdate(uint256,string,string,address)',
|
||||
uint24: 'MetadataUpdate(uint256,string,uint24,address)',
|
||||
stringTuple: 'MetadataUpdate(uint256,string,string[2],address)',
|
||||
stringArray4: 'MetadataUpdate(uint256,string,string[4],address)',
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export abstract class Fixtures {
|
|||
TestConstants.MintParams.ens,
|
||||
TestConstants.MintParams.commitHash,
|
||||
TestConstants.MintParams.gitRepository,
|
||||
TestConstants.MintParams.ipfsHash,
|
||||
TestConstants.MintParams.logo,
|
||||
TestConstants.MintParams.color,
|
||||
TestConstants.MintParams.accessPointAutoApprovalSettings,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ describe('FleekERC721.Minting', () => {
|
|||
MintParams.ens,
|
||||
MintParams.commitHash,
|
||||
MintParams.gitRepository,
|
||||
MintParams.ipfsHash,
|
||||
MintParams.logo,
|
||||
MintParams.color,
|
||||
MintParams.accessPointAutoApprovalSettings,
|
||||
|
|
@ -40,6 +41,7 @@ describe('FleekERC721.Minting', () => {
|
|||
MintParams.ens,
|
||||
MintParams.commitHash,
|
||||
MintParams.gitRepository,
|
||||
MintParams.ipfsHash,
|
||||
MintParams.logo,
|
||||
MintParams.color,
|
||||
MintParams.accessPointAutoApprovalSettings,
|
||||
|
|
@ -66,6 +68,7 @@ describe('FleekERC721.Minting', () => {
|
|||
MintParams.ens,
|
||||
MintParams.commitHash,
|
||||
MintParams.gitRepository,
|
||||
MintParams.ipfsHash,
|
||||
MintParams.logo,
|
||||
MintParams.color,
|
||||
MintParams.accessPointAutoApprovalSettings,
|
||||
|
|
@ -87,6 +90,7 @@ describe('FleekERC721.Minting', () => {
|
|||
'',
|
||||
MintParams.commitHash,
|
||||
MintParams.gitRepository,
|
||||
MintParams.ipfsHash,
|
||||
MintParams.logo,
|
||||
MintParams.color,
|
||||
MintParams.accessPointAutoApprovalSettings,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ describe('FleekERC721.Pausable', () => {
|
|||
MintParams.ens,
|
||||
MintParams.commitHash,
|
||||
MintParams.gitRepository,
|
||||
MintParams.ipfsHash,
|
||||
MintParams.logo,
|
||||
MintParams.color,
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -51,12 +51,20 @@ describe('FleekERC721.UpdateProperties', () => {
|
|||
it('should emit event for build change', async () => {
|
||||
const { contract, tokenId, owner } = fixture;
|
||||
|
||||
await expect(contract.setTokenBuild(tokenId, 'commitHash', 'gitRepository'))
|
||||
.to.emit(contract, Events.MetadataUpdate.stringTuple)
|
||||
await expect(
|
||||
contract.setTokenBuild(
|
||||
tokenId,
|
||||
'commitHash',
|
||||
'gitRepository',
|
||||
'ipfsHash',
|
||||
'domain'
|
||||
)
|
||||
)
|
||||
.to.emit(contract, Events.MetadataUpdate.stringArray4)
|
||||
.withArgs(
|
||||
tokenId,
|
||||
'build',
|
||||
['commitHash', 'gitRepository'],
|
||||
['commitHash', 'gitRepository', 'ipfsHash', 'domain'],
|
||||
owner.address
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ describe('Deploy', () => {
|
|||
TestConstants.MintParams.ens,
|
||||
TestConstants.MintParams.commitHash,
|
||||
TestConstants.MintParams.gitRepository,
|
||||
TestConstants.MintParams.ipfsHash,
|
||||
TestConstants.MintParams.logo,
|
||||
TestConstants.MintParams.color,
|
||||
TestConstants.MintParams.accessPointAutoApprovalSettings,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"pinst": "^3.0.0",
|
||||
"prettier": "^2.8.4",
|
||||
"prettier-plugin-solidity": "^1.0.0",
|
||||
"serverless-offline": "^12.0.4",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# Environment variables declared in this file are automatically made available to Prisma.
|
||||
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
|
||||
|
||||
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
|
||||
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
||||
|
||||
DATABASE_URL="mongodb+srv://root:randompassword@cluster0.ab1cd.mongodb.net/mydb?retryWrites=true&w=majority"
|
||||
|
|
@ -36,3 +36,31 @@ To deploy to development environment:
|
|||
|
||||
To deploy to production environment:
|
||||
`yarn sls deploy --stage prd`
|
||||
|
||||
### Running MongoDB
|
||||
|
||||
The first step to run MongoDB is making sure the service is installed on the machine locally. You can check the [official MongoDB website](https://www.mongodb.com/docs/manual/installation/#mongodb-installation-tutorials) for more information on the installation process.
|
||||
|
||||
To process database transactions such as `create` calls, Prisma needs the MongoDB instance to be running as a replica set. Run the commands below to start a replica set with `mongod` and `mongosh`:
|
||||
|
||||
```
|
||||
// You should replace the dbpath with the actual path on your machine and assign a name to your replica set. (Default path on linux is: /var/lib/mongodb)
|
||||
// Do not close the terminal tab after running mongod.
|
||||
$ sudo mongod --port 27017 --dbpath /path/to/db --replSet replicaName --bind_ip localhost,127.0.0.1
|
||||
// Start a mongosh session and run the replica set initiation command in the mongo shell.
|
||||
$ mongosh
|
||||
> rs.initiate()
|
||||
```
|
||||
|
||||
Make sure you copy the connection string that is presented in the `Connecting to` field when the mongosh service starts to run. We need the connection string to access the replica set. Rename the `.env.example` file to `.env` and replace the connection string placeholder in the file with the one you copied.
|
||||
|
||||
### Prisma configuration
|
||||
|
||||
In order to use and integrate Prisma, both of the `prisma` and `@prisma/client` packages are needed. The `prisma` package reads the schema and generates a version of Prisma Client that is tailored to our modules.
|
||||
|
||||
Run the following commands to install the packages and generate the customized Prisma Client version based on the schema:
|
||||
|
||||
```
|
||||
yarn add prisma @prisma/client
|
||||
yarn prisma:generate
|
||||
```
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
// Connect the client
|
||||
await prisma.$connect();
|
||||
|
||||
// Query the total count of tokens
|
||||
const allTokens = await prisma.tokens.findMany();
|
||||
console.log('Total tokens:');
|
||||
console.log(allTokens.length);
|
||||
|
||||
// Query the token associated with tokenId 1
|
||||
const tokenOne = await prisma.tokens.findRaw({
|
||||
filter: {
|
||||
tokenId: 1,
|
||||
},
|
||||
});
|
||||
console.log('Token Id One:');
|
||||
console.log(tokenOne);
|
||||
}
|
||||
|
||||
main()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -5,7 +5,10 @@
|
|||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "yarn tsc",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"invoke:build": "yarn build && serverless invoke local --function submitBuildInfo",
|
||||
"prisma:generate": "npx prisma generate",
|
||||
"prisma:pull": "npx prisma db pull --force",
|
||||
"start": "serverless offline"
|
||||
},
|
||||
"author": "fleek",
|
||||
"license": "MIT",
|
||||
|
|
@ -27,7 +30,14 @@
|
|||
"@middy/core": "^4.2.7",
|
||||
"@middy/http-json-body-parser": "^4.2.7",
|
||||
"@middy/http-response-serializer": "^4.2.8",
|
||||
"@prisma/client": "^4.13.0",
|
||||
"@types/node": "^18.15.11",
|
||||
"aws-sdk": "^2.1342.0",
|
||||
"uuid": "^9.0.0"
|
||||
"dotenv": "^16.0.3",
|
||||
"prisma": "^4.13.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.4",
|
||||
"uuid": "^9.0.0",
|
||||
"web3": "^1.9.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "mongodb"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model builds {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
commitHash String
|
||||
domain String
|
||||
githubRepository String
|
||||
ipfsHash String
|
||||
}
|
||||
|
||||
model tokens {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
commitHash String
|
||||
domain String
|
||||
githubRepository String
|
||||
ipfsHash String
|
||||
owner String
|
||||
tokenId Int
|
||||
verified Boolean
|
||||
}
|
||||
|
|
@ -31,15 +31,25 @@ custom:
|
|||
|
||||
functions:
|
||||
submitBuildInfo:
|
||||
handler: dist/functions/builds/handler.submitBuildInfo
|
||||
handler: src/functions/builds/handler.submitBuildInfo # Change `src` to `dist` for deployment
|
||||
events:
|
||||
- http:
|
||||
path: build
|
||||
method: post
|
||||
cors: true
|
||||
request:
|
||||
parameters:
|
||||
querystrings:
|
||||
githubOrg: true
|
||||
githubRepo: true
|
||||
commitHash: true
|
||||
author: true
|
||||
timestamp: true
|
||||
ipfsHash: true
|
||||
tokenId: true
|
||||
|
||||
submitMintInfo:
|
||||
handler: dist/functions/mints/handler.submitMintInfo
|
||||
handler: src/functions/mints/handler.submitMintInfo
|
||||
events:
|
||||
- http:
|
||||
path: mint
|
||||
|
|
|
|||
|
|
@ -1,16 +1,92 @@
|
|||
import { APIGatewayProxyResult } from 'aws-lambda';
|
||||
import { APIGatewayProxyResult, APIGatewayEvent } from 'aws-lambda';
|
||||
import { formatJSONResponse } from '@libs/api-gateway';
|
||||
|
||||
import { v4 } from 'uuid';
|
||||
import { prisma } from '@libs/prisma';
|
||||
import { account, nfaContract } from '@libs/nfa-contract';
|
||||
|
||||
export const submitBuildInfo = async (): Promise<APIGatewayProxyResult> => {
|
||||
export const submitBuildInfo = async (
|
||||
event: APIGatewayEvent
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
try {
|
||||
if (event.body === null) {
|
||||
return formatJSONResponse({
|
||||
status: 422,
|
||||
message: 'Required parameters were not passed.',
|
||||
});
|
||||
}
|
||||
|
||||
const data = JSON.parse(event.body);
|
||||
|
||||
const id = v4();
|
||||
const buildInfo = {
|
||||
buildId: id,
|
||||
createdAt: new Date().toISOString(),
|
||||
githubRepository: data.githubRepository,
|
||||
commitHash: data.commitHash,
|
||||
ipfsHash: data.ipfsHash,
|
||||
domain: data.domain,
|
||||
};
|
||||
|
||||
// Add build record to the database, if it's not already added
|
||||
const buildRecord = await prisma.builds.findMany({
|
||||
where: {
|
||||
commitHash: buildInfo.commitHash,
|
||||
githubRepository: buildInfo.githubRepository,
|
||||
ipfsHash: buildInfo.ipfsHash,
|
||||
domain: buildInfo.domain,
|
||||
},
|
||||
});
|
||||
|
||||
if (buildRecord.length == 0) {
|
||||
await prisma.builds.create({
|
||||
data: {
|
||||
githubRepository: buildInfo.githubRepository,
|
||||
commitHash: buildInfo.commitHash,
|
||||
ipfsHash: buildInfo.ipfsHash,
|
||||
domain: buildInfo.domain,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const mintRecord = await prisma.tokens.findMany({
|
||||
where: {
|
||||
ipfsHash: buildInfo.ipfsHash,
|
||||
domain: buildInfo.domain,
|
||||
commitHash: buildInfo.commitHash,
|
||||
githubRepository: buildInfo.githubRepository,
|
||||
verified: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (mintRecord.length > 0) {
|
||||
// Trigger verification
|
||||
|
||||
// Mark the token as verified in the contract
|
||||
// call the `setTokenVerified` method
|
||||
await nfaContract.methods
|
||||
.setTokenVerified(mintRecord[0].tokenId, true)
|
||||
.send({
|
||||
from: account.address,
|
||||
gas: '1000000',
|
||||
})
|
||||
.catch(console.error);
|
||||
|
||||
// Update the database record in the tokens collection
|
||||
await prisma.tokens.updateMany({
|
||||
where: {
|
||||
ipfsHash: buildInfo.ipfsHash,
|
||||
domain: buildInfo.domain,
|
||||
commitHash: buildInfo.commitHash,
|
||||
githubRepository: buildInfo.githubRepository,
|
||||
verified: false,
|
||||
},
|
||||
data: {
|
||||
verified: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return formatJSONResponse({
|
||||
buildInfo,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,45 +1,198 @@
|
|||
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)) {
|
||||
APIGatewayProxyResult,
|
||||
APIGatewayEvent,
|
||||
///APIGatewayEventRequestContext,
|
||||
} from 'aws-lambda';
|
||||
import { formatJSONResponse } from '@libs/api-gateway';
|
||||
|
||||
import { v4 } from 'uuid';
|
||||
import { initPrisma, prisma } from '@libs/prisma';
|
||||
import { account, nfaContract, web3 } from '@libs/nfa-contract';
|
||||
|
||||
export const submitMintInfo = async (
|
||||
event: APIGatewayEvent
|
||||
///context: APIGatewayEventRequestContext
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
try {
|
||||
if (event.body === null) {
|
||||
return formatJSONResponse({
|
||||
status: 422,
|
||||
message: 'Required parameters were not passed.',
|
||||
});
|
||||
}
|
||||
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,
|
||||
const eventBody = JSON.parse(event.body);
|
||||
const topics = eventBody.event.data.block.logs[1].slice(1, 3);
|
||||
const hexCalldata = eventBody.event.data.block.logs[1].data;
|
||||
|
||||
const decodedLogs = web3.eth.abi.decodeLog(
|
||||
[
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'uint256',
|
||||
name: 'tokenId',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'string',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'string',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'string',
|
||||
name: 'externalURL',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'string',
|
||||
name: 'ENS',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'string',
|
||||
name: 'commitHash',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'string',
|
||||
name: 'gitRepository',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'string',
|
||||
name: 'ipfsHash',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'string',
|
||||
name: 'logo',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'uint24',
|
||||
name: 'color',
|
||||
type: 'uint24',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'bool',
|
||||
name: 'accessPointAutoApproval',
|
||||
type: 'bool',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'minter',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: 'address',
|
||||
name: 'owner',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: 'address',
|
||||
name: 'verifier',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
hexCalldata,
|
||||
topics
|
||||
);
|
||||
|
||||
const mintInfo = {
|
||||
mintId: id,
|
||||
createdAt: new Date().toISOString(),
|
||||
tokenId: decodedLogs.tokenId,
|
||||
githubRepository: decodedLogs.gitRepository,
|
||||
commit_hash: decodedLogs.commitHash,
|
||||
owner: decodedLogs.owner,
|
||||
ipfsHash: decodedLogs.ipfsHash,
|
||||
domain: decodedLogs.externalURL,
|
||||
};
|
||||
|
||||
initPrisma();
|
||||
|
||||
// Check if there is any build associated with the repository, commit hash, tokenId, and ipfsHash
|
||||
|
||||
const build = await prisma.builds.findMany({
|
||||
where: {
|
||||
githubRepository: mintInfo.githubRepository,
|
||||
commitHash: mintInfo.commit_hash,
|
||||
ipfsHash: mintInfo.ipfsHash,
|
||||
domain: mintInfo.domain,
|
||||
},
|
||||
});
|
||||
|
||||
let verified = false;
|
||||
|
||||
if (build.length > 0) {
|
||||
// Mark the token as verified in the contract
|
||||
try {
|
||||
// call the `setTokenVerified` method
|
||||
await nfaContract.methods
|
||||
.setTokenVerified(mintInfo.tokenId, true)
|
||||
.send({
|
||||
from: account.address,
|
||||
gas: '1000000',
|
||||
});
|
||||
verified = true;
|
||||
} catch (error) {
|
||||
// catch transaction error
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the record to the database
|
||||
|
||||
const token = await prisma.tokens.findMany({
|
||||
where: {
|
||||
tokenId: Number(mintInfo.tokenId),
|
||||
},
|
||||
});
|
||||
|
||||
if (token.length == 0) {
|
||||
await prisma.tokens.create({
|
||||
data: {
|
||||
tokenId: Number(mintInfo.tokenId),
|
||||
githubRepository: mintInfo.githubRepository,
|
||||
commitHash: mintInfo.commit_hash,
|
||||
owner: mintInfo.owner,
|
||||
ipfsHash: mintInfo.ipfsHash,
|
||||
verified: verified,
|
||||
domain: mintInfo.domain,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return formatJSONResponse({
|
||||
mintInfo,
|
||||
});
|
||||
} catch (e) {
|
||||
return formatJSONResponse({
|
||||
status: 500,
|
||||
message: e,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ export const newMint = {
|
|||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
var Web3 = require('web3');
|
||||
|
||||
var web3 = new Web3(Web3.givenProvider || 'ws://localhost:17895');
|
||||
|
||||
export const logDecoder = (
|
||||
eventFieldsABI: {
|
||||
indexed: boolean;
|
||||
internalType: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}[],
|
||||
data: string,
|
||||
topics: string[]
|
||||
) => {
|
||||
return web3.eth.abi.decodeLog(eventFieldsABI, data, topics);
|
||||
};
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import Web3 from 'web3';
|
||||
import * as abiFile from '../../../contracts/deployments/goerli/FleekERC721.json';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
if (process.env.PRIVATE_KEY === undefined) {
|
||||
throw Error('Private key environment variable not set.');
|
||||
}
|
||||
|
||||
const contract_address = abiFile.address;
|
||||
export const abi = abiFile.abi as any;
|
||||
|
||||
export const web3 = new Web3('https://rpc.goerli.mudit.blog');
|
||||
export const nfaContract = new web3.eth.Contract(abi, contract_address);
|
||||
export const account = web3.eth.accounts.privateKeyToAccount(
|
||||
process.env.PRIVATE_KEY
|
||||
);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
export const prisma = new PrismaClient();
|
||||
|
||||
export async function initPrisma() {
|
||||
// Connect the client
|
||||
await prisma.$connect();
|
||||
}
|
||||
|
||||
initPrisma()
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(() => {
|
||||
prisma.$disconnect();
|
||||
});
|
||||
|
|
@ -1,12 +1,19 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"lib": ["ESNext"],
|
||||
"moduleResolution": "node",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"removeComments": true,
|
||||
"outDir": "dist",
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@functions/*": ["src/functions/*"],
|
||||
|
|
|
|||
1967
serverless/yarn.lock
1967
serverless/yarn.lock
File diff suppressed because it is too large
Load Diff
|
|
@ -27,11 +27,12 @@ type NewMint @entity(immutable: true) {
|
|||
ENS: String!
|
||||
commitHash: String! # string
|
||||
gitRepository: String! # string
|
||||
ipfsHash: String!
|
||||
logo: String!
|
||||
color: Int!
|
||||
accessPointAutoApproval: Boolean!
|
||||
triggeredBy: Bytes! # address
|
||||
owner: Owner! # address
|
||||
owner: Owner! # address
|
||||
verifier: Bytes!
|
||||
blockNumber: BigInt!
|
||||
blockTimestamp: BigInt!
|
||||
|
|
@ -44,7 +45,7 @@ type MetadataUpdate @entity(immutable: true) {
|
|||
key: String!
|
||||
stringValue: String
|
||||
uint24Value: Int
|
||||
doubleStringValue: [String!]!
|
||||
multipleStringValue: [String!]!
|
||||
booleanValue: Boolean
|
||||
byAddress: Bytes!
|
||||
blockNumber: BigInt!
|
||||
|
|
@ -76,10 +77,20 @@ type Token @entity {
|
|||
owner: Owner!
|
||||
mintedBy: Bytes!
|
||||
controllers: [Controller!]
|
||||
accessPoints: [AccessPoint!] @derivedFrom(field: "token")
|
||||
verifier: Verifier # Address
|
||||
verified: Boolean!
|
||||
createdAt: BigInt!
|
||||
builds: [Build!]!
|
||||
}
|
||||
|
||||
type Build @entity {
|
||||
id: Bytes! # Token ID
|
||||
gitRepository: GitRepository!
|
||||
commitHash: String!
|
||||
accessPoints: [AccessPoint!] @derivedFrom(field: "token")
|
||||
verifier: Verifier! # Address
|
||||
ipfsHash: String!
|
||||
domain: String!
|
||||
token: Token! @derivedFrom(field: "builds")
|
||||
}
|
||||
|
||||
# Owner entity for collection, access points, and tokens
|
||||
|
|
@ -104,7 +115,7 @@ type Verifier @entity {
|
|||
|
||||
type GitRepository @entity {
|
||||
id: String! # transaction hash of the first transaction this repository appeared in
|
||||
tokens: [Token!] @derivedFrom(field: "gitRepository")
|
||||
builds: [Build!] @derivedFrom(field: "gitRepository")
|
||||
}
|
||||
|
||||
type AccessPoint @entity {
|
||||
|
|
@ -115,4 +126,10 @@ type AccessPoint @entity {
|
|||
nameVerified: Boolean!
|
||||
owner: Owner!
|
||||
creationStatus: String!
|
||||
}
|
||||
createdAt: BigInt!
|
||||
}
|
||||
|
||||
type Collection @entity {
|
||||
id: Bytes!
|
||||
totalTokens: BigInt!
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Bytes, log } from '@graphprotocol/graph-ts';
|
||||
import { Bytes, log, store } from '@graphprotocol/graph-ts';
|
||||
|
||||
// Event Imports [based on the yaml config]
|
||||
import {
|
||||
|
|
@ -7,15 +7,8 @@ import {
|
|||
} from '../generated/FleekNFA/FleekNFA';
|
||||
|
||||
// Entity Imports [based on the schema]
|
||||
import { Owner, Token } from '../generated/schema';
|
||||
|
||||
enum CollectionRoles {
|
||||
Owner,
|
||||
}
|
||||
|
||||
enum TokenRoles {
|
||||
Controller,
|
||||
}
|
||||
import { Owner, Token, Verifier } from '../generated/schema';
|
||||
import { CollectionRoles, TokenRoles } from './constants';
|
||||
|
||||
export function handleCollectionRoleChanged(
|
||||
event: CollectionRoleChangedEvent
|
||||
|
|
@ -25,35 +18,56 @@ export function handleCollectionRoleChanged(
|
|||
const role = event.params.role;
|
||||
const status = event.params.status;
|
||||
|
||||
if (role === CollectionRoles.Owner) {
|
||||
// Owner role
|
||||
if (status) {
|
||||
// granted
|
||||
let owner = Owner.load(toAddress);
|
||||
if (!owner) {
|
||||
owner = new Owner(toAddress);
|
||||
switch (role) {
|
||||
case CollectionRoles.Owner:
|
||||
// Owner role
|
||||
if (status) {
|
||||
// granted
|
||||
let owner = Owner.load(toAddress);
|
||||
if (!owner) {
|
||||
owner = new Owner(toAddress);
|
||||
}
|
||||
owner.collection = true;
|
||||
owner.save();
|
||||
} else {
|
||||
// revoked
|
||||
const owner = Owner.load(toAddress);
|
||||
if (!owner) {
|
||||
log.error(
|
||||
'Owner entity not found. Role: {}, byAddress: {}, toAddress: {}',
|
||||
[role.toString(), byAddress.toHexString(), toAddress.toHexString()]
|
||||
);
|
||||
return;
|
||||
}
|
||||
owner.collection = false;
|
||||
owner.save();
|
||||
}
|
||||
owner.collection = true;
|
||||
owner.save();
|
||||
} else {
|
||||
// revoked
|
||||
const owner = Owner.load(toAddress);
|
||||
if (!owner) {
|
||||
log.error(
|
||||
'Owner entity not found. Role: {}, byAddress: {}, toAddress: {}',
|
||||
[role.toString(), byAddress.toHexString(), toAddress.toHexString()]
|
||||
);
|
||||
return;
|
||||
break;
|
||||
|
||||
case CollectionRoles.Verifier:
|
||||
// Verifier role
|
||||
if (status) {
|
||||
// granted
|
||||
let verifier = Verifier.load(toAddress);
|
||||
if (!verifier) {
|
||||
verifier = new Verifier(toAddress);
|
||||
}
|
||||
verifier.save();
|
||||
} else {
|
||||
// revoked
|
||||
const verifier = Verifier.load(toAddress);
|
||||
if (verifier) {
|
||||
store.remove('Verifier', verifier.id.toString());
|
||||
}
|
||||
}
|
||||
owner.collection = false;
|
||||
owner.save();
|
||||
}
|
||||
} else {
|
||||
log.error('Role not supported. Role: {}, byAddress: {}, toAddress: {}', [
|
||||
role.toString(),
|
||||
byAddress.toHexString(),
|
||||
toAddress.toHexString(),
|
||||
]);
|
||||
|
||||
break;
|
||||
default:
|
||||
log.error('Role not supported. Role: {}, byAddress: {}, toAddress: {}', [
|
||||
role.toString(),
|
||||
byAddress.toHexString(),
|
||||
toAddress.toHexString(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export function handleNewAccessPoint(event: NewAccessPointEvent): void {
|
|||
accessPointEntity.token = Bytes.fromByteArray(
|
||||
Bytes.fromBigInt(event.params.tokenId)
|
||||
);
|
||||
accessPointEntity.createdAt = event.block.timestamp;
|
||||
|
||||
// Load / Create an Owner entity
|
||||
let ownerEntity = Owner.load(event.params.owner);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
export enum CollectionRoles {
|
||||
Owner,
|
||||
Verifier,
|
||||
}
|
||||
|
||||
export enum TokenRoles {
|
||||
Controller,
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { log, ethereum } from '@graphprotocol/graph-ts';
|
||||
import { log, ethereum, BigInt, Address } from '@graphprotocol/graph-ts';
|
||||
|
||||
// Event Imports [based on the yaml config]
|
||||
import { Initialized as InitializedEvent } from '../generated/FleekNFA/FleekNFA';
|
||||
|
||||
// Entity Imports [based on the schema]
|
||||
import { Owner } from '../generated/schema';
|
||||
import { Collection, Owner, Verifier } from '../generated/schema';
|
||||
export function handleInitialized(event: InitializedEvent): void {
|
||||
// This is the contract creation transaction.
|
||||
log.warning('This is the contract creation transaction.', []);
|
||||
|
|
@ -14,9 +14,18 @@ export function handleInitialized(event: InitializedEvent): void {
|
|||
receipt.contractAddress.toHexString(),
|
||||
]);
|
||||
|
||||
// start collection entity
|
||||
const collection = new Collection(event.address);
|
||||
collection.totalTokens = BigInt.fromU32(0);
|
||||
collection.save();
|
||||
|
||||
// add owner
|
||||
const owner = new Owner(event.transaction.from);
|
||||
owner.collection = true;
|
||||
owner.save();
|
||||
|
||||
// add verifier
|
||||
const verifier = new Verifier(event.transaction.from);
|
||||
verifier.save();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { Bytes } from '@graphprotocol/graph-ts';
|
|||
|
||||
// Event Imports [based on the yaml config]
|
||||
import {
|
||||
MetadataUpdate as MetadataUpdateEvent,
|
||||
MetadataUpdate1 as MetadataUpdateEvent1,
|
||||
MetadataUpdate2 as MetadataUpdateEvent2,
|
||||
MetadataUpdate3 as MetadataUpdateEvent3,
|
||||
|
|
@ -14,6 +13,7 @@ import {
|
|||
GitRepository as GitRepositoryEntity,
|
||||
MetadataUpdate,
|
||||
Token,
|
||||
Build,
|
||||
} from '../generated/schema';
|
||||
|
||||
export function handleMetadataUpdateWithStringValue(
|
||||
|
|
@ -62,7 +62,7 @@ export function handleMetadataUpdateWithStringValue(
|
|||
}
|
||||
}
|
||||
|
||||
export function handleMetadataUpdateWithDoubleStringValue(
|
||||
export function handleMetadataUpdateWithMultipleStringValues(
|
||||
event: MetadataUpdateEvent3
|
||||
): void {
|
||||
/**
|
||||
|
|
@ -74,30 +74,29 @@ export function handleMetadataUpdateWithDoubleStringValue(
|
|||
|
||||
entity.key = event.params.key;
|
||||
entity.tokenId = event.params._tokenId;
|
||||
entity.doubleStringValue = event.params.value;
|
||||
entity.multipleStringValue = event.params.value;
|
||||
entity.blockNumber = event.block.number;
|
||||
entity.blockTimestamp = event.block.timestamp;
|
||||
entity.transactionHash = event.transaction.hash;
|
||||
|
||||
entity.save();
|
||||
|
||||
// UPDATE TOKEN
|
||||
const token = Token.load(
|
||||
// CREATE BUILD
|
||||
const build = new Build(
|
||||
Bytes.fromByteArray(Bytes.fromBigInt(event.params._tokenId))
|
||||
);
|
||||
|
||||
if (token) {
|
||||
if (event.params.key == 'build') {
|
||||
let gitRepositoryEntity = GitRepositoryEntity.load(event.params.value[1]);
|
||||
if (!gitRepositoryEntity) {
|
||||
// Create a new gitRepository entity
|
||||
gitRepositoryEntity = new GitRepositoryEntity(event.params.value[1]);
|
||||
}
|
||||
token.commitHash = event.params.value[0];
|
||||
token.gitRepository = event.params.value[1];
|
||||
token.save();
|
||||
gitRepositoryEntity.save();
|
||||
if (event.params.key == 'build') {
|
||||
let gitRepositoryEntity = GitRepositoryEntity.load(event.params.value[1]);
|
||||
if (!gitRepositoryEntity) {
|
||||
// Create a new gitRepository entity
|
||||
gitRepositoryEntity = new GitRepositoryEntity(event.params.value[1]);
|
||||
}
|
||||
build.commitHash = event.params.value[0];
|
||||
build.gitRepository = event.params.value[1];
|
||||
build.ipfsHash = event.params.value[2];
|
||||
build.domain = event.params.value[3];
|
||||
build.save();
|
||||
gitRepositoryEntity.save();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,6 +158,9 @@ export function handleMetadataUpdateWithBooleanValue(
|
|||
if (event.params.key == 'accessPointAutoApproval') {
|
||||
token.accessPointAutoApproval = event.params.value;
|
||||
}
|
||||
if (event.params.key == 'verified') {
|
||||
token.verified = event.params.value;
|
||||
}
|
||||
token.save();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
import { Bytes, log } from '@graphprotocol/graph-ts';
|
||||
import { BigInt, Bytes, log } from '@graphprotocol/graph-ts';
|
||||
|
||||
// Event Imports [based on the yaml config]
|
||||
import { NewMint as NewMintEvent } from '../generated/FleekNFA/FleekNFA';
|
||||
|
||||
// Entity Imports [based on the schema]
|
||||
import { Owner, NewMint, Token } from '../generated/schema';
|
||||
import {
|
||||
Owner,
|
||||
NewMint,
|
||||
Token,
|
||||
GitRepository,
|
||||
Collection,
|
||||
Verifier,
|
||||
} from '../generated/schema';
|
||||
|
||||
export function handleNewMint(event: NewMintEvent): void {
|
||||
const newMintEntity = new NewMint(
|
||||
|
|
@ -16,6 +23,7 @@ export function handleNewMint(event: NewMintEvent): void {
|
|||
const externalURL = event.params.externalURL;
|
||||
const ENS = event.params.ENS;
|
||||
const gitRepository = event.params.gitRepository;
|
||||
const ipfsHash = event.params.ipfsHash;
|
||||
const commitHash = event.params.commitHash;
|
||||
const logo = event.params.logo;
|
||||
const color = event.params.color;
|
||||
|
|
@ -31,6 +39,7 @@ export function handleNewMint(event: NewMintEvent): void {
|
|||
newMintEntity.ENS = ENS;
|
||||
newMintEntity.commitHash = commitHash;
|
||||
newMintEntity.gitRepository = gitRepository;
|
||||
newMintEntity.ipfsHash = ipfsHash;
|
||||
newMintEntity.logo = logo;
|
||||
newMintEntity.color = color;
|
||||
newMintEntity.accessPointAutoApproval = accessPointAutoApproval;
|
||||
|
|
@ -61,20 +70,37 @@ export function handleNewMint(event: NewMintEvent): void {
|
|||
token.description = description;
|
||||
token.externalURL = externalURL;
|
||||
token.ENS = ENS;
|
||||
token.gitRepository = gitRepository;
|
||||
token.commitHash = commitHash;
|
||||
token.logo = logo;
|
||||
token.color = color;
|
||||
token.accessPointAutoApproval = accessPointAutoApproval;
|
||||
token.owner = ownerAddress;
|
||||
token.verifier = verifierAddress;
|
||||
token.verified = false;
|
||||
token.mintTransaction = event.transaction.hash.concatI32(
|
||||
event.logIndex.toI32()
|
||||
);
|
||||
token.mintedBy = event.params.minter;
|
||||
token.controllers = [ownerAddress];
|
||||
token.createdAt = event.block.timestamp;
|
||||
|
||||
if (Verifier.load(verifierAddress)) {
|
||||
token.verifier = verifierAddress;
|
||||
}
|
||||
|
||||
// Populate GitRepository entity
|
||||
let repository = GitRepository.load(gitRepository);
|
||||
if (!repository) {
|
||||
repository = new GitRepository(gitRepository);
|
||||
}
|
||||
|
||||
// Increase total tokens counter
|
||||
const collection = Collection.load(event.address);
|
||||
if (collection) {
|
||||
collection.totalTokens = collection.totalTokens.plus(BigInt.fromU32(1));
|
||||
collection.save();
|
||||
}
|
||||
|
||||
// Save entities
|
||||
owner.save();
|
||||
token.save();
|
||||
repository.save();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { Bytes, log, store } from '@graphprotocol/graph-ts';
|
||||
import { BigInt, Bytes, log, store } from '@graphprotocol/graph-ts';
|
||||
|
||||
// Event Imports [based on the yaml config]
|
||||
import { Transfer as TransferEvent } from '../generated/FleekNFA/FleekNFA';
|
||||
|
||||
// Entity Imports [based on the schema]
|
||||
import { Owner, Token, Transfer } from '../generated/schema';
|
||||
import { Collection, Owner, Token, Transfer } from '../generated/schema';
|
||||
|
||||
export function handleTransfer(event: TransferEvent): void {
|
||||
const transfer = new Transfer(
|
||||
|
|
@ -39,6 +39,14 @@ export function handleTransfer(event: TransferEvent): void {
|
|||
// Remove the entity from storage
|
||||
// Its controllers and owner will be affected.
|
||||
store.remove('Token', TokenId.toString());
|
||||
// decrement the collection entity's token count
|
||||
const collection = Collection.load(event.address);
|
||||
if (collection) {
|
||||
collection.totalTokens = collection.totalTokens.minus(
|
||||
BigInt.fromU32(1)
|
||||
);
|
||||
collection.save();
|
||||
}
|
||||
} else {
|
||||
// Transfer
|
||||
// Load the Token by using its TokenId
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ dataSources:
|
|||
- ChangeAccessPointAutoApproval
|
||||
abis:
|
||||
- name: FleekNFA
|
||||
file: ../contracts/deployments/goerli/FleekERC721.json
|
||||
file: ../contracts/artifacts/contracts/FleekERC721.sol/FleekERC721.json
|
||||
eventHandlers:
|
||||
- event: Approval(indexed address,indexed address,indexed uint256)
|
||||
handler: handleApproval
|
||||
|
|
@ -41,13 +41,13 @@ dataSources:
|
|||
# Token Events
|
||||
- event: MetadataUpdate(indexed uint256,string,string,indexed address)
|
||||
handler: handleMetadataUpdateWithStringValue
|
||||
- event: MetadataUpdate(indexed uint256,string,string[2],indexed address)
|
||||
handler: handleMetadataUpdateWithDoubleStringValue
|
||||
- event: MetadataUpdate(indexed uint256,string,string[4],indexed address)
|
||||
handler: handleMetadataUpdateWithMultipleStringValues
|
||||
- event: MetadataUpdate(indexed uint256,string,uint24,indexed address)
|
||||
handler: handleMetadataUpdateWithIntValue
|
||||
- event: MetadataUpdate(indexed uint256,string,bool,indexed address)
|
||||
handler: handleMetadataUpdateWithBooleanValue
|
||||
- event: NewMint(indexed uint256,string,string,string,string,string,string,string,uint24,bool,indexed address,indexed address,address)
|
||||
- event: NewMint(indexed uint256,string,string,string,string,string,string,string,string,uint24,bool,indexed address,indexed address,address)
|
||||
handler: handleNewMint
|
||||
- event: Transfer(indexed address,indexed address,indexed uint256)
|
||||
handler: handleTransfer
|
||||
|
|
|
|||
|
|
@ -256,13 +256,6 @@
|
|||
dependencies:
|
||||
assemblyscript "0.19.10"
|
||||
|
||||
"@graphprotocol/graph-ts@^0.27.0":
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@graphprotocol/graph-ts/-/graph-ts-0.27.0.tgz#948fe1716f6082964a01a63a19bcbf9ac44e06ff"
|
||||
integrity sha512-r1SPDIZVQiGMxcY8rhFSM0y7d/xAbQf5vHMWUf59js1KgoyWpM6P3tczZqmQd7JTmeyNsDGIPzd9FeaxllsU4w==
|
||||
dependencies:
|
||||
assemblyscript "0.19.10"
|
||||
|
||||
"@rescript/std@9.0.0":
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@rescript/std/-/std-9.0.0.tgz#df53f3fa5911cb4e85bd66b92e9e58ddf3e4a7e1"
|
||||
|
|
@ -496,7 +489,7 @@ assemblyscript@0.19.10:
|
|||
binaryen "101.0.0-nightly.20210723"
|
||||
long "^4.0.0"
|
||||
|
||||
assemblyscript@0.19.23, assemblyscript@^0.19.20:
|
||||
assemblyscript@0.19.23:
|
||||
version "0.19.23"
|
||||
resolved "https://registry.yarnpkg.com/assemblyscript/-/assemblyscript-0.19.23.tgz#16ece69f7f302161e2e736a0f6a474e6db72134c"
|
||||
integrity sha512-fwOQNZVTMga5KRsfY80g7cpOl4PsFQczMwHzdtgoqLXaYhkhavufKb0sB0l3T1DUxpAufA0KNhlbpuuhZUwxMA==
|
||||
|
|
@ -2227,15 +2220,6 @@ mafmt@^7.0.0:
|
|||
dependencies:
|
||||
multiaddr "^7.3.0"
|
||||
|
||||
matchstick-as@0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/matchstick-as/-/matchstick-as-0.5.0.tgz#cdafc1ef49d670b9cbe98e933bc2a5cb7c450aeb"
|
||||
integrity sha512-4K619YDH+so129qt4RB4JCNxaFwJJYLXPc7drpG+/mIj86Cfzg6FKs/bA91cnajmS1CLHdhHl9vt6Kd6Oqvfkg==
|
||||
dependencies:
|
||||
"@graphprotocol/graph-ts" "^0.27.0"
|
||||
assemblyscript "^0.19.20"
|
||||
wabt "1.0.24"
|
||||
|
||||
md5.js@^1.3.4:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
||||
|
|
@ -3399,11 +3383,6 @@ verror@1.10.0:
|
|||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
wabt@1.0.24:
|
||||
version "1.0.24"
|
||||
resolved "https://registry.yarnpkg.com/wabt/-/wabt-1.0.24.tgz#c02e0b5b4503b94feaf4a30a426ef01c1bea7c6c"
|
||||
integrity sha512-8l7sIOd3i5GWfTWciPL0+ff/FK/deVK2Q6FN+MPz4vfUcD78i2M/49XJTwF6aml91uIiuXJEsLKWMB2cw/mtKg==
|
||||
|
||||
wcwidth@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ query lastNFAsPaginated(
|
|||
}
|
||||
}
|
||||
|
||||
query totalTokens {
|
||||
tokens {
|
||||
id
|
||||
query totalTokens($contractId: ID!) {
|
||||
collection(id: $contractId) {
|
||||
totalTokens
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ query getLatestNFAs {
|
|||
}
|
||||
}
|
||||
|
||||
query getNFA($id: ID!) {
|
||||
query getNFADetail($id: ID!) {
|
||||
token(id: $id) {
|
||||
tokenId
|
||||
owner {
|
||||
|
|
@ -50,6 +50,33 @@ query getNFA($id: ID!) {
|
|||
externalURL
|
||||
logo
|
||||
color
|
||||
createdAt
|
||||
accessPoints {
|
||||
createdAt
|
||||
contentVerified
|
||||
owner {
|
||||
id
|
||||
}
|
||||
id
|
||||
}
|
||||
verified
|
||||
verifier {
|
||||
id
|
||||
}
|
||||
gitRepository {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getNFA($id: ID!) {
|
||||
token(id: $id) {
|
||||
tokenId
|
||||
name
|
||||
ENS
|
||||
externalURL
|
||||
logo
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@
|
|||
href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
content="Minimal UI for Sites as NFTs. Fleek XYZ"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev:css": "tailwindcss -o ./tailwind.css --watch && yarn dev",
|
||||
"build": "yarn graphclient build && vite build",
|
||||
"build:graph": "yarn graphclient build",
|
||||
"build": "yarn build:graph && vite build",
|
||||
"postinstall": "graphclient build",
|
||||
"preview": "vite preview",
|
||||
"prod": "yarn build && npx serve dist -s"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { styled } from '@/theme';
|
|||
|
||||
export abstract class CardStyles {
|
||||
static readonly Container = styled('div', {
|
||||
width: '$full',
|
||||
backgroundColor: '$slate2',
|
||||
borderRadius: '$xlh',
|
||||
padding: '$7',
|
||||
|
|
@ -10,10 +11,8 @@ export abstract class CardStyles {
|
|||
borderWidth: '$default',
|
||||
});
|
||||
|
||||
static readonly Heading = styled('h3', {
|
||||
color: '$slate12',
|
||||
fontSize: '$xl',
|
||||
fontWeight: '$medium',
|
||||
static readonly Header = styled('div', {
|
||||
width: '$full',
|
||||
});
|
||||
|
||||
static readonly Body = styled('div', {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
/* eslint-disable react/display-name */
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import { Flex } from '../layout';
|
||||
import { CardStyles } from './card.styles';
|
||||
|
||||
export abstract class Card {
|
||||
|
|
@ -15,21 +14,7 @@ export abstract class Card {
|
|||
}
|
||||
);
|
||||
|
||||
static readonly Heading = forwardRef<HTMLHeadingElement, Card.HeadingProps>(
|
||||
({ title, leftIcon, rightIcon, css, ...props }, ref) => {
|
||||
return (
|
||||
<Flex css={{ justifyContent: 'space-between', ...css }}>
|
||||
<Flex>
|
||||
{leftIcon}
|
||||
<CardStyles.Heading ref={ref} {...props}>
|
||||
{title}
|
||||
</CardStyles.Heading>
|
||||
</Flex>
|
||||
{rightIcon}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
static readonly Header = CardStyles.Header;
|
||||
|
||||
static readonly Body = forwardRef<HTMLDivElement, Card.BodyProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
|
|
@ -57,12 +42,7 @@ export namespace Card {
|
|||
typeof CardStyles.Container
|
||||
>;
|
||||
|
||||
export type HeadingProps = {
|
||||
title: string;
|
||||
css?: React.CSSProperties;
|
||||
leftIcon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
} & React.ComponentProps<typeof CardStyles.Heading>;
|
||||
export type HeadingProps = React.ComponentProps<typeof CardStyles.Header>;
|
||||
|
||||
export type BodyProps = React.ComponentProps<typeof CardStyles.Body>;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { Card, Flex } from '@/components';
|
||||
import { styled } from '@/theme';
|
||||
|
||||
export const CustomCardStyles = {
|
||||
Container: styled(Card.Container, {
|
||||
maxWidth: '$107h',
|
||||
}),
|
||||
Title: {
|
||||
Container: styled(Flex, {
|
||||
justifyContent: 'space-between',
|
||||
}),
|
||||
Text: styled('h3', {
|
||||
color: '$slate12',
|
||||
fontSize: '$xl',
|
||||
fontWeight: '$medium',
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import { Card, Flex, Icon, IconButton } from '@/components';
|
||||
import { forwardStyledRef } from '@/theme';
|
||||
|
||||
import { CardStyles } from '../card.styles';
|
||||
import { CustomCardStyles as S } from './custom-card.styles';
|
||||
|
||||
export const CustomCardContainer = S.Container;
|
||||
|
||||
export abstract class CustomCardHeader {
|
||||
static readonly Default = forwardStyledRef<
|
||||
HTMLHeadingElement,
|
||||
CustomCard.HeadingProps
|
||||
>(({ title, onClickBack, ...props }, ref) => {
|
||||
return (
|
||||
<Card.Header ref={ref} {...props}>
|
||||
<S.Title.Container>
|
||||
<Flex css={{ gap: '$2' }}>
|
||||
{onClickBack && (
|
||||
<IconButton
|
||||
aria-label="back"
|
||||
colorScheme="gray"
|
||||
variant="link"
|
||||
icon={<Icon name="back" />}
|
||||
onClick={onClickBack}
|
||||
/>
|
||||
)}
|
||||
<S.Title.Text>{title}</S.Title.Text>
|
||||
</Flex>
|
||||
<IconButton
|
||||
aria-label="Add"
|
||||
colorScheme="gray"
|
||||
variant="link"
|
||||
icon={<Icon name="info" />}
|
||||
/>
|
||||
</S.Title.Container>
|
||||
</Card.Header>
|
||||
);
|
||||
});
|
||||
|
||||
static readonly Success = forwardStyledRef<
|
||||
HTMLHeadingElement,
|
||||
Omit<CustomCard.HeadingProps, 'onClickBack'>
|
||||
>(({ title, ...props }, ref) => {
|
||||
return (
|
||||
<Card.Header ref={ref} {...props}>
|
||||
<Flex css={{ gap: '$2' }}>
|
||||
<Icon
|
||||
name="check-circle"
|
||||
css={{ color: '$green11', fontSize: '$xl' }}
|
||||
/>
|
||||
<S.Title.Text>{title}</S.Title.Text>
|
||||
</Flex>
|
||||
</Card.Header>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export namespace CustomCard {
|
||||
export type ContainerProps = React.ComponentProps<typeof S.Container>;
|
||||
|
||||
export type HeadingProps = {
|
||||
title: string;
|
||||
onClickBack?: () => void;
|
||||
} & React.ComponentProps<typeof CardStyles.Header>;
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './custom-card';
|
||||
|
|
@ -1 +1,2 @@
|
|||
export * from './card';
|
||||
export * from './custom-card';
|
||||
|
|
|
|||
|
|
@ -1,57 +1,23 @@
|
|||
import React from 'react';
|
||||
|
||||
import { ButtonProps } from '.';
|
||||
import {
|
||||
StyledButtonContentFlex,
|
||||
StyledButtonContentGrid,
|
||||
} from './button-content.styles';
|
||||
import { ButtonIcon } from './button-icon';
|
||||
|
||||
export type ButtonContentProps = Pick<
|
||||
ButtonProps,
|
||||
| 'leftIcon'
|
||||
| 'rightIcon'
|
||||
| 'topIcon'
|
||||
| 'bottomIcon'
|
||||
| 'children'
|
||||
| 'iconSpacing'
|
||||
'leftIcon' | 'rightIcon' | 'children'
|
||||
>;
|
||||
|
||||
export const ButtonContent: React.FC<ButtonContentProps> = (props) => {
|
||||
const {
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
topIcon,
|
||||
bottomIcon,
|
||||
children,
|
||||
iconSpacing = '1h',
|
||||
} = props;
|
||||
const { leftIcon, rightIcon, children } = props;
|
||||
|
||||
const midNode = (
|
||||
<>
|
||||
{leftIcon && (
|
||||
<ButtonIcon css={{ marginRight: `$${iconSpacing}` }}>
|
||||
{leftIcon}
|
||||
</ButtonIcon>
|
||||
)}
|
||||
{leftIcon && <ButtonIcon>{leftIcon}</ButtonIcon>}
|
||||
{children}
|
||||
{rightIcon && (
|
||||
<ButtonIcon css={{ marginLeft: `$${iconSpacing}` }}>
|
||||
{rightIcon}
|
||||
</ButtonIcon>
|
||||
)}
|
||||
{rightIcon && <ButtonIcon>{rightIcon}</ButtonIcon>}
|
||||
</>
|
||||
);
|
||||
|
||||
if (!topIcon && !bottomIcon) {
|
||||
return midNode;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledButtonContentGrid>
|
||||
{topIcon && <ButtonIcon>{topIcon}</ButtonIcon>}
|
||||
<StyledButtonContentFlex>{midNode}</StyledButtonContentFlex>
|
||||
{bottomIcon && <ButtonIcon>{bottomIcon}</ButtonIcon>}
|
||||
</StyledButtonContentGrid>
|
||||
);
|
||||
return midNode;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -49,11 +49,6 @@ export interface ButtonProps extends StyledButtonProps {
|
|||
* @type React.ReactElement
|
||||
*/
|
||||
bottomIcon?: React.ReactElement;
|
||||
/**
|
||||
* The space between the button icon and label.
|
||||
* @type SystemProps["marginRight"]
|
||||
*/
|
||||
iconSpacing?: string;
|
||||
/**
|
||||
* Replace the spinner component when `isLoading` is set to `true`
|
||||
* @type React.ReactElement
|
||||
|
|
@ -131,17 +126,6 @@ const getButtonCompoundVariant = ({
|
|||
'&:focus, &:active': {
|
||||
backgroundColor: `$${color}3`,
|
||||
},
|
||||
|
||||
'&:disabled': {
|
||||
backgroundColor: `initial`,
|
||||
'&:hover': {
|
||||
color: `$${color}11`,
|
||||
backgroundColor: `initial`,
|
||||
},
|
||||
'& img, & svg': {
|
||||
filter: 'grayscale(100%)',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -13,9 +13,6 @@ export const Button = forwardStyledRef<HTMLButtonElement, ButtonProps>(
|
|||
spinnerPlacement = 'start',
|
||||
spinner,
|
||||
loadingText,
|
||||
iconSpacing,
|
||||
topIcon,
|
||||
bottomIcon,
|
||||
rightIcon,
|
||||
leftIcon,
|
||||
isFullWidth,
|
||||
|
|
@ -26,9 +23,6 @@ export const Button = forwardStyledRef<HTMLButtonElement, ButtonProps>(
|
|||
const contentProps = {
|
||||
rightIcon,
|
||||
leftIcon,
|
||||
bottomIcon,
|
||||
topIcon,
|
||||
iconSpacing,
|
||||
children,
|
||||
};
|
||||
|
||||
|
|
@ -40,16 +34,12 @@ export const Button = forwardStyledRef<HTMLButtonElement, ButtonProps>(
|
|||
data-loading={isLoading}
|
||||
css={{
|
||||
width: isFullWidth ? '100%' : undefined,
|
||||
...(ownProps?.css || {}),
|
||||
...ownProps?.css,
|
||||
}}
|
||||
{...ownProps}
|
||||
>
|
||||
{isLoading && spinnerPlacement === 'start' && (
|
||||
<ButtonSpinner
|
||||
label={loadingText}
|
||||
placement={spinnerPlacement}
|
||||
spacing={iconSpacing}
|
||||
>
|
||||
<ButtonSpinner label={loadingText} placement={spinnerPlacement}>
|
||||
{spinner}
|
||||
</ButtonSpinner>
|
||||
)}
|
||||
|
|
@ -65,11 +55,7 @@ export const Button = forwardStyledRef<HTMLButtonElement, ButtonProps>(
|
|||
)}
|
||||
|
||||
{isLoading && spinnerPlacement === 'end' && (
|
||||
<ButtonSpinner
|
||||
label={loadingText}
|
||||
placement={spinnerPlacement}
|
||||
spacing={iconSpacing}
|
||||
>
|
||||
<ButtonSpinner label={loadingText} placement={spinnerPlacement}>
|
||||
{spinner}
|
||||
</ButtonSpinner>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ type OmittedProps =
|
|||
| 'isFullWidth'
|
||||
| 'rightIcon'
|
||||
| 'loadingText'
|
||||
| 'iconSpacing'
|
||||
| 'spinnerPlacement';
|
||||
|
||||
type BaseButtonProps = Omit<ButtonProps, OmittedProps>;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
|
|||
</Button>
|
||||
<input
|
||||
ref={inputColorRef}
|
||||
className="absolute right-16"
|
||||
className="absolute right-16 h-5"
|
||||
type="color"
|
||||
value={logoColor}
|
||||
onChange={handleColorChange}
|
||||
|
|
|
|||
|
|
@ -1,186 +0,0 @@
|
|||
import { Listbox, Transition } from '@headlessui/react';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
import { Flex } from '@/components';
|
||||
import { Icon } from '@/components/core/icon';
|
||||
|
||||
type DropdownOptionProps = {
|
||||
option: DropdownItem;
|
||||
};
|
||||
|
||||
const DropdownOption: React.FC<DropdownOptionProps> = ({
|
||||
option,
|
||||
}: DropdownOptionProps) => (
|
||||
<Listbox.Option
|
||||
className={({ active }) =>
|
||||
`relative cursor-default select-none py-2 px-3.5 text-slate11 rounded-xl mb-2 text-sm max-w-full ${
|
||||
active ? 'bg-slate5 text-slate12' : 'bg-transparent'
|
||||
}`
|
||||
}
|
||||
value={option}
|
||||
>
|
||||
{({ selected, active }) => (
|
||||
<Flex css={{ justifyContent: 'space-between' }}>
|
||||
<span
|
||||
className={`${
|
||||
active ? 'text-slate12' : 'text-slate11'
|
||||
} max-w-full break-words pr-5`}
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
{selected && (
|
||||
<Icon
|
||||
name="check"
|
||||
color="white"
|
||||
css={{
|
||||
position: 'absolute',
|
||||
top: '$0',
|
||||
bottom: '$0',
|
||||
right: '$0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
pr: '$4',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
);
|
||||
|
||||
type DropdownButtonProps = {
|
||||
/**
|
||||
* The selected value of the dropdown.
|
||||
*/
|
||||
selectedValue: DropdownItem | undefined;
|
||||
/**
|
||||
* If it's true, the list of options will be displayed
|
||||
*/
|
||||
open: boolean;
|
||||
/**
|
||||
* Background color of the dropdown. Should be on tailwind palette.
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
/**
|
||||
* Text color of the dropdown. Should be on tailwind palette.
|
||||
*/
|
||||
textColor?: string;
|
||||
};
|
||||
|
||||
const DropdownButton: React.FC<DropdownButtonProps> = ({
|
||||
selectedValue,
|
||||
backgroundColor,
|
||||
textColor,
|
||||
}: DropdownButtonProps) => {
|
||||
const textColorCss = textColor ? `text-${textColor}` : 'text-slate12';
|
||||
const borderColor = backgroundColor
|
||||
? `border-${backgroundColor}`
|
||||
: 'border-slate7';
|
||||
const backgroundColorClass = backgroundColor
|
||||
? `bg-${backgroundColor}`
|
||||
: 'bg-transparent';
|
||||
|
||||
return (
|
||||
<Listbox.Button
|
||||
className={`relative w-full cursor-default ${borderColor} border-solid border rounded-xl py-3 pl-3.5 pr-10 h-11 text-left focus:outline-none sm:text-sm
|
||||
${backgroundColorClass}
|
||||
`}
|
||||
>
|
||||
<span
|
||||
className={`block truncate ${
|
||||
selectedValue && selectedValue.label
|
||||
? `${textColorCss}`
|
||||
: 'text-slate11'
|
||||
} break-words`}
|
||||
>
|
||||
{selectedValue && selectedValue.label ? selectedValue.label : 'Select'}
|
||||
</span>
|
||||
<span
|
||||
className={`pointer-events-none absolute top-1 bottom-0 right-0 flex items-center pr-4 ${textColorCss}`}
|
||||
>
|
||||
<Icon name="chevron-down" />
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
);
|
||||
};
|
||||
|
||||
export type DropdownItem = {
|
||||
/**
|
||||
* The key of the item.
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* The label to display of the item.
|
||||
*/
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type DropdownProps = {
|
||||
/**
|
||||
* List of items to be displayed in the dropdown.
|
||||
*/
|
||||
items: DropdownItem[];
|
||||
/**
|
||||
* The selected value of the dropdown.
|
||||
*/
|
||||
selectedValue: DropdownItem | undefined;
|
||||
/**
|
||||
* Callback when the selected value changes.
|
||||
*/
|
||||
onChange(option: DropdownItem): void;
|
||||
/**
|
||||
* Background color of the dropdown. Should be on tailwind palette. https://tailwindcss.com/docs/background-color
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
/**
|
||||
* Text color of the dropdown. Should be on tailwind palette. https://tailwindcss.com/docs/text-color
|
||||
*/
|
||||
textColor?: string;
|
||||
/**
|
||||
* Width of the options list. Should be on tailwind width. https://tailwindcss.com/docs/width
|
||||
*/
|
||||
optionsWidth?: string;
|
||||
};
|
||||
|
||||
export const Dropdown: React.FC<DropdownProps> = ({
|
||||
items,
|
||||
selectedValue,
|
||||
onChange,
|
||||
backgroundColor,
|
||||
textColor,
|
||||
optionsWidth,
|
||||
}: DropdownProps) => {
|
||||
const handleDropdownChange = (option: DropdownItem): void => {
|
||||
onChange(option);
|
||||
};
|
||||
const width = optionsWidth ? `w-${optionsWidth}` : 'w-full';
|
||||
|
||||
return (
|
||||
<Listbox value={selectedValue} by="value" onChange={handleDropdownChange}>
|
||||
{({ open }) => (
|
||||
<div className="relative max-w-full">
|
||||
<DropdownButton
|
||||
selectedValue={selectedValue}
|
||||
open={open}
|
||||
backgroundColor={backgroundColor}
|
||||
textColor={textColor}
|
||||
/>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options
|
||||
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} />
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</Listbox>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,2 +1 @@
|
|||
export * from './dropdown';
|
||||
export * from './combobox';
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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 { FaBars } from '@react-icons/all-files/fa/FaBars';
|
||||
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';
|
||||
|
|
@ -26,7 +27,7 @@ import {
|
|||
|
||||
export const IconLibrary = Object.freeze({
|
||||
back: IoArrowBackCircleSharp,
|
||||
betaTag: BetaTag,
|
||||
'beta-tag': BetaTag,
|
||||
branch: BiGitBranch,
|
||||
check: AiOutlineCheck,
|
||||
'check-circle': IoCheckmarkCircleSharp,
|
||||
|
|
@ -36,10 +37,11 @@ export const IconLibrary = Object.freeze({
|
|||
error: ErrorIcon,
|
||||
ethereum: EthereumIcon,
|
||||
'external-link': FaExternalLinkAlt,
|
||||
fleekLogo: FleekLogo,
|
||||
fleekName: FleekName,
|
||||
'fleek-logo': FleekLogo,
|
||||
'fleek-name': FleekName,
|
||||
github: IoLogoGithub,
|
||||
info: IoInformationCircleSharp,
|
||||
menu: FaBars,
|
||||
metamask: MetamaskIcon, //remove if not used
|
||||
search: BiSearch,
|
||||
square: BsFillSquareFill,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { styled } from '@/theme';
|
||||
|
||||
import { Icon } from '../icon';
|
||||
|
||||
const styles = {
|
||||
all: 'unset',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
borderStyle: 'solid',
|
||||
|
|
@ -32,7 +29,9 @@ const styles = {
|
|||
color: '$slate8',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const variants = {
|
||||
variants: {
|
||||
size: {
|
||||
sm: {
|
||||
|
|
@ -59,13 +58,49 @@ const styles = {
|
|||
},
|
||||
};
|
||||
|
||||
export const InputStyled = styled('input', styles);
|
||||
|
||||
export const InputIconStyled = styled(Icon, {
|
||||
position: 'absolute',
|
||||
left: '$4',
|
||||
top: '0.9375rem',
|
||||
color: 'slate8',
|
||||
export const InputStyled = styled('input', {
|
||||
all: 'unset',
|
||||
...variants,
|
||||
...styles,
|
||||
});
|
||||
|
||||
export const TextareaStyled = styled('textarea', styles);
|
||||
export const InputGroupStyled = styled('div', {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: '$2h',
|
||||
|
||||
...styles,
|
||||
|
||||
variants: {
|
||||
size: {
|
||||
sm: {
|
||||
borderRadius: '$md',
|
||||
fontSize: '$xs',
|
||||
lineHeight: '$4',
|
||||
},
|
||||
md: {
|
||||
borderRadius: '$lg',
|
||||
fontSize: '$sm',
|
||||
height: '$11',
|
||||
px: '$3h',
|
||||
},
|
||||
lg: {
|
||||
borderRadius: '$xl',
|
||||
fontSize: '$md',
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'md',
|
||||
},
|
||||
});
|
||||
|
||||
export const InputGroupTextSyled = styled('input', {
|
||||
all: 'unset',
|
||||
});
|
||||
|
||||
export const TextareaStyled = styled('textarea', {
|
||||
all: 'unset',
|
||||
...variants,
|
||||
...styles,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,37 +1,17 @@
|
|||
import React from 'react';
|
||||
|
||||
import { forwardStyledRef } from '@/theme';
|
||||
|
||||
import { IconName } from '../icon';
|
||||
import { InputIconStyled, InputStyled, TextareaStyled } from './input.styles';
|
||||
import {
|
||||
InputGroupStyled,
|
||||
InputGroupTextSyled,
|
||||
InputStyled,
|
||||
TextareaStyled,
|
||||
} from './input.styles';
|
||||
import { StyledInputFile } from './input-file';
|
||||
|
||||
export const Textarea = TextareaStyled;
|
||||
|
||||
export const LogoFileInput = StyledInputFile;
|
||||
|
||||
type InputProps = {
|
||||
leftIcon?: IconName;
|
||||
wrapperClassName?: string; //tailwind css
|
||||
} & React.ComponentPropsWithRef<typeof InputStyled>;
|
||||
export const Input = InputStyled;
|
||||
|
||||
export const Input = forwardStyledRef<HTMLInputElement, InputProps>(
|
||||
(props, ref) => {
|
||||
const { leftIcon, wrapperClassName: css = '', ...ownProps } = props;
|
||||
export const InputGroup = InputGroupStyled;
|
||||
|
||||
return (
|
||||
<div className={`relative ${css}`}>
|
||||
{leftIcon && (
|
||||
<InputIconStyled name={leftIcon} css={{ fontSize: '$lg' }} />
|
||||
)}
|
||||
<InputStyled
|
||||
{...props}
|
||||
ref={ref}
|
||||
css={{ ...(leftIcon && { pl: '$10' }), ...(ownProps.css || {}) }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Input.displayName = 'Input';
|
||||
export const InputGroupText = InputGroupTextSyled;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
import { styled } from '@/theme';
|
||||
|
||||
export const Text = styled('span');
|
||||
export const Text = styled('span', {
|
||||
variants: {
|
||||
ellipsis: {
|
||||
true: {
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,3 +9,4 @@ export * from './nfa-card';
|
|||
export * from './nfa-preview';
|
||||
export * from './card-tag';
|
||||
export * from './resolved-address';
|
||||
export * from './row-data';
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export const ConnectWalletButton: React.FC = () => {
|
|||
if (ensName && address) setEnsNameStore(ensName, address);
|
||||
|
||||
return (
|
||||
<Button onClick={show}>
|
||||
<Button onClick={show} css={{ gridArea: 'wallet' }}>
|
||||
{isConnected && !!address && !!truncatedAddress ? (
|
||||
<Flex css={{ gap: '$2' }}>
|
||||
<Avatar address={address} size={20} />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Icon } from '../../core/icon';
|
||||
import { NavBarStyles as S } from './nav-bar.styles';
|
||||
|
||||
export const Logo: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<S.Logo.Wrapper onClick={() => navigate('/home')}>
|
||||
<Icon
|
||||
name="fleek-logo"
|
||||
css={{ fontSize: '$2xl' }}
|
||||
iconElementCss={{ height: '$6' }}
|
||||
/>
|
||||
<Icon name="fleek-name" css={{ fontSize: '$6xl', mr: '$3' }} />
|
||||
<Icon name="beta-tag" css={{ fontSize: '$5xl' }} />
|
||||
</S.Logo.Wrapper>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,33 +1,129 @@
|
|||
import { styled } from '@/theme';
|
||||
import { StyledButton } from '@/components/core';
|
||||
import { alphaColor, styled } from '@/theme';
|
||||
|
||||
import { Flex } from '../flex.styles';
|
||||
|
||||
export abstract class NavBarStyles {
|
||||
static readonly Container = styled('header', {
|
||||
export const NavBarStyles = {
|
||||
Container: styled('header', {
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '$black',
|
||||
zIndex: '$sticky',
|
||||
height: '$22',
|
||||
overflow: 'hidden', // TODO: this must be worked on for responsive layout
|
||||
});
|
||||
|
||||
static readonly Content = styled('div', {
|
||||
display: 'flex',
|
||||
'&:after': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
backgroundColor: alphaColor('black', 0.8),
|
||||
backdropFilter: 'blur(4px)',
|
||||
zIndex: -1,
|
||||
},
|
||||
}),
|
||||
|
||||
Content: styled('div', {
|
||||
width: '100%',
|
||||
maxWidth: '$7xl',
|
||||
margin: '0 auto',
|
||||
alignItems: 'center',
|
||||
padding: '$6',
|
||||
});
|
||||
gap: '$3',
|
||||
|
||||
static readonly Navigation = styled(Flex, {
|
||||
gap: '$10',
|
||||
flexGrow: 4,
|
||||
justifyContent: 'center',
|
||||
});
|
||||
}
|
||||
display: 'grid',
|
||||
gridTemplateAreas: '"logo navigation wallet menu"',
|
||||
gridTemplateColumns: 'auto 1fr auto',
|
||||
}),
|
||||
|
||||
Navigation: {
|
||||
Container: styled(Flex, {
|
||||
gridArea: 'navigation',
|
||||
gap: '$10',
|
||||
justifyContent: 'center',
|
||||
|
||||
fontSize: '$lg',
|
||||
|
||||
variants: {
|
||||
stacked: {
|
||||
true: {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '$4',
|
||||
|
||||
[`${StyledButton}`]: {
|
||||
fontSize: '$lg',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
Button: styled(StyledButton, {
|
||||
variants: {
|
||||
active: {
|
||||
true: {
|
||||
color: '$slate12 !important',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
Sidebar: {
|
||||
Content: styled(Flex, {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
padding: '$6',
|
||||
minWidth: '40vw',
|
||||
zIndex: '$sticky',
|
||||
backgroundColor: '$black',
|
||||
transition: 'transform 0.3s ease-in-out',
|
||||
borderLeft: '1px solid $slate6',
|
||||
|
||||
variants: {
|
||||
open: {
|
||||
true: {
|
||||
transform: 'translateX(0%)',
|
||||
},
|
||||
false: {
|
||||
transform: 'translateX(100%)',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
Backdrop: styled('div', {
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
zIndex: '$sticky',
|
||||
backgroundColor: alphaColor('black', 0.5),
|
||||
display: 'none',
|
||||
transition: 'opacity 0.3s ease-in-out',
|
||||
|
||||
variants: {
|
||||
open: {
|
||||
true: {
|
||||
display: 'block',
|
||||
backdropFilter: 'blur(4px)',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
Logo: {
|
||||
Wrapper: styled(Flex, {
|
||||
gridArea: 'logo',
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,29 +1,20 @@
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Button } from '@/components/core';
|
||||
import { Logo } from '@/components/logo/logo';
|
||||
import { useMediaQuery } from '@/hooks';
|
||||
|
||||
import { ConnectWalletButton } from './connect-wallet-button';
|
||||
import { Logo } from './logo';
|
||||
import { NavBarStyles as Styles } from './nav-bar.styles';
|
||||
import { Navigation } from './navigation';
|
||||
import { Sidebar } from './sidebar';
|
||||
|
||||
export const NavBar: React.FC = () => {
|
||||
const enableSidebar = useMediaQuery('(max-width: 540px)');
|
||||
|
||||
return (
|
||||
<Styles.Container>
|
||||
<Styles.Content>
|
||||
<Logo />
|
||||
|
||||
<Styles.Navigation>
|
||||
<Button as={Link} to="/explore" variant="link" color="gray">
|
||||
Explore
|
||||
</Button>
|
||||
<Button as={Link} to="/mint" variant="link" color="gray">
|
||||
Create
|
||||
</Button>
|
||||
<Button as={Link} to="/" variant="link" color="gray">
|
||||
Learn
|
||||
</Button>
|
||||
</Styles.Navigation>
|
||||
<ConnectWalletButton />
|
||||
{enableSidebar ? <Sidebar /> : <Navigation />}
|
||||
</Styles.Content>
|
||||
</Styles.Container>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import { Link, useLocation } from 'react-router-dom';
|
||||
|
||||
import { forwardStyledRef } from '@/theme';
|
||||
|
||||
import { NavBarStyles as S } from './nav-bar.styles';
|
||||
|
||||
const Paths = [
|
||||
{ path: '/explore', name: 'Explore', activeRegex: /\/$|\/explore/ },
|
||||
{ path: '/mint', name: 'Create', activeRegex: /\/mint/ },
|
||||
{ path: '/', name: 'Learn', activeRegex: /\/learn/ },
|
||||
];
|
||||
|
||||
export const Navigation = forwardStyledRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentPropsWithRef<typeof S.Navigation.Container>
|
||||
>((props, ref) => {
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<S.Navigation.Container {...props} ref={ref}>
|
||||
{Paths.map(({ path, name, activeRegex }) => (
|
||||
<S.Navigation.Button
|
||||
key={path}
|
||||
as={Link}
|
||||
to={path}
|
||||
active={activeRegex.test(location.pathname)}
|
||||
variant="link"
|
||||
color="gray"
|
||||
>
|
||||
{name}
|
||||
</S.Navigation.Button>
|
||||
))}
|
||||
</S.Navigation.Container>
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { Button, Icon } from '@/components/core';
|
||||
|
||||
import { NavBarStyles as Styles } from './nav-bar.styles';
|
||||
import { Navigation } from './navigation';
|
||||
|
||||
export const Sidebar: React.FC = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const sidebarRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleToggle = (): void => setIsOpen(!isOpen);
|
||||
|
||||
const handleNavigationClick = (): void => setIsOpen(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
const { current } = sidebarRef;
|
||||
if (!current) return;
|
||||
|
||||
const handleClickOutside = (event: MouseEvent): void => {
|
||||
if (current && !current.contains(event.target as Node)) setIsOpen(false);
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, [isOpen, sidebarRef]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={handleToggle}
|
||||
css={{ gridArea: 'menu', fontSize: '$lg' }}
|
||||
>
|
||||
<Icon name="menu" />
|
||||
</Button>
|
||||
|
||||
<Styles.Sidebar.Backdrop open={isOpen} />
|
||||
|
||||
<Styles.Sidebar.Content open={isOpen} ref={sidebarRef}>
|
||||
<Navigation stacked onClick={handleNavigationClick} />
|
||||
</Styles.Sidebar.Content>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -10,8 +10,11 @@ export abstract class PageStyles {
|
|||
width: '100%',
|
||||
minHeight: '85vh',
|
||||
maxWidth: '$6xl',
|
||||
padding: '0 $6',
|
||||
padding: '$6',
|
||||
margin: '0 auto',
|
||||
display: 'grid',
|
||||
|
||||
'@md': {
|
||||
padding: '0 $6',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export * from './logo';
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import { styled } from '@/theme';
|
||||
|
||||
import { Flex } from '../layout';
|
||||
|
||||
export abstract class LogoStyles {
|
||||
static readonly Container = styled(Flex, {
|
||||
cursor: 'pointer',
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
static readonly Logo = styled('img', {
|
||||
width: '$6',
|
||||
height: 'auto',
|
||||
});
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Icon } from '../core/icon';
|
||||
import { LogoStyles as LS } from './logo.styles';
|
||||
|
||||
export const Logo: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<LS.Container onClick={() => navigate('/home')}>
|
||||
<Icon
|
||||
name="fleekLogo"
|
||||
css={{ fontSize: '$2xl' }}
|
||||
iconElementCss={{ height: '$6' }}
|
||||
/>
|
||||
<Icon name="fleekName" css={{ fontSize: '$6xl', mr: '$3' }} />
|
||||
<Icon name="betaTag" css={{ fontSize: '$5xl' }} />
|
||||
</LS.Container>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { Text } from '@/components';
|
||||
import { keyframes, styled } from '@/theme';
|
||||
|
||||
const Loading = keyframes({
|
||||
|
|
@ -13,7 +14,7 @@ const Loading = keyframes({
|
|||
});
|
||||
|
||||
export const ResolvedAddressStyles = {
|
||||
Container: styled('span', {
|
||||
Container: styled(Text, {
|
||||
'&[data-loading="true"]': {
|
||||
animation: `${Loading} 1s ease-in-out infinite`,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export type ResolvedAddressProps = React.ComponentPropsWithRef<
|
|||
export const ResolvedAddress = forwardStyledRef<
|
||||
HTMLSpanElement,
|
||||
ResolvedAddressProps
|
||||
>(({ children, truncated = false, ...props }, ref) => {
|
||||
>(({ children, truncated = true, ...props }, ref) => {
|
||||
const [resolvedAddress, loading] = useResolvedAddress(children);
|
||||
|
||||
const text = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export * from './row-data';
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { Flex, Text } from '@/components';
|
||||
import { styled } from '@/theme';
|
||||
|
||||
import { IconStyles } from '../core/icon/icon.styles';
|
||||
|
||||
export const RowDataStyles = {
|
||||
Container: styled(Flex, {
|
||||
justifyContent: 'space-between',
|
||||
}),
|
||||
Text: {
|
||||
Container: styled(Flex, {
|
||||
alignItems: 'center',
|
||||
maxWidth: '60%',
|
||||
gap: '$2',
|
||||
|
||||
[`${IconStyles.Container}`]: {
|
||||
fontSize: '$2xl',
|
||||
},
|
||||
}),
|
||||
Label: styled(Text, {
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import { forwardStyledRef } from '@/theme';
|
||||
|
||||
import { RowDataStyles as S } from './row-data.styles';
|
||||
|
||||
type RowDataProps = {
|
||||
leftIcon: React.ReactNode;
|
||||
label: string;
|
||||
rightComponent: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const RowData = forwardStyledRef<HTMLDivElement, RowDataProps>(
|
||||
({ leftIcon, label, rightComponent, onClick, ...props }, ref) => {
|
||||
const handleOnClick = (): void => {
|
||||
if (onClick) onClick();
|
||||
};
|
||||
|
||||
return (
|
||||
<S.Container ref={ref} {...props} onClick={handleOnClick}>
|
||||
<S.Text.Container>
|
||||
{leftIcon}
|
||||
<S.Text.Label>{label}</S.Text.Label>
|
||||
</S.Text.Container>
|
||||
{rightComponent}
|
||||
</S.Container>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
RowData.displayName = 'RowData';
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { Flex } from '@/components';
|
||||
import { styled } from '@/theme';
|
||||
|
||||
export const StepStyles = {
|
||||
Container: styled(Flex, {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '$full',
|
||||
gap: '$6',
|
||||
|
||||
'@media (min-width: 768px)': {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
|
||||
'@media (min-width: 1024px)': {
|
||||
gap: '$34',
|
||||
},
|
||||
}),
|
||||
Indicator: styled(Flex, {
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
maxWidth: '$106',
|
||||
}),
|
||||
};
|
||||
|
|
@ -1,37 +1,6 @@
|
|||
import { Flex, Stepper } from '@/components';
|
||||
import { Stepper } from '@/components';
|
||||
|
||||
type StepperIndicatorContainerProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const StepperIndicatorContainer: React.FC<StepperIndicatorContainerProps> = ({
|
||||
children,
|
||||
}: StepperIndicatorContainerProps) => {
|
||||
return (
|
||||
<Flex
|
||||
css={{
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
mr: '$34',
|
||||
width: '$106',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
type MintStepContainerProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const Container: React.FC<MintStepContainerProps> = ({
|
||||
children,
|
||||
}: MintStepContainerProps) => (
|
||||
<Flex css={{ flexDirection: 'row', justifyContent: 'center' }}>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
import { StepStyles as S } from './step.styles';
|
||||
|
||||
type StepProps = {
|
||||
children: React.ReactNode;
|
||||
|
|
@ -40,12 +9,12 @@ type StepProps = {
|
|||
|
||||
export const Step: React.FC<StepProps> = ({ children, header }: StepProps) => {
|
||||
return (
|
||||
<Container>
|
||||
<StepperIndicatorContainer>
|
||||
<S.Container>
|
||||
<S.Indicator>
|
||||
<Stepper.Indicator />
|
||||
<h2 className="text-4xl">{header}</h2>
|
||||
</StepperIndicatorContainer>
|
||||
</S.Indicator>
|
||||
{children}
|
||||
</Container>
|
||||
</S.Container>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export * from './use-transaction-cost';
|
||||
export * from './use-window-scroll-end';
|
||||
export * from './use-debounce';
|
||||
export * from './use-media-query';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const useMediaQuery = (query: string): boolean => {
|
||||
const getMatches = (query: string): boolean => {
|
||||
// Prevents SSR issues
|
||||
if (typeof window !== 'undefined') {
|
||||
return window.matchMedia(query).matches;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const [matches, setMatches] = useState<boolean>(getMatches(query));
|
||||
|
||||
const handleChange = (): void => {
|
||||
setMatches(getMatches(query));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const matchMedia = window.matchMedia(query);
|
||||
|
||||
// Triggered at the first client-side load and if query changes
|
||||
handleChange();
|
||||
|
||||
// Listen matchMedia
|
||||
if (matchMedia.addListener) {
|
||||
matchMedia.addListener(handleChange);
|
||||
} else {
|
||||
matchMedia.addEventListener('change', handleChange);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (matchMedia.removeListener) {
|
||||
matchMedia.removeListener(handleChange);
|
||||
} else {
|
||||
matchMedia.removeEventListener('change', handleChange);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [query]);
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { config, theme } from './themes';
|
||||
|
||||
config.theme.colors;
|
||||
export const alphaColor = (
|
||||
color: keyof typeof theme.colors,
|
||||
value: number
|
||||
): string =>
|
||||
config.theme.colors[color] +
|
||||
`00${Math.round(0xff * value).toString(16)}`.slice(-2);
|
||||
|
|
@ -23,6 +23,7 @@ export const colors = {
|
|||
...amber,
|
||||
};
|
||||
export const darkColors = {
|
||||
black: '#000000',
|
||||
...grayDark,
|
||||
...slateDark,
|
||||
...blueDark,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
export const media = {
|
||||
// Breakpoints
|
||||
xs: '(min-width: 375px)',
|
||||
sm: '(min-width: 640px)',
|
||||
md: '(min-width: 768px)',
|
||||
lg: '(min-width: 1024px)',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const themeGlobals = globalCss({
|
|||
fontFamily: 'Manrope',
|
||||
|
||||
fontSize: '16px',
|
||||
|
||||
'@media (max-width: 850px)': {
|
||||
fontSize: '13px',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@ export * from './themes';
|
|||
export * from './foundations';
|
||||
export * from './key-frames';
|
||||
export * from './forward-styled-ref';
|
||||
export * from './alpha-color';
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ const createDripStitches = <
|
|||
},
|
||||
theme: {
|
||||
colors: {
|
||||
black: '#000000',
|
||||
...darkColors, // TODO: replace with light colors once it's done the light mode
|
||||
...(theme?.colors || {}),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,6 +10,5 @@ export const parseColorToNumber = (color: string): number => {
|
|||
* Converts string number to hex color string.
|
||||
*/
|
||||
export const parseNumberToHexColor = (color: number): string => {
|
||||
const hexColor = color.toString(16);
|
||||
return hexColor;
|
||||
return `${`000000${color.toString(16)}`.slice(-6)}`;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,3 +10,45 @@ export const getRepoAndCommit = (url: string): object => {
|
|||
export const contractAddress = (address: string): string => {
|
||||
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
||||
};
|
||||
|
||||
export const getRepositoryFromURL = (url: string): string => {
|
||||
const urlSplitted = url.split('/');
|
||||
return `${urlSplitted[3]}/${urlSplitted[4]}`;
|
||||
};
|
||||
|
||||
export const getDate = (date: number): string => {
|
||||
return new Date(date * 1000).toLocaleDateString();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param date date in tiemstamp format
|
||||
* @returns time since date
|
||||
*/
|
||||
export const getTimeSince = (date: number): string => {
|
||||
const now = new Date().getTime(); //in timestamp format
|
||||
|
||||
const milliseconds = now - date * 1000;
|
||||
const seconds = milliseconds / 1000;
|
||||
const minutes = Math.round((seconds / 60) % 60);
|
||||
const days = Math.round(seconds / (60 * 60 * 24));
|
||||
const hours = Math.round(minutes % 60);
|
||||
const months = Math.round(days / 30.5);
|
||||
const years = Math.round(months / 12);
|
||||
|
||||
if (years > 0) {
|
||||
return `${years} year${years > 1 ? 's' : ''} ago`;
|
||||
}
|
||||
if (months > 0) {
|
||||
return `${months} month${months > 1 ? 's' : ''} ago`;
|
||||
}
|
||||
if (days > 0) {
|
||||
return `${days} day${days > 1 ? 's' : ''} ago`;
|
||||
}
|
||||
if (hours > 0) {
|
||||
return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes} min ago`;
|
||||
}
|
||||
return `${Math.round(seconds)} sec ago}`;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
CardTag,
|
||||
Flex,
|
||||
Form,
|
||||
RowData,
|
||||
Spinner,
|
||||
Stepper,
|
||||
Text,
|
||||
|
|
@ -26,25 +27,11 @@ 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>
|
||||
<RowData
|
||||
leftIcon={<NFAIconFragment image={nfa.logo} color={nfa.color} />}
|
||||
label={nfa.name}
|
||||
rightComponent={<CardTag css={{ minWidth: '$28' }}>Selected NFA</CardTag>}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -77,8 +64,8 @@ export const CreateAccessPointFormBody: React.FC = () => {
|
|||
},
|
||||
onCompleted(data) {
|
||||
if (data.token && id) {
|
||||
const { name, tokenId, logo, color, externalURL: domain } = data.token;
|
||||
setNfa({ name, tokenId, logo, color, domain });
|
||||
const { name, tokenId, logo, color, externalURL } = data.token;
|
||||
setNfa({ name, tokenId, logo, color, externalURL });
|
||||
} else {
|
||||
AppLog.errorToast("We couldn't find the NFA you are looking for");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import { Card, Flex, Icon, IconButton, Stepper } from '@/components';
|
||||
import {
|
||||
Card,
|
||||
CustomCardContainer,
|
||||
CustomCardHeader,
|
||||
Flex,
|
||||
Stepper,
|
||||
} from '@/components';
|
||||
|
||||
import { CreateAccessPointFormBody } from './create-ap-form-body';
|
||||
|
||||
|
|
@ -6,28 +12,8 @@ export const CreateAccessPointForm: React.FC = () => {
|
|||
const { prevStep } = Stepper.useContext();
|
||||
|
||||
return (
|
||||
<Card.Container css={{ width: '$107h' }}>
|
||||
<Card.Heading
|
||||
title="Enter Domain"
|
||||
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" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<CustomCardContainer>
|
||||
<CustomCardHeader.Default title="Enter Domain" onClickBack={prevStep} />
|
||||
<Card.Body>
|
||||
<Flex
|
||||
css={{
|
||||
|
|
@ -38,6 +24,6 @@ export const CreateAccessPointForm: React.FC = () => {
|
|||
<CreateAccessPointFormBody />
|
||||
</Flex>
|
||||
</Card.Body>
|
||||
</Card.Container>
|
||||
</CustomCardContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
import { Button, Card, Grid, SpinnerDot, Stepper, Text } from '@/components';
|
||||
import { Button, Card, Flex, SpinnerDot, Stepper, Text } from '@/components';
|
||||
import { bunnyCDNActions, useAppDispatch, useBunnyCDNStore } from '@/store';
|
||||
|
||||
import { useAccessPointFormContext } from '../ap-form-step';
|
||||
|
|
@ -49,9 +49,10 @@ export const APRecordCardBody: React.FC = () => {
|
|||
</Text>
|
||||
</Card.Text>
|
||||
) : (
|
||||
<Grid
|
||||
<Flex
|
||||
css={{
|
||||
rowGap: '$6',
|
||||
gap: '$6',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
|
|
@ -73,7 +74,7 @@ export const APRecordCardBody: React.FC = () => {
|
|||
>
|
||||
I added the record
|
||||
</Button>
|
||||
</Grid>
|
||||
</Flex>
|
||||
)}
|
||||
</Card.Body>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,29 +1,9 @@
|
|||
import { Card, Icon, IconButton, Stepper } from '@/components';
|
||||
import { CustomCardHeader, 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" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<CustomCardHeader.Default title="Create Record" onClickBack={prevStep} />
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue