diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7f11afb385..c601038141 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,17 +2,18 @@ ## Definition of Done -1. [ ] Acceptance criteria are met. -2. [ ] PR is manually tested before the merge by developer(s). +1. [ ] If required, the desciption of your change is added to the [QA changelog](https://www.notion.so/octantapp/Changelog-for-the-QA-d96fa3b411cf488bb1d8d9a598d88281) +2. [ ] Acceptance criteria are met. +3. [ ] PR is manually tested before the merge by developer(s). - [ ] Happy path is manually checked. -3. [ ] PR is manually tested by QA when their assistance is required (1). +4. [ ] PR is manually tested by QA when their assistance is required (1). - [ ] Octant Areas & Test Cases are checked for impact and updated if required (2). -4. [ ] Unit tests are added unless there is a reason to omit them. -5. [ ] Automated tests are added when required. -6. [ ] The code is merged. -7. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). +5. [ ] Unit tests are added unless there is a reason to omit them. +6. [ ] Automated tests are added when required. +7. [ ] The code is merged. +8. [ ] Tech documentation is added / updated, reviewed and approved (including mandatory approval by a code owner, should such exist for changed files). - [ ] BE: Swagger documentation is updated. -8. [ ] When required by QA: +9. [ ] When required by QA: - [ ] Deployed to the relevant environment. - [ ] Passed system tests. diff --git a/.github/workflows/deploy-pr.yml b/.github/workflows/deploy-pr.yml index a8703199ff..3c27a7d756 100644 --- a/.github/workflows/deploy-pr.yml +++ b/.github/workflows/deploy-pr.yml @@ -41,6 +41,7 @@ jobs: anvil-block-time: 5 decision-window: 1800 epoch-duration: 3600 + ipfs-gateways: 'https://ipfs.octant.wildland.dev/ipfs/' secrets: inherit run: diff --git a/.github/workflows/e2e-run.yml b/.github/workflows/e2e-run.yml index e0b1321f7b..ac979b838d 100644 --- a/.github/workflows/e2e-run.yml +++ b/.github/workflows/e2e-run.yml @@ -72,6 +72,7 @@ jobs: glm-claim-enabled: true vault-confirm-withdrawals-enabled: true anvil-block-time: 5 + ipfs-gateways: 'https://ipfs.octant.wildland.dev/ipfs/' secrets: inherit run-e2e-tests: diff --git a/.github/workflows/tpl-deploy-app.yml b/.github/workflows/tpl-deploy-app.yml index b009f240db..5ff8349dac 100644 --- a/.github/workflows/tpl-deploy-app.yml +++ b/.github/workflows/tpl-deploy-app.yml @@ -118,6 +118,10 @@ on: required: false default: true type: boolean + ipfs-gateways: + required: false + default: 'https://turquoise-accused-gayal-88.mypinata.cloud/ipfs/,https://octant.infura-ipfs.io/ipfs/' + type: string env: ENV_TYPE: ${{ inputs.env-type }} @@ -160,6 +164,7 @@ env: TESTNET_RPC_URL: "${{ secrets.TESTNET_RPC_URL }}" ETHERSCAN_API_KEY: "${{ secrets.ETHERSCAN_API_KEY }}" VITE_ALCHEMY_ID: "${{ secrets.VITE_ALCHEMY_ID }}" + IPFS_GATEWAYS: "${{ inputs.ipfs-gateways }}" MULTIDEPLOYER_ENABLED: ${{ inputs.multideployer-enabled }} SUBGRAPH_DEPLOY: ${{ inputs.subgraph-deploy }} GRAPH_HEALTCHECKER_ENABLED: ${{ inputs.graph-healtchecker-enabled }} diff --git a/backend/README.md b/backend/README.md index 5f56dc3565..f83fbd0747 100644 --- a/backend/README.md +++ b/backend/README.md @@ -64,7 +64,7 @@ yarn apitest:run # in a second console When backend code is changed, just re-run `yarn apitest:run`. To run just one test, use standard pytest naming: ``` -yarn apitest:run tests/legacy/test_api_snapshot.py::test_pending_snapshot +yarn apitest:run tests/api-e2e/test_api_snapshot.py::test_pending_snapshot ``` To stop the env, run `yarn apitest:down` diff --git a/backend/app/constants.py b/backend/app/constants.py index a30e418d9d..e5a4f09333 100644 --- a/backend/app/constants.py +++ b/backend/app/constants.py @@ -12,10 +12,11 @@ VALIDATOR_DEPOSIT_GWEI = 32_000000000 ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" MR_FUNDING_CAP_PERCENT = Decimal("0.2") -LOW_UQ_SCORE = Decimal("0.2") +LOW_UQ_SCORE = Decimal("0.01") MAX_UQ_SCORE = Decimal("1.0") +NULLIFIED_UQ_SCORE = Decimal("0.0") UQ_THRESHOLD_NOT_MAINNET = 5 -UQ_THRESHOLD_MAINNET = 20 +UQ_THRESHOLD_MAINNET = 15 BEACONCHAIN_API = "https://beaconcha.in/api" ETHERSCAN_API = "https://api.etherscan.io/api" @@ -87,478 +88,478 @@ "0xea809d3fb969d1d4de90c022c34b075b1fa5ec50", ] -GUEST_LIST = set( - [ - "0x16f3f2f0ba34973937A1ebb989a295Ca106b67C7", - "0xBB5935dAaFbacAE82c8D2CA8377F16073D70061a", - "0xba84B5cA750b33DfAdDBFdD1B7C6887885a34977", - "0x4e9A05226993F094A56A3472C8c816F2599423A6", - "0x40DE3299Bd8a10D8Ac3f32C1A55DE40640cF9B75", - "0xC33F87697EF41e0E95e7a55d1ec8180F04088578", - "0xCBc924183Bc32D02746Fa8D38843B5Ce08662eB4", - "0xDc9C5e34959eC3643AF1e1D34A83D6b251AAb1eF", - "0x762BBc211990D0a356F35E4D500843F59d223C2e", - "0x55187a1165EBB441A1BF227fff1EB0D32a65bc46", - "0x7aE59f3F2B2E5f3842B50a15bCb5247c5De881Be", - "0x59072B3a3287F4a75cadfb36D671A2f0d1959B09", - "0x4A5da2a1D3258dF8FFb431Cf0110FE9b98ADeEbf", - "0x514A9771Af8Afe71057666b680238dFBeA578d65", - "0xE70055e9575f15A6f51F3068901D73ac63952adF", - "0x9e831B58001e2b69F70C892e4F8ce9d2118B7E00", - "0x51c1C7f1e168a36Bf1FaBFD91E98b43476a6B14D", - "0x33878e070db7f70D2953Fe0278Cd32aDf8104572", - "0x3df13B9bd79158f0cccDDd0833cF774178e3d2e9", - "0xB9573982875b83aaDc1296726E2ae77D13D9B98F", - "0xE862E2C1ca94eAcfEDe3c95a217c15EF0086a29D", - "0x0442A9aBbc93058a873c371F21CC366338254A88", - "0x0194325BF525Be0D4fBB0856894cEd74Da3B8356", - "0x399e0Ae23663F27181Ebb4e66Ec504b3AAB25541", - "0x9f729294b308f79243285348A7Be3f58ae5ED31A", - "0xb62E762Af637b49Eb4870BCe8fE21bffF189e495", - "0x5725a458b319d73B8Ec84c47de80620E7B191B0C", - "0x57Ccc081824b43B75986727875929AF3A6Ad721C", - "0xf13e477365B0FAa64130DA2FF663aAb20d32d929", - "0xFD868dB0696ef762351F8421535cC5f9F423B23C", - "0x30043aAbBCeBbD887437Ec4F0Cfe6d4c0eB5CC64", - "0xAa01DeC5307CF17F20881A3286dcaA062578cea7", - "0x3FFD0C300fa4a021364Ae7e85a7b0d3a02133f99", - "0xBEa26DE685Ef828b60cA53b40Ecc9Bab35645fDF", - "0x4103CFcb300599dFcB31dBc95d919592619B4EAc", - "0x22bAac1E95efC010E35D5eD643BB16c9dB254a11", - "0x686A484bc2E2bE79f358c7055e8539A69413A3Ed", - "0x073a360C372FD51Bd6E56B4a4d73790fDAec4641", - "0xdd0206010CA82fF22303b58863b3a6f3006C86C4", - "0x25FA68A4c340202737EDBC67fD1a2Ec8DE872dB6", - "0x5b655EDa7D101f98934392Cc3610BcB25b633789", - "0x32cEfb2dC869BBfe636f7547CDa43f561Bf88d5A", - "0xA4369e39e3ED13593Adb0142A1ea5d08AbdF99C4", - "0xA8F0048A0d1A04663Ca5010d0bEaC5BCAEeA0eef", - "0x65F632cfe8015B7ae6976e549645ed04cde60fe4", - "0xb35E0a0D00c640ab75fAD3cf3B83264bC64D23eC", - "0xafA3E6E29D99337b166b83fB24bA17b19764B49D", - "0x57DD1517c12659365E59F71129Fa9B1611Dd18AF", - "0x9120FfD5d04ca4B26AaBCe611989A8F026dc099a", - "0x2AC6A3561a43f06d62602eF9728C2B9eEc393326", - "0x297Aa50D0557c865F6C9B0AA0a91f41C26E55eE6", - "0x9Ff46343d0b652D6e766F85f9aE91653869349a5", - "0xEd36bf0b2b17768E782Db2ece6A327055b2f3e9C", - "0xC28D2fDFE6d5a482d32f855457Bb5F8cAcdB32b1", - "0x1d44404C1C53991Ec33095225da173d544Cd4Af3", - "0x5d9fbd984B9CeC714a4B14c38Ea83bBC82d06d69", - "0x5Bc0AEbdbab698e12FD33A2E133e6858fe6Cdd76", - "0x66805D8B82664Acab4CbE0C0498889dDE9aF7841", - "0xaF7610578F54c7De7563655AaF461E2CbeCB08C6", - "0x6c3F373Baec5D2d0Fb3C82C4f3Db5E48873ae363", - "0x015122A625b45f68E6D795C0Ab99fC7107e4c3B9", - "0x508A4F07B60BA0940283Cd4e32d5DEb0CC38AdF7", - "0xb150c9bEd10a8C62997d58a81c4e1fA75160643e", - "0x212647c56BA10ee429a838bc567dFb03A8D054Ba", - "0x73306dAb0D39A4D47df4972c7022CB2cac075D4e", - "0x914D5d84aAA064207C2c31014426227405edab41", - "0x3FBcEC42405391B1fb377664daA5AE7Bc9Ba7BF5", - "0x8c8296a0042E842Cb865DfFD94678c941fD24bAE", - "0xf5c2087877218AA979Dd0e2e5108837199aF44D2", - "0x529dc928E67D8A43133D10769B308F1D5A629401", - "0xF1bb436c29E46B1987bC825879ffc9c34Ab97f99", - "0xFDDE7aE208B3596f1982D66F6BAe4cDabF29244b", - "0x02e4Cc9ffF7566563618fb21B3BB10Eab4B3D726", - "0xd8821dbbcb8ea0c14Bd1F0aCbFBeBB3Fd984269b", - "0x31d23825aFbda5B6B1690Bbdbbb8117B5ea0f8E4", - "0x731022D6De647991864203a35dFaD1A192240d07", - "0xEb5e0B8e80FCe271c13F533fA728D7bB03cafa4c", - "0xFC967DE4e029fdcD16B418DaC2147d282C93085b", - "0x801a6d6dBC1e40466E131aA21D951629A9efAB4e", - "0x4892139De0e73141438D9E55D593171C0Cc6B143", - "0x8124eFC94c951cF41D4B0B42794C678458a00726", - "0x81cc36DdA894256aa95458F78B4029381b09BDfb", - "0x4Dcb2BCA3450B427F3d1b424C885259D05363080", - "0x7Dd8030F9d33Af4a40ee074f990892E825132e61", - "0x432C53218A11bEd08d238Cf84ff547CE4fe933ab", - "0xE77ad9c5af60332D24E5531B51A6B7f61D0B8703", - "0x0f792e55668AD78476d4B563E6EB1228D636a71e", - "0x583bBaDA56bb535BCBb31877A620A6ff2A25CeA5", - "0x5C0E777dC8F3De6b0911b44DbBDD8Bf71b2E8e38", - "0x8a4a50B13Fd2cb36FeB96c408CB98B4c9F2b8F25", - "0x1e55C85801a2C4F0beC57c84742a8eF3d72dE57B", - "0x26d3bE736aB6b5D8A3266fFCC0895dDc1bc19a38", - "0x809C9f8dd8CA93A41c3adca4972Fa234C28F7714", - "0xfB94f39B150Ae661F85762154c0CadC65E083791", - "0x4B7C0Da1C299Ce824f55A0190Efb13663442FA2c", - "0x77E64560Bd6C323c075F206a5AB9dD6850F31609", - "0x0F46540678c7e6D2eF983B382CC07Fa815AB148c", - "0x82073f802547fEeEc0fd49719a3D7697fB66076a", - "0xBAab83De8DbA764bF02a530cad33555bD23eba22", - "0x890a0047f8D573347872cB6C019F86552f2367d6", - "0x14D92832265eeAFDEF9e526356FEfc90105966c3", - "0x512B436cB2Ed6016e80d4F89ca578F99DBBccb61", - "0x696Ee4AE0b15feae8ED1AfC865930e0ea65b1f3F", - "0xbb4D885fD41c807e8eCC2dD9e6295a7F96Adb0EB", - "0xB1dE969883b1FdD90a43fF475A5171a3CfEfe76d", - "0x7DBF6820D32cFBd5D656bf9BFf0deF229B37cF0E", - "0xfE2e3cCEE9714b29Ab2FB4E940e52672194815fC", - "0x57fb3f4b027fbaDbd8d20Eb5E48feb1e2b02DF30", - "0x9AE494FBAc34682c122A1D4e3D6ae1Eb5404A469", - "0xb2b9300475aF157676C44eE64d39a5eB3C294DbD", - "0x01Bc28E036b6e75247Fe8F49f0a8b9410b19d851", - "0xcCE9A28b570946123f392Cf1DbfA6D2D5e636a1f", - "0xb2a3b5B9d2C0f07cBA328b58737147cfc172EB9f", - "0xCC3d7F9fE6946979215A901BbA385a88FdabBBf4", - "0x38f80f8f76B1C44B2beeFB63bb561F570fb6ddB6", - "0xd82803b7B9A5EB1D5FC558FD619afC6c031cd0B1", - "0x844AeeD1B294Ef9632c18E73F57ef77D0A23D0e2", - "0x9cD7D1981B3e15a2DEE4d512ac60E0579Ae18546", - "0xEBCd250474C27cBaD3C56f3F34e08F97b370AC2d", - "0xDA47bdcC48f26FB4709f90316341D9104cB1fb89", - "0x5cbB6ad79008908aA125667D1300558D9253B589", - "0x1078DaA844CDF1EDB51E5189c8b113B80a6A6957", - "0x8341c4106523b49fc247f84e412Bb2AF5597038f", - "0xCe57ebEd9aC38402DcAA44f65a1c9b04e26b8283", - "0x2dd2036C9Db2ADA2739509AF0047c00C8b9291EF", - "0xa77294828d42B538890fa6E97AdFfE9305536171", - "0x8dA48e5846c06B558970ACd42EDc7Da8799481E4", - "0x50418699cB44BfDa9c9afc9B7a0b0d244d8927D2", - "0x936d69AbCD9acdC89455EEFAf744044fFC1CA660", - "0x90C32e6B29794Fd7f5BbA2BBEE74e924078B3f9b", - "0x362B7e0599E950b921ca9D86336ca409208FFDEC", - "0xd98aD1Fd4aa0E1c876d91968D1385aa9E1Aa98df", - "0xD2602C7bDFC9F413974e944280BbFae275d1B1b6", - "0x731A2e51ebfAeBacF8477E992CDEB1E8eacf519C", - "0x072d63796C4FE69B306a23E1D01156d51F7B3e16", - "0x051010142A0B9de7F0Fd8fb31d085407287F6381", - "0x8498843f6D9046f7b59482978E152D61869203bC", - "0xB48ef8e4e7Bef79ddF64d4424151f003a59BfbfB", - "0xb423A138fD171c28d90A5883A01ec92fF3D63609", - "0xAfA3a2528E8baAd576a83ffC52dB9f100dEbe307", - "0x055fdA7Eb509cc338C898b0F698B7624387AB813", - "0x0B3BD83E857997b370FaDC8504fB712244F6786C", - "0x8D12A71Cb933A4222d42feCBb4ba9c15e455305b", - "0xDEF3D19ff35a42F5b8E3c706c8fD287De72e6D15", - "0x19a2BC678785BAD6A947A87494D480DAD57711c6", - "0x2c3E79D3DCE90FB0886C89Ec602E61757E589a94", - "0xe8aa836a597a66724D678860D105561c13E95bFa", - "0x3352a3277d2B74A773Fa6E68a625FcB18E4Fc282", - "0x2df292AF809Fd693D94C7D17E36BE352e15Bb98a", - "0x269Aa10398Aaa695259C3E8211ab27a15004110C", - "0x02d9c84a495986b8b3C3347Ad16849DCB1b9793e", - "0x8FDA1Daa6a674C1726d1896E3054B9a82d123F12", - "0x1021e61f2cDd8bB295b0e64A20eBB7D8ec3734bf", - "0x58d7d9c971A613117E493062bEC1A6A5484f2780", - "0x2bb96f44b9709b02189A50B377755edC30bc65C7", - "0x7bE20B02095944657275eD608615A39931d783F2", - "0x4AA51a723882ee676FeC444D4561c5eE16c339E9", - "0x1B243D42F53924118646EFaec5b3f6116b563960", - "0x01b7348EC3fb20Ab1f40b97Cc82df44aeD360768", - "0xbf4C0104dbfb028f3484CfAC17BB22aa15E5c7E2", - "0xCc3B817D4ABa7698EaafB4C68E7688CF61B0BF46", - "0x572E1b86471c900Cd16AFa9cBB7701862D0e70cB", - "0x602Ac8C3f61b351be325FEeb58842EF557431c2e", - "0x8d0CD1AB81EaDa4F92C7cb5c8DBc25C69cc296AD", - "0xAE2C7AB762317DB453317b70f1f40145755fAfb7", - "0x7bdae9AAbE238188c4882D48a3aEE21288A38eD0", - "0x96e4152f00894f677d860023b9784d578bC1c145", - "0xF572C9b11E757d3580C7C7310630cd488E8EA736", - "0x3769092DBfa6eb34434fB5B7cf0eB06E710728F3", - "0xCA72c93172BA6EfF168E59e7F17C3C7A8FeA9B62", - "0x1c0AcCc24e1549125b5b3c14D999D3a496Afbdb1", - "0x7fC80faD32Ec41fd5CfcC14EeE9C31953b6B4a8B", - "0x5d36a202687fD6Bd0f670545334bF0B4827Cc1E2", - "0xe64113140960528f6AF928d7cA4f45d192286a7a", - "0xf6B6F07862A02C85628B3A9688beae07fEA9C863", - "0xD779aFEE481e3Df5cd0544F0e4353Cf534FD99Db", - "0x183bDB344A07Ee3D27f07AC4799A56E4A2fE5439", - "0xA8cadC2268B01395f8573682fb9DD00Bd582E8A0", - "0x75535661Ab25a468Dfb3137320a7568FeCda4832", - "0xd37ED782323A82e5BD55A92500E48FF5eFcc415E", - "0x03bB5bC3c8fdAB212A6b2B347a049133DfCB3A47", - "0x61987699055394c65355F2797D3e4e589f7FaBf4", - "0x2bC12061C8912505978472C21d4a23dB43AF62aA", - "0xad7575AEFd4d64520c3269FD24eae1b0E13dbE7B", - "0x0D89421D6eec0A4385F95f410732186A2Ab45077", - "0x04c0cD38B8c203b14ef2b7B8d736D69B938AFF71", - "0x0CF30daf2Fb962Ed1d5D19C97F5f6651F3b691c1", - "0x6EEb37b9757DcA963120f61c7E0e0160469A44D3", - "0x616caD18642F45d3fa5FCaaD0a2d81764A9cBa84", - "0xdC1d963D21C9c1bFf7b6Bea6e10080dAa9b4fc51", - "0x8073639B11994C549eDa58fC3cd7132a72aaDF10", - "0xe52C39327FF7576bAEc3DBFeF0787bd62dB6d726", - "0x8f21bD39FcAeA3A729D46339A383081ecB7E84E0", - "0x8Fb7087336678F36E42313f6130567A109a8e73d", - "0x276E69CdD336001afEF07075859A93078496C3c1", - "0x954F716e6de059360d278B773138f8e046696721", - "0x997D410b26CdD17b0750F2c1751e59cBcfaE446f", - "0xE8b6b71f3b1E6d2ad406D2cf36B1f2C567342dF1", - "0x83108A0653a14EAeB8301E7b10a37CfAc39C82f6", - "0xF95D9549b3Ab9470d306a6413Aa45082e8B66043", - "0x82d92494f6fFFB17A1DDFfd9B7d88D1d0a360009", - "0xA19947DA8B916f64Ac6F362cEC9001D8BCBeEe93", - "0x7ef5e4062dcCaD29A6F8d5458590160536056C81", - "0x653d973b36137A5cB2fc304996E0af1F1afCC628", - "0x5F319CA6Ecf072A4d183edAa711Cd04dC225df19", - "0x4D32D90D6535bD4e7eaBaa27EE72932cB214BbfA", - "0x73b9f6a6e52aCE2797F0a6E52AAc530Ed1F2a2Af", - "0xaA3600788b72863ff51C8f0dB5F10bB65fbFeAB4", - "0xf93F0b770784602fC3079eb1D2fB1Ff488Bb02B0", - "0xC8Ddd59c496D04C4C060Ab5038d03d661DDC2617", - "0xc42c77b6B2A2B220b9502F357bBf51334Db3C93f", - "0x2615214F8200B526a7B1eACe03971F2672B48CF2", - "0x9d8d7220D060fd12Ca33336B7239688e366327dE", - "0x9e602c1920443F01Cb100a57A7F894df8Eb42f66", - "0x7e651F5f597436cD0fa941F5FF2cD45Ef3F2Fda8", - "0x8e30Dc2AEF957B1F7dd67B1b7bC651fFe7E17a06", - "0x597dC4159a4b85c086c3C679a0B6c8Fe2836886F", - "0x7fBdE8B27D2B4F164B66F2a9dc02bbD6697e5b19", - "0xf5819cC26F0481c9b86294B4c24027518a04BD5B", - "0x8e7D20638947132B0e6E1aFdE2da1B103aFF9280", - "0xCBA711BEF21496Cfd66323d9AEA8C8EFd0F43e9d", - "0xdfBaeeF21396BF205D4B7D23345155489072Cf9B", - "0x3B981fA5dD50237dAb6F96A417A6690B6f20FcC4", - "0x6C31212a23040998E1D1c157ACe3982aBDBE3154", - "0xCDdF772F8A3295C89DC37510E16e360ee2d29789", - "0x002B5dfB3C71E1dC97A2e5A0A7f69F3e7b83F269", - "0xAD7A185b2456d5AFD85838A50C7d8aCE3aB2f871", - "0x7993F18C91A9f68593d308C5846f380A2a374F46", - "0xc5d82775c9bc5272B1225DB8D62b7034e064BA91", - "0x8bfcF8cb383149D4Ef37e7A609cEc195CDCbE099", - "0xA515F7fB260095eebC860425493b8761B4FC9abd", - "0xaA95cA26c92b0634dF7a1A1504f579F13bFB7f9d", - "0xC2812325caD4C4C782CbbC1164e9373371D31dB2", - "0x4831DdB6502ca45dbEEDf58B47292061Cdb6050B", - "0x6733c60E6E02f9C8FA221Db1aeA018d80D949861", - "0xCaD3887923B39cD2b0B6d13538C4ecB7C5EE9825", - "0x4520cD8BC085B962eF8c0ec696ac9D3Ef1d8bf55", - "0x7D85fCbB505D48E6176483733b62b51704e0bF95", - "0x27259b0F4209e76f8C6Cf27106C9FF83BdC2E831", - "0xE04885c3f1419C6E8495C33bDCf5F8387cd88846", - "0x23ee51e614cBF138e4cAbA9EC5ed4fF7D27A8596", - "0x2cab4d881962D247218356B32aBc4AA5c46bA0d2", - "0x1c0A032954f20761E59138feE236204bECbb8bdb", - "0x701d0ECB3BA780De7b2b36789aEC4493A426010a", - "0x1Ec3C1f70E1D6bBDC84092ae86eAaDE495fdDB9b", - "0xB53b0255895c4F9E3a185E484e5B674bCCfbc076", - "0x770569f85346B971114e11E4Bb5F7aC776673469", - "0x8289432ACD5EB0214B1C2526A5EDB480Aa06A9ab", - "0xdca6F7CB3cF361C8dF8FDE119370F1b21b2fFf63", - "0x117e1EbB7D05545064850513021dF6ADe3C1690B", - "0x7fb43C99a26a9EA8ba841d58390BF1C2996EDFB0", - "0x84B5a60Df2d7e3397B3A4A3c6282f090304Aca26", - "0x72F434Fa010929656AeF58695dab85447E51Fbc6", - "0xA29b0D2F3b4555359A1bF684d700753b1b06cBc4", - "0x4318cC449b1cbE6d64dd82E16abE58C79E076C2B", - "0x8F48282e50B0210bd7c7DD69C54205E98b9Ef5D9", - "0xa305B293e44A82f3Cd489b5fB26084647bb5D8ae", - "0xd9e5De13eF1dBC4DFE0Ee1BB76276228b9B23d0f", - "0x4AcEEB7bF9ec8104CC2379f1E8D648Ee47249FCb", - "0x0743542070891051861f8D0a4550f97B43B0B89a", - "0x58aD805f26272C5Ba06D24Bd0E43c8a2d1c634D9", - "0xE6ED9C681967a4EA7Cef4486942b800139DfB000", - "0x51b9C1Df35B044b5c0099D1fD07EAb7cE38f325d", - "0x55DFFA17578e6bAcE42e4Bf8687A11A85cCfEF97", - "0x1FAE8f99E9F932BdBA910061590C2156eE512A91", - "0xA25207Bb8f8EC2423E2ddf2686A0CD2048352f3E", - "0x746bb7beFD31D9052BB8EbA7D5dD74C9aCf54C6d", - "0x38bc91AA6Aa434c4fae7E666F68C859292deEd95", - "0xA3aD5CFb4FF4B68e37A338Da200BA441C1850B5b", - "0x4bfb2c232F70c83136a3F206cd26Df2A0B605cEC", - "0xf5AB6B4a8d578807491ef59cE855982990932617", - "0x1Fdd220E14b59E26bf1888e8267C4C221983a0A6", - "0xE2D6AFF297b41881c1aEA9599F68AEDFAB38C651", - "0x7d547666209755FB833f9B37EebEa38eBF513Abb", - "0xb681B19bb1F7e9F3C2AE0EDeab368c2afaa4e590", - "0x7Eb84E42059F0D44269C50f4D3A280Fd307a6824", - "0x84f0620A547a4D14A7987770c4F5C25d488d6335", - "0x4Ae6a8A28c87b75e935a90D6128F2649C969c0D8", - "0xb79223E868871DBAc27E8E301f73734abd4Cc628", - "0x6F219Bd1167568aB67494A9067CbbB5679bf0022", - "0x9Ff548c1B3eA3dd123AFE39C759dDA548009B6C8", - "0x3085051F89666E7124e7Ab95b693Fc1E09770907", - "0xa25211B64D041F690C0c818183E32f28ba9647Dd", - "0x6166E1964447E0959bC7c8d543DB3ab82dB65044", - "0x76E059C6FF6bf9FFFD5f33AFdf4AB2FD511C9DF4", - "0x4CC9E6fABb800F083a2685501d1A30CdAbb4B2De", - "0x5f3371793285920351344a1EaaAA48d45e600652", - "0xAFE2b51592b89095A4cFb18da2B5914b528f4c01", - "0xe3F4F3aD70C1190EC480554bbc3Ed30285aE0610", - "0xE0D8926A51F9A1dD8E089D9a3DD88F88fFb2F1Dc", - "0xa6c366D97cb64708211f24310dFAd5363BC96a04", - "0xB7562F12E41C762CeCDA99d62Bd6EAC7b0C3B4c1", - "0x301605C95acbED7A1fD9C2c0DeEe964e2AFBd0C3", - "0x5d47e5D242a8F66a6286b0a2353868875F5d6068", - "0x0ea26051F7657d59418da186137141CeA90D0652", - "0x88f1706c20d94A4d1551C5F799C9E3380A24C3AC", - "0xFB40932271Fc9Db9DbF048E80697E2Da4AA57250", - "0x40Db8365d1252bcb06598927698238a99D39228E", - "0xaCf4C2950107eF9b1C37faA1F9a866C8F0da88b9", - "0x144c4E5027B69f7798B2B162D924BcAE5c149f15", - "0xeeE844540644b204f0005c063Ae95F244BF06a84", - "0x014607F2d6477bADD9d74bF2c5D6356e29a9b957", - "0x1E8eE48D0621289297693fC98914DA2EfDcE1477", - "0x4AdA1B9D9fe28aBd9585f58cfEeD2169A39e1c6b", - "0x31460f49EEA93Ef8255b42be019FB96F89Cf0c49", - "0x63A32F1595a68E811496D820680B74A5ccA303c5", - "0x022ca32d31da3Ef85922AAFD9aD29C5b2418172C", - "0x93B109C3c279bcBbB673Ed1ae1A8BB2dE8eEf9da", - "0x689476323Eb5e9A5DEd342F54B562fc2c156A522", - "0x1C9F765C579F94f6502aCd9fc356171d85a1F8D0", - "0xe0144FA05A0d32B5B1De10CcEe7211616B3E3EF0", - "0x6C965b656C450259a6D4d95A2E68Fb4319EecBc0", - "0xE36BD8C15a83b89E2E49806d7312846069755C63", - "0x59DDA36bD196Ec849838CE2163E6821f946b37Dc", - "0xDd31dB93082a3A71b98D37ba26230f8734Bd63C3", - "0x83c98211C50480e457a0dF930d2A56a891BC4d4b", - "0x11FA934f6754076AEb7Cf0A72a1c2D2518aA4C77", - "0x2B888954421b424C5D3D9Ce9bB67c9bD47537d12", - "0x2383A8b8cC8561a65871F1d2783B7C52e22B62c1", - "0xCED608Aa29bB92185D9b6340Adcbfa263DAe075b", - "0x841AD0AbAb2D33520ca236A2F5D8b038adDc12BA", - "0x76d2DDCe6b781e66c4B184C82Fbf4F94346Cfb0D", - "0xf21e38ac177B48fDE02dB7F2CA97466AE8Eae87D", - "0x7537Cb0AEe6a3483a7601ebf1084eD4df73166Ab", - "0x5f0bD06A71E038206ef3e5090eB448E9a9773772", - "0x3C0c7B44c1F9366271F5c491121a1F7d55d33eF5", - "0xa96a437eFb71bAF50A59027C340FA3362ef703F7", - "0x55bA9c90c37e3206570AC9dc872c0f053d155F77", - "0xC68bba423525576C7684e7ea25E7D5F079b1361E", - "0x78E87757861185Ec5e8C0EF6BF0C69Fa7832df6C", - "0xCb36F8580A36788A48518dEC95Ea458357E64E30", - "0x25854e2a49A6CDAeC7f0505b4179834509038549", - "0x639749b7b08aEe65039c21d8a411103C6ceBEBF0", - "0xF517529866d371F04780885923F739bc17694BFb", - "0xC728DEa8B2972E6e07493BE8DC2F0314F7dC3E98", - "0x33f6EE932cEa603Fafd6854827259bE172C91Da4", - "0x6D97d65aDfF6771b31671443a6b9512104312d3D", - "0xB7BaBe35CE543e2Cf2F615CB1c792a2025feb572", - "0x4D9e86a5AC368Aa4Df0473eF07e13Ec2Fbe04025", - "0xaa79B87DC8B046A5E4f7D03F1562D7fe5BF98737", - "0xE71FbB197BC8fD11090FA657C100d52Dbb407662", - "0xB22981bA3FE1De2325935c91a3B717168fB86714", - "0xf389dD1F828525b449D63D14157f2d3A25eE0a41", - "0x877B37D3E5467B4aAE7687Dd3480A46C8D3e16Be", - "0xf9e1D1e9F22c96752356AdFd377231528c7E851E", - "0x187089B33E5812310Ed32A57F53B3fAD0383a19D", - "0xF1659A2FD5007192314F9676e6a4a39FD1202160", - "0xFdd210ce1b829E837D9e034DAE0F0312F176cef6", - "0xaCE1f1c6c5c89AE3Fc3209ff92e7120fb74445aA", - "0x6Ceb397b68059Ca73049874D0a30c62500aE9877", - "0xC46c67Bb7E84490D7EbdD0b8ecDaca68Cf3823F4", - "0xbb2eb4c7eB36ECce7A3E6bc501590CE12c9c1050", - "0x9Cf251A782cE7310D5bec0fe0a1C2B826d962545", - "0x43930Ff04D18a5B59805151c1Eb403C55870641E", - "0xA270f5649A42feDfE66ddb3b0b50bebAe1e3DDB0", - "0xd3488EA0c1DC99a5d72F75c84004224f8b58694E", - "0x7aBa691D12D8eF8793F1643eBa66b69C70EC72f1", - "0x8558f502887a9a52c4B265d72327E0E529Ff790d", - "0xA906c85B7e809b79c5e69d485693B44d65B1B252", - "0x3abdC9ed5f5dE6A74CFeb42a82087C853E160E76", - "0x30C7F4F7504D6366916f669cd8E731ED4dF6C702", - "0xed8DB37778804A913670d9367aAf4F043AAd938b", - "0xc191a29203a83eec8e846c26340f828C68835715", - "0xa32aECda752cF4EF89956e83d60C04835d4FA867", - "0x059F7da59Ad1EB412B4d2fFc12E9B50Da91cFdb6", - "0x85BEad65c61dB8cF230b3ec30552B8b3E6388570", - "0xF3Ad97364bcCC3eA0582Ede58C363888f8C4ec85", - "0x3F87755E2974534888Ddb20A52dCE45Ef9f204AB", - "0x757CC91CcBB88cB0d78d6798D20051d39E5A7296", - "0xF553C8223cA8542Af9Db7b916Fe9dc7c28b73751", - "0x40f9bf922c23c43acdad71Ab4425280C0ffBD697", - "0x9600e2eE6377DAdad7299B120026661c336A5e6d", - "0x516fCA170bfE24BFC54e01F215EF85Fe9B5B798A", - "0x61C820e261717A5A0555488872F78ac7b9CE77Ec", - "0xEb263241eB948Cc0eB53A58bf743289D074F474F", - "0x841C11b14c428dd591093348B8Afa2652C863988", - "0x3c114973c0260290C2dbD40323327d996972FCeB", - "0x765a16ca391A6b9249cfA65bf2D14C38722198e3", - "0xC3268DDB8E38302763fFdC9191FCEbD4C948fe1b", - "0x6B92686c40747C85809a6772D0eda8e22a77C60c", - "0xc799bE8De03F20B2D3b101E6F6516D614e6fFe06", - "0x40Dc654af5cE40C122ffDC679fa8E8ca8b91556A", - "0xCE8D52c38d74B77a0aA361c48Fdce6b220A3370e", - "0xEfa4c696Ea2505ec038c9dDC849b1bf817d7f69d", - "0xf7253A0E87E39d2cD6365919D4a3D56D431D0041", - "0xcf79C7EaEC5BDC1A9e32D099C5D6BdF67E4cF6e8", - "0xff75E131c711e4310C045317779d39B3B4f718C4", - "0xdE2BE7C9C542c55a7a77489A3A7745493988947F", - "0xFeB3E0f50107f6cfB2EC8C2bC8287f2707E0E2Ea", - "0x6b759Bf480407D19c8903c16023c706868c29a2A", - "0x6E38911dA6Dd0379F1CaC396F74387c95A1f0D21", - "0x5a5D9aB7b1bD978F80909503EBb828879daCa9C3", - "0xe96056A9936C58e89D1703cF6bD97F134341EE44", - "0x4dD6720D2Bb8721A46bdF9a528704164578E03B9", - "0xE83B9A1B9056B21a01b85162E77AD76a42A1c64B", - "0xbeC48f1cCf82d8e4C983Ee00Ac2eC6B03B81d710", - "0xEFEdaf9c07E6eB56BB8F82f30018e4461B1c5F4c", - "0xB68da7fbF71383Afab240839287878539cFFf20b", - "0xfBDDB719cC7c795a1D9b7EA7aC11494A19b3231F", - "0x07506a5F48D71fDB34D3900fB086D43EF1B58FF9", - "0xa85cdd5478B7E525a808eF9707c3e33432cE1e7F", - "0xCf7C21DeD40f2Df85A564207A89b3379780d9CE3", - "0xd26b76e50f6510cdD4bf45d59279705f36946d23", - "0xdb7a41e39807E8C988859f150296Db92674b7dc7", - "0x719028736f10164c838Ef129936779eD739312f2", - "0xaBCdef0AbbA5D0106595174213156797bc0DB33E", - "0x3D2b8879f97e413b2609F9844A5fc8dB8FE4f6F8", - "0x81EbE8Ee7b51741fD5DaD31F6987E626A9bb8111", - "0x1D45c8fa65F6b18E7dAe04b2efEa332c55696DaA", - "0x978eB534b26CB8749D352a2C94EC21e659e4248d", - "0xa7CA400d49BBa87EB606ee05af93689BD21FaB99", - "0x65ad2BF7E09af2597C140dF6386a3003d0F5f8Ee", - "0x835918a3fBDf946364a9aee3114173865b712663", - "0x6073cFfc1D46b1eA57BA89A28074cA734aCD7003", - "0x2B13D52dFd33E2eBd13232866fDf96088e77d596", - "0x55F5601357f6e0B10a3386914c93916c6C9A368A", - "0xA1D5D2d931b532A0503e97f540f65ed256f374e6", - "0x6C9258895FFBE2178b3EdEfE09AF304a1e99bF2F", - "0x973375b099943cDdFd390022CeA90D4F1d0c493c", - "0x8A8C879D39A74fCE0593714956bB7Ed048A5c1BF", - "0x9c42B0c70D0dAF1211f3aab2A1E6EC5E717dE12a", - "0x81a6383041593c556d1c8e69e2749b35b5008F09", - "0xF41b98a4C32bB61468C8001E0C69Ef64ce6DEa57", - "0x8FaE81bb674c89cCDE35a386587333D074b57786", - "0xa8258ED271BB9be9d7E16c5818E45eF6F2577d92", - "0x1e90474D2E83e7B7dD45553156Beb316845E66A4", - "0x2cCbbC4c10F5d807FDd447219B57D0b883a28DC8", - "0x1bBeAc736875c5043486A8a4374E6B5616eC8883", - "0x95add3DfEF3AE0A832607Dc71C4A9C6A6C2D7Eb7", - "0x744c6Eed427aF293b0106B46700fdDD3C9f62Eef", - "0x743Ec55fc166D24D2FD0211fb6Ce53926D0Ff3b1", - "0xDd03d2434C02c6BfFb097b7130528F9568b6C70d", - "0x97C12EFA574923E3ee445370d2dE432332857110", - "0xB69951a0642b55CD5731535ed5B290Fa49D3454A", - "0xBA56878729540404dE2aa14561b451aE2350744a", - "0x8D247f4Fbbe81429d3D164a5c9Ae0063210edBdC", - "0x850a146D7478dAAa98Fc26Fd85e6A24e50846A9d", - "0xfE1552DA65FAcAaC5B50b73CEDa4C993e16d4694", - "0x9705FE3586a7D768Fee061aAfE9384b1D4B8F2D8", - "0x5554672e67bA866B9861701D0e0494AB324aD19A", - "0xacc5c1e73d70F7F9622De2d574885Ce8E6981033", - "0xbe9E7b0ed19526544B55b697107231f9467a805f", - "0x172DBab6f5E62A1FE7E2bA5eA1624ADB33e0aa14", - "0x96725Fa2F9A0b5bAf80fC36C20C2cA79d86424ed", - "0xa392cCadABFf735dbFF69dC93d7C13f34A30611b", - "0xEbF0e04E47F726D0f44801dFEC5e705aDcd6694b", - "0xC0891e8FCeA09680BFe9170809fad1BCCa10b96b", - "0xA21000E7A5A2A2Bd9329428A859f9d7dcE0f0961", - "0x9A387307F7508DE113092BaFC5CB4B3AE0706521", - "0xBA719E0197470A790726075fD98EDEF04E2467af", - "0xda08BE028304db1A73a13Bce7C943127C2E393dB", - "0xfB4a965A35603010FeAcC648cA022Cb6A12D33F5", - "0x3Aa73ed90e9f0CEd87ff99CB60cA79019279e6CE", - "0x150bB505A9259b0be44FFb15415C79199E83c445", - "0xB170A41F2523220A12F84f17A54bD31953D98027", - "0x2Fcd65d9c8078644adCf1CB0cd70A1b61F3F9C5b", - "0x73006C818880d07dD510e165C3De3E74F2407187", - "0x747e6ABc102222f1dF65C662540dDf471241a644", - "0xeeEe5D271A56Aa09C4F8862aF514ADD3E882857c", - "0x98Ad82AB467bc8c70e0CC183a5826d903751b7d8", - "0xC624434420f6CbE835D6358A8223b78432773cEd", - "0x848e313d4b25bC0B48CaFdB6A72391E892E6A247", - "0x0025Ab2d69F6c2C3Ffac32Ab6A16e18c807518B8", - "0x2efe744ecc4F6BD55538da57D09DAE895C95b223", - "0xBc6d82D8d6632938394905Bb0217Ad9c673015d1", - "0xe1555c6EE61366a3f90135Dc704Acd25C3247ACA", - "0x2f51E78ff8aeC6A941C4CEeeb26B4A1f03737c50", - ] -) +GUEST_LIST = { + "0x16f3f2f0ba34973937A1ebb989a295Ca106b67C7", + "0xBB5935dAaFbacAE82c8D2CA8377F16073D70061a", + "0xba84B5cA750b33DfAdDBFdD1B7C6887885a34977", + "0x4e9A05226993F094A56A3472C8c816F2599423A6", + "0x40DE3299Bd8a10D8Ac3f32C1A55DE40640cF9B75", + "0xC33F87697EF41e0E95e7a55d1ec8180F04088578", + "0xCBc924183Bc32D02746Fa8D38843B5Ce08662eB4", + "0xDc9C5e34959eC3643AF1e1D34A83D6b251AAb1eF", + "0x762BBc211990D0a356F35E4D500843F59d223C2e", + "0x55187a1165EBB441A1BF227fff1EB0D32a65bc46", + "0x7aE59f3F2B2E5f3842B50a15bCb5247c5De881Be", + "0x59072B3a3287F4a75cadfb36D671A2f0d1959B09", + "0x4A5da2a1D3258dF8FFb431Cf0110FE9b98ADeEbf", + "0x514A9771Af8Afe71057666b680238dFBeA578d65", + "0xE70055e9575f15A6f51F3068901D73ac63952adF", + "0x9e831B58001e2b69F70C892e4F8ce9d2118B7E00", + "0x51c1C7f1e168a36Bf1FaBFD91E98b43476a6B14D", + "0x33878e070db7f70D2953Fe0278Cd32aDf8104572", + "0x3df13B9bd79158f0cccDDd0833cF774178e3d2e9", + "0xB9573982875b83aaDc1296726E2ae77D13D9B98F", + "0xE862E2C1ca94eAcfEDe3c95a217c15EF0086a29D", + "0x0442A9aBbc93058a873c371F21CC366338254A88", + "0x0194325BF525Be0D4fBB0856894cEd74Da3B8356", + "0x399e0Ae23663F27181Ebb4e66Ec504b3AAB25541", + "0x9f729294b308f79243285348A7Be3f58ae5ED31A", + "0xb62E762Af637b49Eb4870BCe8fE21bffF189e495", + "0x5725a458b319d73B8Ec84c47de80620E7B191B0C", + "0x57Ccc081824b43B75986727875929AF3A6Ad721C", + "0xf13e477365B0FAa64130DA2FF663aAb20d32d929", + "0xFD868dB0696ef762351F8421535cC5f9F423B23C", + "0x30043aAbBCeBbD887437Ec4F0Cfe6d4c0eB5CC64", + "0xAa01DeC5307CF17F20881A3286dcaA062578cea7", + "0x3FFD0C300fa4a021364Ae7e85a7b0d3a02133f99", + "0xBEa26DE685Ef828b60cA53b40Ecc9Bab35645fDF", + "0x4103CFcb300599dFcB31dBc95d919592619B4EAc", + "0x22bAac1E95efC010E35D5eD643BB16c9dB254a11", + "0x686A484bc2E2bE79f358c7055e8539A69413A3Ed", + "0x073a360C372FD51Bd6E56B4a4d73790fDAec4641", + "0xdd0206010CA82fF22303b58863b3a6f3006C86C4", + "0x25FA68A4c340202737EDBC67fD1a2Ec8DE872dB6", + "0x5b655EDa7D101f98934392Cc3610BcB25b633789", + "0x32cEfb2dC869BBfe636f7547CDa43f561Bf88d5A", + "0xA4369e39e3ED13593Adb0142A1ea5d08AbdF99C4", + "0xA8F0048A0d1A04663Ca5010d0bEaC5BCAEeA0eef", + "0x65F632cfe8015B7ae6976e549645ed04cde60fe4", + "0xb35E0a0D00c640ab75fAD3cf3B83264bC64D23eC", + "0xafA3E6E29D99337b166b83fB24bA17b19764B49D", + "0x57DD1517c12659365E59F71129Fa9B1611Dd18AF", + "0x9120FfD5d04ca4B26AaBCe611989A8F026dc099a", + "0x2AC6A3561a43f06d62602eF9728C2B9eEc393326", + "0x297Aa50D0557c865F6C9B0AA0a91f41C26E55eE6", + "0x9Ff46343d0b652D6e766F85f9aE91653869349a5", + "0xEd36bf0b2b17768E782Db2ece6A327055b2f3e9C", + "0xC28D2fDFE6d5a482d32f855457Bb5F8cAcdB32b1", + "0x1d44404C1C53991Ec33095225da173d544Cd4Af3", + "0x5d9fbd984B9CeC714a4B14c38Ea83bBC82d06d69", + "0x5Bc0AEbdbab698e12FD33A2E133e6858fe6Cdd76", + "0x66805D8B82664Acab4CbE0C0498889dDE9aF7841", + "0xaF7610578F54c7De7563655AaF461E2CbeCB08C6", + "0x6c3F373Baec5D2d0Fb3C82C4f3Db5E48873ae363", + "0x015122A625b45f68E6D795C0Ab99fC7107e4c3B9", + "0x508A4F07B60BA0940283Cd4e32d5DEb0CC38AdF7", + "0xb150c9bEd10a8C62997d58a81c4e1fA75160643e", + "0x212647c56BA10ee429a838bc567dFb03A8D054Ba", + "0x73306dAb0D39A4D47df4972c7022CB2cac075D4e", + "0x914D5d84aAA064207C2c31014426227405edab41", + "0x3FBcEC42405391B1fb377664daA5AE7Bc9Ba7BF5", + "0x8c8296a0042E842Cb865DfFD94678c941fD24bAE", + "0xf5c2087877218AA979Dd0e2e5108837199aF44D2", + "0x529dc928E67D8A43133D10769B308F1D5A629401", + "0xF1bb436c29E46B1987bC825879ffc9c34Ab97f99", + "0xFDDE7aE208B3596f1982D66F6BAe4cDabF29244b", + "0x02e4Cc9ffF7566563618fb21B3BB10Eab4B3D726", + "0xd8821dbbcb8ea0c14Bd1F0aCbFBeBB3Fd984269b", + "0x31d23825aFbda5B6B1690Bbdbbb8117B5ea0f8E4", + "0x731022D6De647991864203a35dFaD1A192240d07", + "0xEb5e0B8e80FCe271c13F533fA728D7bB03cafa4c", + "0xFC967DE4e029fdcD16B418DaC2147d282C93085b", + "0x801a6d6dBC1e40466E131aA21D951629A9efAB4e", + "0x4892139De0e73141438D9E55D593171C0Cc6B143", + "0x8124eFC94c951cF41D4B0B42794C678458a00726", + "0x81cc36DdA894256aa95458F78B4029381b09BDfb", + "0x4Dcb2BCA3450B427F3d1b424C885259D05363080", + "0x7Dd8030F9d33Af4a40ee074f990892E825132e61", + "0x432C53218A11bEd08d238Cf84ff547CE4fe933ab", + "0xE77ad9c5af60332D24E5531B51A6B7f61D0B8703", + "0x0f792e55668AD78476d4B563E6EB1228D636a71e", + "0x583bBaDA56bb535BCBb31877A620A6ff2A25CeA5", + "0x5C0E777dC8F3De6b0911b44DbBDD8Bf71b2E8e38", + "0x8a4a50B13Fd2cb36FeB96c408CB98B4c9F2b8F25", + "0x1e55C85801a2C4F0beC57c84742a8eF3d72dE57B", + "0x26d3bE736aB6b5D8A3266fFCC0895dDc1bc19a38", + "0x809C9f8dd8CA93A41c3adca4972Fa234C28F7714", + "0xfB94f39B150Ae661F85762154c0CadC65E083791", + "0x4B7C0Da1C299Ce824f55A0190Efb13663442FA2c", + "0x77E64560Bd6C323c075F206a5AB9dD6850F31609", + "0x0F46540678c7e6D2eF983B382CC07Fa815AB148c", + "0x82073f802547fEeEc0fd49719a3D7697fB66076a", + "0xBAab83De8DbA764bF02a530cad33555bD23eba22", + "0x890a0047f8D573347872cB6C019F86552f2367d6", + "0x14D92832265eeAFDEF9e526356FEfc90105966c3", + "0x512B436cB2Ed6016e80d4F89ca578F99DBBccb61", + "0x696Ee4AE0b15feae8ED1AfC865930e0ea65b1f3F", + "0xbb4D885fD41c807e8eCC2dD9e6295a7F96Adb0EB", + "0xB1dE969883b1FdD90a43fF475A5171a3CfEfe76d", + "0x7DBF6820D32cFBd5D656bf9BFf0deF229B37cF0E", + "0xfE2e3cCEE9714b29Ab2FB4E940e52672194815fC", + "0x57fb3f4b027fbaDbd8d20Eb5E48feb1e2b02DF30", + "0x9AE494FBAc34682c122A1D4e3D6ae1Eb5404A469", + "0xb2b9300475aF157676C44eE64d39a5eB3C294DbD", + "0x01Bc28E036b6e75247Fe8F49f0a8b9410b19d851", + "0xcCE9A28b570946123f392Cf1DbfA6D2D5e636a1f", + "0xb2a3b5B9d2C0f07cBA328b58737147cfc172EB9f", + "0xCC3d7F9fE6946979215A901BbA385a88FdabBBf4", + "0x38f80f8f76B1C44B2beeFB63bb561F570fb6ddB6", + "0xd82803b7B9A5EB1D5FC558FD619afC6c031cd0B1", + "0x844AeeD1B294Ef9632c18E73F57ef77D0A23D0e2", + "0x9cD7D1981B3e15a2DEE4d512ac60E0579Ae18546", + "0xEBCd250474C27cBaD3C56f3F34e08F97b370AC2d", + "0xDA47bdcC48f26FB4709f90316341D9104cB1fb89", + "0x5cbB6ad79008908aA125667D1300558D9253B589", + "0x1078DaA844CDF1EDB51E5189c8b113B80a6A6957", + "0x8341c4106523b49fc247f84e412Bb2AF5597038f", + "0xCe57ebEd9aC38402DcAA44f65a1c9b04e26b8283", + "0x2dd2036C9Db2ADA2739509AF0047c00C8b9291EF", + "0xa77294828d42B538890fa6E97AdFfE9305536171", + "0x8dA48e5846c06B558970ACd42EDc7Da8799481E4", + "0x50418699cB44BfDa9c9afc9B7a0b0d244d8927D2", + "0x936d69AbCD9acdC89455EEFAf744044fFC1CA660", + "0x90C32e6B29794Fd7f5BbA2BBEE74e924078B3f9b", + "0x362B7e0599E950b921ca9D86336ca409208FFDEC", + "0xd98aD1Fd4aa0E1c876d91968D1385aa9E1Aa98df", + "0xD2602C7bDFC9F413974e944280BbFae275d1B1b6", + "0x731A2e51ebfAeBacF8477E992CDEB1E8eacf519C", + "0x072d63796C4FE69B306a23E1D01156d51F7B3e16", + "0x051010142A0B9de7F0Fd8fb31d085407287F6381", + "0x8498843f6D9046f7b59482978E152D61869203bC", + "0xB48ef8e4e7Bef79ddF64d4424151f003a59BfbfB", + "0xb423A138fD171c28d90A5883A01ec92fF3D63609", + "0xAfA3a2528E8baAd576a83ffC52dB9f100dEbe307", + "0x055fdA7Eb509cc338C898b0F698B7624387AB813", + "0x0B3BD83E857997b370FaDC8504fB712244F6786C", + "0x8D12A71Cb933A4222d42feCBb4ba9c15e455305b", + "0xDEF3D19ff35a42F5b8E3c706c8fD287De72e6D15", + "0x19a2BC678785BAD6A947A87494D480DAD57711c6", + "0x2c3E79D3DCE90FB0886C89Ec602E61757E589a94", + "0xe8aa836a597a66724D678860D105561c13E95bFa", + "0x3352a3277d2B74A773Fa6E68a625FcB18E4Fc282", + "0x2df292AF809Fd693D94C7D17E36BE352e15Bb98a", + "0x269Aa10398Aaa695259C3E8211ab27a15004110C", + "0x02d9c84a495986b8b3C3347Ad16849DCB1b9793e", + "0x8FDA1Daa6a674C1726d1896E3054B9a82d123F12", + "0x1021e61f2cDd8bB295b0e64A20eBB7D8ec3734bf", + "0x58d7d9c971A613117E493062bEC1A6A5484f2780", + "0x2bb96f44b9709b02189A50B377755edC30bc65C7", + "0x7bE20B02095944657275eD608615A39931d783F2", + "0x4AA51a723882ee676FeC444D4561c5eE16c339E9", + "0x1B243D42F53924118646EFaec5b3f6116b563960", + "0x01b7348EC3fb20Ab1f40b97Cc82df44aeD360768", + "0xbf4C0104dbfb028f3484CfAC17BB22aa15E5c7E2", + "0xCc3B817D4ABa7698EaafB4C68E7688CF61B0BF46", + "0x572E1b86471c900Cd16AFa9cBB7701862D0e70cB", + "0x602Ac8C3f61b351be325FEeb58842EF557431c2e", + "0x8d0CD1AB81EaDa4F92C7cb5c8DBc25C69cc296AD", + "0xAE2C7AB762317DB453317b70f1f40145755fAfb7", + "0x7bdae9AAbE238188c4882D48a3aEE21288A38eD0", + "0x96e4152f00894f677d860023b9784d578bC1c145", + "0xF572C9b11E757d3580C7C7310630cd488E8EA736", + "0x3769092DBfa6eb34434fB5B7cf0eB06E710728F3", + "0xCA72c93172BA6EfF168E59e7F17C3C7A8FeA9B62", + "0x1c0AcCc24e1549125b5b3c14D999D3a496Afbdb1", + "0x7fC80faD32Ec41fd5CfcC14EeE9C31953b6B4a8B", + "0x5d36a202687fD6Bd0f670545334bF0B4827Cc1E2", + "0xe64113140960528f6AF928d7cA4f45d192286a7a", + "0xf6B6F07862A02C85628B3A9688beae07fEA9C863", + "0xD779aFEE481e3Df5cd0544F0e4353Cf534FD99Db", + "0x183bDB344A07Ee3D27f07AC4799A56E4A2fE5439", + "0xA8cadC2268B01395f8573682fb9DD00Bd582E8A0", + "0x75535661Ab25a468Dfb3137320a7568FeCda4832", + "0xd37ED782323A82e5BD55A92500E48FF5eFcc415E", + "0x03bB5bC3c8fdAB212A6b2B347a049133DfCB3A47", + "0x61987699055394c65355F2797D3e4e589f7FaBf4", + "0x2bC12061C8912505978472C21d4a23dB43AF62aA", + "0xad7575AEFd4d64520c3269FD24eae1b0E13dbE7B", + "0x0D89421D6eec0A4385F95f410732186A2Ab45077", + "0x04c0cD38B8c203b14ef2b7B8d736D69B938AFF71", + "0x0CF30daf2Fb962Ed1d5D19C97F5f6651F3b691c1", + "0x6EEb37b9757DcA963120f61c7E0e0160469A44D3", + "0x616caD18642F45d3fa5FCaaD0a2d81764A9cBa84", + "0xdC1d963D21C9c1bFf7b6Bea6e10080dAa9b4fc51", + "0x8073639B11994C549eDa58fC3cd7132a72aaDF10", + "0xe52C39327FF7576bAEc3DBFeF0787bd62dB6d726", + "0x8f21bD39FcAeA3A729D46339A383081ecB7E84E0", + "0x8Fb7087336678F36E42313f6130567A109a8e73d", + "0x276E69CdD336001afEF07075859A93078496C3c1", + "0x954F716e6de059360d278B773138f8e046696721", + "0x997D410b26CdD17b0750F2c1751e59cBcfaE446f", + "0xE8b6b71f3b1E6d2ad406D2cf36B1f2C567342dF1", + "0x83108A0653a14EAeB8301E7b10a37CfAc39C82f6", + "0xF95D9549b3Ab9470d306a6413Aa45082e8B66043", + "0x82d92494f6fFFB17A1DDFfd9B7d88D1d0a360009", + "0xA19947DA8B916f64Ac6F362cEC9001D8BCBeEe93", + "0x7ef5e4062dcCaD29A6F8d5458590160536056C81", + "0x653d973b36137A5cB2fc304996E0af1F1afCC628", + "0x5F319CA6Ecf072A4d183edAa711Cd04dC225df19", + "0x4D32D90D6535bD4e7eaBaa27EE72932cB214BbfA", + "0x73b9f6a6e52aCE2797F0a6E52AAc530Ed1F2a2Af", + "0xaA3600788b72863ff51C8f0dB5F10bB65fbFeAB4", + "0xf93F0b770784602fC3079eb1D2fB1Ff488Bb02B0", + "0xC8Ddd59c496D04C4C060Ab5038d03d661DDC2617", + "0xc42c77b6B2A2B220b9502F357bBf51334Db3C93f", + "0x2615214F8200B526a7B1eACe03971F2672B48CF2", + "0x9d8d7220D060fd12Ca33336B7239688e366327dE", + "0x9e602c1920443F01Cb100a57A7F894df8Eb42f66", + "0x7e651F5f597436cD0fa941F5FF2cD45Ef3F2Fda8", + "0x8e30Dc2AEF957B1F7dd67B1b7bC651fFe7E17a06", + "0x597dC4159a4b85c086c3C679a0B6c8Fe2836886F", + "0x7fBdE8B27D2B4F164B66F2a9dc02bbD6697e5b19", + "0xf5819cC26F0481c9b86294B4c24027518a04BD5B", + "0x8e7D20638947132B0e6E1aFdE2da1B103aFF9280", + "0xCBA711BEF21496Cfd66323d9AEA8C8EFd0F43e9d", + "0xdfBaeeF21396BF205D4B7D23345155489072Cf9B", + "0x3B981fA5dD50237dAb6F96A417A6690B6f20FcC4", + "0x6C31212a23040998E1D1c157ACe3982aBDBE3154", + "0xCDdF772F8A3295C89DC37510E16e360ee2d29789", + "0x002B5dfB3C71E1dC97A2e5A0A7f69F3e7b83F269", + "0xAD7A185b2456d5AFD85838A50C7d8aCE3aB2f871", + "0x7993F18C91A9f68593d308C5846f380A2a374F46", + "0xc5d82775c9bc5272B1225DB8D62b7034e064BA91", + "0x8bfcF8cb383149D4Ef37e7A609cEc195CDCbE099", + "0xA515F7fB260095eebC860425493b8761B4FC9abd", + "0xaA95cA26c92b0634dF7a1A1504f579F13bFB7f9d", + "0xC2812325caD4C4C782CbbC1164e9373371D31dB2", + "0x4831DdB6502ca45dbEEDf58B47292061Cdb6050B", + "0x6733c60E6E02f9C8FA221Db1aeA018d80D949861", + "0xCaD3887923B39cD2b0B6d13538C4ecB7C5EE9825", + "0x4520cD8BC085B962eF8c0ec696ac9D3Ef1d8bf55", + "0x7D85fCbB505D48E6176483733b62b51704e0bF95", + "0x27259b0F4209e76f8C6Cf27106C9FF83BdC2E831", + "0xE04885c3f1419C6E8495C33bDCf5F8387cd88846", + "0x23ee51e614cBF138e4cAbA9EC5ed4fF7D27A8596", + "0x2cab4d881962D247218356B32aBc4AA5c46bA0d2", + "0x1c0A032954f20761E59138feE236204bECbb8bdb", + "0x701d0ECB3BA780De7b2b36789aEC4493A426010a", + "0x1Ec3C1f70E1D6bBDC84092ae86eAaDE495fdDB9b", + "0xB53b0255895c4F9E3a185E484e5B674bCCfbc076", + "0x770569f85346B971114e11E4Bb5F7aC776673469", + "0x8289432ACD5EB0214B1C2526A5EDB480Aa06A9ab", + "0xdca6F7CB3cF361C8dF8FDE119370F1b21b2fFf63", + "0x117e1EbB7D05545064850513021dF6ADe3C1690B", + "0x7fb43C99a26a9EA8ba841d58390BF1C2996EDFB0", + "0x84B5a60Df2d7e3397B3A4A3c6282f090304Aca26", + "0x72F434Fa010929656AeF58695dab85447E51Fbc6", + "0xA29b0D2F3b4555359A1bF684d700753b1b06cBc4", + "0x4318cC449b1cbE6d64dd82E16abE58C79E076C2B", + "0x8F48282e50B0210bd7c7DD69C54205E98b9Ef5D9", + "0xa305B293e44A82f3Cd489b5fB26084647bb5D8ae", + "0xd9e5De13eF1dBC4DFE0Ee1BB76276228b9B23d0f", + "0x4AcEEB7bF9ec8104CC2379f1E8D648Ee47249FCb", + "0x0743542070891051861f8D0a4550f97B43B0B89a", + "0x58aD805f26272C5Ba06D24Bd0E43c8a2d1c634D9", + "0xE6ED9C681967a4EA7Cef4486942b800139DfB000", + "0x51b9C1Df35B044b5c0099D1fD07EAb7cE38f325d", + "0x55DFFA17578e6bAcE42e4Bf8687A11A85cCfEF97", + "0x1FAE8f99E9F932BdBA910061590C2156eE512A91", + "0xA25207Bb8f8EC2423E2ddf2686A0CD2048352f3E", + "0x746bb7beFD31D9052BB8EbA7D5dD74C9aCf54C6d", + "0x38bc91AA6Aa434c4fae7E666F68C859292deEd95", + "0xA3aD5CFb4FF4B68e37A338Da200BA441C1850B5b", + "0x4bfb2c232F70c83136a3F206cd26Df2A0B605cEC", + "0xf5AB6B4a8d578807491ef59cE855982990932617", + "0x1Fdd220E14b59E26bf1888e8267C4C221983a0A6", + "0xE2D6AFF297b41881c1aEA9599F68AEDFAB38C651", + "0x7d547666209755FB833f9B37EebEa38eBF513Abb", + "0xb681B19bb1F7e9F3C2AE0EDeab368c2afaa4e590", + "0x7Eb84E42059F0D44269C50f4D3A280Fd307a6824", + "0x84f0620A547a4D14A7987770c4F5C25d488d6335", + "0x4Ae6a8A28c87b75e935a90D6128F2649C969c0D8", + "0xb79223E868871DBAc27E8E301f73734abd4Cc628", + "0x6F219Bd1167568aB67494A9067CbbB5679bf0022", + "0x9Ff548c1B3eA3dd123AFE39C759dDA548009B6C8", + "0x3085051F89666E7124e7Ab95b693Fc1E09770907", + "0xa25211B64D041F690C0c818183E32f28ba9647Dd", + "0x6166E1964447E0959bC7c8d543DB3ab82dB65044", + "0x76E059C6FF6bf9FFFD5f33AFdf4AB2FD511C9DF4", + "0x4CC9E6fABb800F083a2685501d1A30CdAbb4B2De", + "0x5f3371793285920351344a1EaaAA48d45e600652", + "0xAFE2b51592b89095A4cFb18da2B5914b528f4c01", + "0xe3F4F3aD70C1190EC480554bbc3Ed30285aE0610", + "0xE0D8926A51F9A1dD8E089D9a3DD88F88fFb2F1Dc", + "0xa6c366D97cb64708211f24310dFAd5363BC96a04", + "0xB7562F12E41C762CeCDA99d62Bd6EAC7b0C3B4c1", + "0x301605C95acbED7A1fD9C2c0DeEe964e2AFBd0C3", + "0x5d47e5D242a8F66a6286b0a2353868875F5d6068", + "0x0ea26051F7657d59418da186137141CeA90D0652", + "0x88f1706c20d94A4d1551C5F799C9E3380A24C3AC", + "0xFB40932271Fc9Db9DbF048E80697E2Da4AA57250", + "0x40Db8365d1252bcb06598927698238a99D39228E", + "0xaCf4C2950107eF9b1C37faA1F9a866C8F0da88b9", + "0x144c4E5027B69f7798B2B162D924BcAE5c149f15", + "0xeeE844540644b204f0005c063Ae95F244BF06a84", + "0x014607F2d6477bADD9d74bF2c5D6356e29a9b957", + "0x1E8eE48D0621289297693fC98914DA2EfDcE1477", + "0x4AdA1B9D9fe28aBd9585f58cfEeD2169A39e1c6b", + "0x31460f49EEA93Ef8255b42be019FB96F89Cf0c49", + "0x63A32F1595a68E811496D820680B74A5ccA303c5", + "0x022ca32d31da3Ef85922AAFD9aD29C5b2418172C", + "0x93B109C3c279bcBbB673Ed1ae1A8BB2dE8eEf9da", + "0x689476323Eb5e9A5DEd342F54B562fc2c156A522", + "0x1C9F765C579F94f6502aCd9fc356171d85a1F8D0", + "0xe0144FA05A0d32B5B1De10CcEe7211616B3E3EF0", + "0x6C965b656C450259a6D4d95A2E68Fb4319EecBc0", + "0xE36BD8C15a83b89E2E49806d7312846069755C63", + "0x59DDA36bD196Ec849838CE2163E6821f946b37Dc", + "0xDd31dB93082a3A71b98D37ba26230f8734Bd63C3", + "0x83c98211C50480e457a0dF930d2A56a891BC4d4b", + "0x11FA934f6754076AEb7Cf0A72a1c2D2518aA4C77", + "0x2B888954421b424C5D3D9Ce9bB67c9bD47537d12", + "0x2383A8b8cC8561a65871F1d2783B7C52e22B62c1", + "0xCED608Aa29bB92185D9b6340Adcbfa263DAe075b", + "0x841AD0AbAb2D33520ca236A2F5D8b038adDc12BA", + "0x76d2DDCe6b781e66c4B184C82Fbf4F94346Cfb0D", + "0xf21e38ac177B48fDE02dB7F2CA97466AE8Eae87D", + "0x7537Cb0AEe6a3483a7601ebf1084eD4df73166Ab", + "0x5f0bD06A71E038206ef3e5090eB448E9a9773772", + "0x3C0c7B44c1F9366271F5c491121a1F7d55d33eF5", + "0xa96a437eFb71bAF50A59027C340FA3362ef703F7", + "0x55bA9c90c37e3206570AC9dc872c0f053d155F77", + "0xC68bba423525576C7684e7ea25E7D5F079b1361E", + "0x78E87757861185Ec5e8C0EF6BF0C69Fa7832df6C", + "0xCb36F8580A36788A48518dEC95Ea458357E64E30", + "0x25854e2a49A6CDAeC7f0505b4179834509038549", + "0x639749b7b08aEe65039c21d8a411103C6ceBEBF0", + "0xF517529866d371F04780885923F739bc17694BFb", + "0xC728DEa8B2972E6e07493BE8DC2F0314F7dC3E98", + "0x33f6EE932cEa603Fafd6854827259bE172C91Da4", + "0x6D97d65aDfF6771b31671443a6b9512104312d3D", + "0xB7BaBe35CE543e2Cf2F615CB1c792a2025feb572", + "0x4D9e86a5AC368Aa4Df0473eF07e13Ec2Fbe04025", + "0xaa79B87DC8B046A5E4f7D03F1562D7fe5BF98737", + "0xE71FbB197BC8fD11090FA657C100d52Dbb407662", + "0xB22981bA3FE1De2325935c91a3B717168fB86714", + "0xf389dD1F828525b449D63D14157f2d3A25eE0a41", + "0x877B37D3E5467B4aAE7687Dd3480A46C8D3e16Be", + "0xf9e1D1e9F22c96752356AdFd377231528c7E851E", + "0x187089B33E5812310Ed32A57F53B3fAD0383a19D", + "0xF1659A2FD5007192314F9676e6a4a39FD1202160", + "0xFdd210ce1b829E837D9e034DAE0F0312F176cef6", + "0xaCE1f1c6c5c89AE3Fc3209ff92e7120fb74445aA", + "0x6Ceb397b68059Ca73049874D0a30c62500aE9877", + "0xC46c67Bb7E84490D7EbdD0b8ecDaca68Cf3823F4", + "0xbb2eb4c7eB36ECce7A3E6bc501590CE12c9c1050", + "0x9Cf251A782cE7310D5bec0fe0a1C2B826d962545", + "0x43930Ff04D18a5B59805151c1Eb403C55870641E", + "0xA270f5649A42feDfE66ddb3b0b50bebAe1e3DDB0", + "0xd3488EA0c1DC99a5d72F75c84004224f8b58694E", + "0x7aBa691D12D8eF8793F1643eBa66b69C70EC72f1", + "0x8558f502887a9a52c4B265d72327E0E529Ff790d", + "0xA906c85B7e809b79c5e69d485693B44d65B1B252", + "0x3abdC9ed5f5dE6A74CFeb42a82087C853E160E76", + "0x30C7F4F7504D6366916f669cd8E731ED4dF6C702", + "0xed8DB37778804A913670d9367aAf4F043AAd938b", + "0xc191a29203a83eec8e846c26340f828C68835715", + "0xa32aECda752cF4EF89956e83d60C04835d4FA867", + "0x059F7da59Ad1EB412B4d2fFc12E9B50Da91cFdb6", + "0x85BEad65c61dB8cF230b3ec30552B8b3E6388570", + "0xF3Ad97364bcCC3eA0582Ede58C363888f8C4ec85", + "0x3F87755E2974534888Ddb20A52dCE45Ef9f204AB", + "0x757CC91CcBB88cB0d78d6798D20051d39E5A7296", + "0xF553C8223cA8542Af9Db7b916Fe9dc7c28b73751", + "0x40f9bf922c23c43acdad71Ab4425280C0ffBD697", + "0x9600e2eE6377DAdad7299B120026661c336A5e6d", + "0x516fCA170bfE24BFC54e01F215EF85Fe9B5B798A", + "0x61C820e261717A5A0555488872F78ac7b9CE77Ec", + "0xEb263241eB948Cc0eB53A58bf743289D074F474F", + "0x841C11b14c428dd591093348B8Afa2652C863988", + "0x3c114973c0260290C2dbD40323327d996972FCeB", + "0x765a16ca391A6b9249cfA65bf2D14C38722198e3", + "0xC3268DDB8E38302763fFdC9191FCEbD4C948fe1b", + "0x6B92686c40747C85809a6772D0eda8e22a77C60c", + "0xc799bE8De03F20B2D3b101E6F6516D614e6fFe06", + "0x40Dc654af5cE40C122ffDC679fa8E8ca8b91556A", + "0xCE8D52c38d74B77a0aA361c48Fdce6b220A3370e", + "0xEfa4c696Ea2505ec038c9dDC849b1bf817d7f69d", + "0xf7253A0E87E39d2cD6365919D4a3D56D431D0041", + "0xcf79C7EaEC5BDC1A9e32D099C5D6BdF67E4cF6e8", + "0xff75E131c711e4310C045317779d39B3B4f718C4", + "0xdE2BE7C9C542c55a7a77489A3A7745493988947F", + "0xFeB3E0f50107f6cfB2EC8C2bC8287f2707E0E2Ea", + "0x6b759Bf480407D19c8903c16023c706868c29a2A", + "0x6E38911dA6Dd0379F1CaC396F74387c95A1f0D21", + "0x5a5D9aB7b1bD978F80909503EBb828879daCa9C3", + "0xe96056A9936C58e89D1703cF6bD97F134341EE44", + "0x4dD6720D2Bb8721A46bdF9a528704164578E03B9", + "0xE83B9A1B9056B21a01b85162E77AD76a42A1c64B", + "0xbeC48f1cCf82d8e4C983Ee00Ac2eC6B03B81d710", + "0xEFEdaf9c07E6eB56BB8F82f30018e4461B1c5F4c", + "0xB68da7fbF71383Afab240839287878539cFFf20b", + "0xfBDDB719cC7c795a1D9b7EA7aC11494A19b3231F", + "0x07506a5F48D71fDB34D3900fB086D43EF1B58FF9", + "0xa85cdd5478B7E525a808eF9707c3e33432cE1e7F", + "0xCf7C21DeD40f2Df85A564207A89b3379780d9CE3", + "0xd26b76e50f6510cdD4bf45d59279705f36946d23", + "0xdb7a41e39807E8C988859f150296Db92674b7dc7", + "0x719028736f10164c838Ef129936779eD739312f2", + "0xaBCdef0AbbA5D0106595174213156797bc0DB33E", + "0x3D2b8879f97e413b2609F9844A5fc8dB8FE4f6F8", + "0x81EbE8Ee7b51741fD5DaD31F6987E626A9bb8111", + "0x1D45c8fa65F6b18E7dAe04b2efEa332c55696DaA", + "0x978eB534b26CB8749D352a2C94EC21e659e4248d", + "0xa7CA400d49BBa87EB606ee05af93689BD21FaB99", + "0x65ad2BF7E09af2597C140dF6386a3003d0F5f8Ee", + "0x835918a3fBDf946364a9aee3114173865b712663", + "0x6073cFfc1D46b1eA57BA89A28074cA734aCD7003", + "0x2B13D52dFd33E2eBd13232866fDf96088e77d596", + "0x55F5601357f6e0B10a3386914c93916c6C9A368A", + "0xA1D5D2d931b532A0503e97f540f65ed256f374e6", + "0x6C9258895FFBE2178b3EdEfE09AF304a1e99bF2F", + "0x973375b099943cDdFd390022CeA90D4F1d0c493c", + "0x8A8C879D39A74fCE0593714956bB7Ed048A5c1BF", + "0x9c42B0c70D0dAF1211f3aab2A1E6EC5E717dE12a", + "0x81a6383041593c556d1c8e69e2749b35b5008F09", + "0xF41b98a4C32bB61468C8001E0C69Ef64ce6DEa57", + "0x8FaE81bb674c89cCDE35a386587333D074b57786", + "0xa8258ED271BB9be9d7E16c5818E45eF6F2577d92", + "0x1e90474D2E83e7B7dD45553156Beb316845E66A4", + "0x2cCbbC4c10F5d807FDd447219B57D0b883a28DC8", + "0x1bBeAc736875c5043486A8a4374E6B5616eC8883", + "0x95add3DfEF3AE0A832607Dc71C4A9C6A6C2D7Eb7", + "0x744c6Eed427aF293b0106B46700fdDD3C9f62Eef", + "0x743Ec55fc166D24D2FD0211fb6Ce53926D0Ff3b1", + "0xDd03d2434C02c6BfFb097b7130528F9568b6C70d", + "0x97C12EFA574923E3ee445370d2dE432332857110", + "0xB69951a0642b55CD5731535ed5B290Fa49D3454A", + "0xBA56878729540404dE2aa14561b451aE2350744a", + "0x8D247f4Fbbe81429d3D164a5c9Ae0063210edBdC", + "0x850a146D7478dAAa98Fc26Fd85e6A24e50846A9d", + "0xfE1552DA65FAcAaC5B50b73CEDa4C993e16d4694", + "0x9705FE3586a7D768Fee061aAfE9384b1D4B8F2D8", + "0x5554672e67bA866B9861701D0e0494AB324aD19A", + "0xacc5c1e73d70F7F9622De2d574885Ce8E6981033", + "0xbe9E7b0ed19526544B55b697107231f9467a805f", + "0x172DBab6f5E62A1FE7E2bA5eA1624ADB33e0aa14", + "0x96725Fa2F9A0b5bAf80fC36C20C2cA79d86424ed", + "0xa392cCadABFf735dbFF69dC93d7C13f34A30611b", + "0xEbF0e04E47F726D0f44801dFEC5e705aDcd6694b", + "0xC0891e8FCeA09680BFe9170809fad1BCCa10b96b", + "0xA21000E7A5A2A2Bd9329428A859f9d7dcE0f0961", + "0x9A387307F7508DE113092BaFC5CB4B3AE0706521", + "0xBA719E0197470A790726075fD98EDEF04E2467af", + "0xda08BE028304db1A73a13Bce7C943127C2E393dB", + "0xfB4a965A35603010FeAcC648cA022Cb6A12D33F5", + "0x3Aa73ed90e9f0CEd87ff99CB60cA79019279e6CE", + "0x150bB505A9259b0be44FFb15415C79199E83c445", + "0xB170A41F2523220A12F84f17A54bD31953D98027", + "0x2Fcd65d9c8078644adCf1CB0cd70A1b61F3F9C5b", + "0x73006C818880d07dD510e165C3De3E74F2407187", + "0x747e6ABc102222f1dF65C662540dDf471241a644", + "0xeeEe5D271A56Aa09C4F8862aF514ADD3E882857c", + "0x98Ad82AB467bc8c70e0CC183a5826d903751b7d8", + "0xC624434420f6CbE835D6358A8223b78432773cEd", + "0x848e313d4b25bC0B48CaFdB6A72391E892E6A247", + "0x0025Ab2d69F6c2C3Ffac32Ab6A16e18c807518B8", + "0x2efe744ecc4F6BD55538da57D09DAE895C95b223", + "0xBc6d82D8d6632938394905Bb0217Ad9c673015d1", + "0xe1555c6EE61366a3f90135Dc704Acd25C3247ACA", + "0x2f51E78ff8aeC6A941C4CEeeb26B4A1f03737c50", +} +TIMEOUT_LIST = set() +TIMEOUT_LIST_NOT_MAINNET = {"0xdf486eeC7b89C390569194834a2f7A71da05Ee13"} GUEST_LIST_STAMP_PROVIDERS = [ "AllowList#OctantFinal", @@ -566,3 +567,12 @@ "AllowList#OctantEpochOne", "AllowList#OctantEpochThree", ] + +GTC_STAKING_STAMP_PROVIDERS_AND_SCORES = { + "SelfStakingBronze": 1.0, + "SelfStakingSilver": 2.0, + "SelfStakingGold": 3.0, + "BeginnerCommunityStaker": 1.5, + "ExperiencedCommunityStaker": 2.5, + "TrustedCitizen": 4.0, +} diff --git a/backend/app/engine/octant_rewards/leftover/__init__.py b/backend/app/engine/octant_rewards/leftover/__init__.py index 144069a783..e9295e963c 100644 --- a/backend/app/engine/octant_rewards/leftover/__init__.py +++ b/backend/app/engine/octant_rewards/leftover/__init__.py @@ -18,3 +18,6 @@ class Leftover(ABC): @abstractmethod def calculate_leftover(self, payload: LeftoverPayload) -> int: pass + + def extract_unused_matched_rewards(self, *args, **kwargs) -> int: + return 0 diff --git a/backend/app/engine/octant_rewards/leftover/with_ppf_and_unused.py b/backend/app/engine/octant_rewards/leftover/with_ppf_and_unused.py index 3faf8085b2..3f0d57acc3 100644 --- a/backend/app/engine/octant_rewards/leftover/with_ppf_and_unused.py +++ b/backend/app/engine/octant_rewards/leftover/with_ppf_and_unused.py @@ -15,3 +15,14 @@ def calculate_leftover(self, payload: LeftoverPayload) -> int: - payload.total_withdrawals + unused_matched_rewards ) + + def extract_unused_matched_rewards(self, leftover, payload) -> int: + extra_individual_rewards = int(payload.ppf / 2) + return ( + leftover + - payload.staking_proceeds + + payload.operational_cost + + payload.community_fund + + extra_individual_rewards + + payload.total_withdrawals + ) diff --git a/backend/app/engine/projects/rewards/__init__.py b/backend/app/engine/projects/rewards/__init__.py index 95d704bd68..5c88af2a5f 100644 --- a/backend/app/engine/projects/rewards/__init__.py +++ b/backend/app/engine/projects/rewards/__init__.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod +from collections import namedtuple from dataclasses import dataclass, field -from typing import List, Optional +from typing import List, Optional, Dict from dataclass_wizard import JSONWizard @@ -41,6 +42,11 @@ class ProjectRewardsResult: threshold: Optional[int] = None +AllocationsBelowThreshold = namedtuple( + "AllocationsBelowThreshold", ["below_threshold", "total"] +) + + @dataclass class ProjectRewards(ABC): projects_allocations: ProjectAllocations = field(init=False) @@ -54,3 +60,12 @@ def calculate_project_rewards( def calculate_threshold(self, total_allocated: int, projects: List[str]) -> None: return None + + def get_total_allocations_below_threshold( + self, allocations: Dict[str, List[AllocationItem]] + ) -> AllocationsBelowThreshold: + allocations_sum = sum( + sum(int(item.amount) for item in project_allocations) + for project_allocations in allocations.values() + ) + return AllocationsBelowThreshold(0, allocations_sum) diff --git a/backend/app/engine/projects/rewards/allocations/__init__.py b/backend/app/engine/projects/rewards/allocations/__init__.py index 5a6856393a..3123dca6ae 100644 --- a/backend/app/engine/projects/rewards/allocations/__init__.py +++ b/backend/app/engine/projects/rewards/allocations/__init__.py @@ -33,10 +33,9 @@ def _calc_allocations( ) -> Union[int, Decimal]: ... - def group_allocations_by_projects( + def segregate_allocations( self, payload: ProjectAllocationsPayload - ) -> (Dict[str, List], List[ProjectSumAllocationsDTO], Union[int, Decimal]): - result_allocations = [] + ) -> Dict[str, List]: grouped_allocations = { key: list(group) for key, group in groupby( @@ -44,7 +43,14 @@ def group_allocations_by_projects( key=lambda a: a.project_address, ) } + return grouped_allocations + + def group_allocations_by_projects( + self, payload: ProjectAllocationsPayload + ) -> (Dict[str, List], List[ProjectSumAllocationsDTO], Union[int, Decimal]): + grouped_allocations = self.segregate_allocations(payload) + result_allocations = [] total_plain_qf = 0 for project_address, project_allocations in grouped_allocations.items(): project_allocations = self._calc_allocations(project_allocations) diff --git a/backend/app/engine/projects/rewards/preliminary.py b/backend/app/engine/projects/rewards/preliminary.py index dd6fdc52b6..6f5c5866ba 100644 --- a/backend/app/engine/projects/rewards/preliminary.py +++ b/backend/app/engine/projects/rewards/preliminary.py @@ -1,20 +1,23 @@ from dataclasses import field, dataclass from decimal import Decimal -from typing import List +from typing import List, Dict from app.engine.projects.rewards import ( ProjectRewardsPayload, ProjectRewardsResult, ProjectRewards, ProjectRewardDTO, + AllocationsBelowThreshold, ) from app.engine.projects.rewards.allocations import ( ProjectAllocations, ProjectAllocationsPayload, + AllocationItem, ) from app.engine.projects.rewards.allocations.preliminary import ( PreliminaryProjectAllocations, ) +from app.engine.projects.rewards.leverage.preliminary import PreliminaryLeverage from app.engine.projects.rewards.threshold import ( ProjectThreshold, ProjectThresholdPayload, @@ -22,7 +25,6 @@ from app.engine.projects.rewards.threshold.preliminary import ( PreliminaryProjectThreshold, ) -from app.engine.projects.rewards.leverage.preliminary import PreliminaryLeverage @dataclass @@ -42,6 +44,13 @@ def calculate_threshold(self, total_allocated: int, projects: List[str]) -> int: ) ) + def get_total_allocations_below_threshold( + self, allocations: Dict[str, List[AllocationItem]] + ) -> AllocationsBelowThreshold: + return self.projects_threshold.get_total_allocations_below_threshold( + allocations + ) + def calculate_project_rewards( self, payload: ProjectRewardsPayload ) -> ProjectRewardsResult: diff --git a/backend/app/engine/projects/rewards/threshold/__init__.py b/backend/app/engine/projects/rewards/threshold/__init__.py index 4295633921..0e9d4ada16 100644 --- a/backend/app/engine/projects/rewards/threshold/__init__.py +++ b/backend/app/engine/projects/rewards/threshold/__init__.py @@ -1,5 +1,9 @@ from abc import ABC, abstractmethod from dataclasses import dataclass +from typing import List, Dict + +from app.engine.projects.rewards import AllocationsBelowThreshold +from app.engine.projects.rewards import AllocationItem @dataclass @@ -13,3 +17,9 @@ class ProjectThreshold(ABC): @abstractmethod def calculate_threshold(self, payload: ProjectThresholdPayload) -> int: pass + + @abstractmethod + def get_total_allocations_below_threshold( + self, allocations: Dict[str, List[AllocationItem]] + ) -> AllocationsBelowThreshold: + pass diff --git a/backend/app/engine/projects/rewards/threshold/preliminary.py b/backend/app/engine/projects/rewards/threshold/preliminary.py index 2c68c221da..534ae6deb0 100644 --- a/backend/app/engine/projects/rewards/threshold/preliminary.py +++ b/backend/app/engine/projects/rewards/threshold/preliminary.py @@ -1,9 +1,12 @@ from dataclasses import dataclass +from typing import List, Dict +from app.engine.projects.rewards import AllocationsBelowThreshold from app.engine.projects.rewards.threshold import ( ProjectThreshold, ProjectThresholdPayload, ) +from app.engine.projects.rewards import AllocationItem @dataclass @@ -19,3 +22,24 @@ def calculate_threshold(self, payload: ProjectThresholdPayload) -> int: if payload.projects_count else 0 ) + + def get_total_allocations_below_threshold( + self, allocations: Dict[str, List[AllocationItem]] + ) -> AllocationsBelowThreshold: + summed_allocations = { + project: sum(map(lambda value: int(value.amount), values)) + for project, values in allocations.items() + } + total_allocations = sum(summed_allocations.values()) + no_projects = len(allocations.keys()) + + threshold = self.calculate_threshold( + ProjectThresholdPayload(total_allocations, no_projects) + ) + + allocations_below_threshold = 0 + for allocations_sum_for_project in summed_allocations.values(): + if allocations_sum_for_project < threshold: + allocations_below_threshold += allocations_sum_for_project + + return AllocationsBelowThreshold(allocations_below_threshold, total_allocations) diff --git a/backend/app/exceptions.py b/backend/app/exceptions.py index 40329daea8..3318ba3167 100644 --- a/backend/app/exceptions.py +++ b/backend/app/exceptions.py @@ -346,3 +346,19 @@ class InvalidDelegationForLockingAddress(OctantException): def __init__(self): super().__init__(self.description, self.code) + + +class InvalidAddressFormat(OctantException): + code = 400 + description = "Invalid address format" + + def __init__(self): + super().__init__(self.description, self.code) + + +class InvalidProjectDetailsInput(OctantException): + code = 400 + description = "Invalid input for projects details" + + def __init__(self): + super().__init__(self.description, self.code) diff --git a/backend/app/infrastructure/__init__.py b/backend/app/infrastructure/__init__.py index fe9c5b9b8b..509cdecc49 100644 --- a/backend/app/infrastructure/__init__.py +++ b/backend/app/infrastructure/__init__.py @@ -11,6 +11,8 @@ from app.infrastructure.exception_handler import ExceptionHandler from flask import current_app as app +from app.exceptions import InvalidAddressFormat + default_decorators = { "delete": ExceptionHandler.print_stacktrace_on_exception(True, True), "get": ExceptionHandler.print_stacktrace_on_exception(False, True), @@ -30,8 +32,12 @@ def _add_address_canonization(handler): def _decorated(*args, **kwargs): field_value = kwargs.get(field_name) if force or field_value is not None: - updated_field = to_checksum_address(field_value) - kwargs.update([(field_name, updated_field)]) + try: + updated_field = to_checksum_address(field_value) + except ValueError: + raise InvalidAddressFormat() + else: + kwargs.update([(field_name, updated_field)]) return handler(*args, **kwargs) diff --git a/backend/app/infrastructure/apscheduler.py b/backend/app/infrastructure/apscheduler.py index 35430ba1f0..223d97f7ca 100644 --- a/backend/app/infrastructure/apscheduler.py +++ b/backend/app/infrastructure/apscheduler.py @@ -1,5 +1,6 @@ from flask import current_app as app +from app import exceptions from app.legacy.core import glm from app.legacy.core import vault from app.extensions import scheduler @@ -16,7 +17,10 @@ def vault_confirm_withdrawals(): with scheduler.app.app_context(): if app.config["VAULT_CONFIRM_WITHDRAWALS_ENABLED"]: app.logger.debug("Confirming withdrawals in Vault contract...") - vault.confirm_withdrawals() + try: + vault.confirm_withdrawals() + except exceptions.MissingSnapshot: + app.logger.warning("No snapshot found") @scheduler.task( diff --git a/backend/app/infrastructure/database/models.py b/backend/app/infrastructure/database/models.py index cada6115b5..4b3f4aae33 100644 --- a/backend/app/infrastructure/database/models.py +++ b/backend/app/infrastructure/database/models.py @@ -204,3 +204,19 @@ class UniquenessQuotient(BaseModel): @property def validated_score(self): return Decimal(self.score) + + +class ProjectsDetails(BaseModel): + """ + This model represents the details of a project that is consistent with data on the IPFS node. + Records here are created only by migrations. + """ + + __tablename__ = "project_details" + + id = Column(db.Integer, primary_key=True) + address = Column(db.String(42), nullable=False) + name = Column(db.String, nullable=False) + epoch = Column(db.Integer, nullable=False) + + __table_args__ = (UniqueConstraint("address", "epoch", name="uq_address_epoch"),) diff --git a/backend/app/infrastructure/database/projects_details.py b/backend/app/infrastructure/database/projects_details.py new file mode 100644 index 0000000000..018ba587b1 --- /dev/null +++ b/backend/app/infrastructure/database/projects_details.py @@ -0,0 +1,7 @@ +from typing import List + +from app.infrastructure.database.models import ProjectsDetails + + +def get_projects_details_for_epoch(epoch: int) -> List[ProjectsDetails]: + return ProjectsDetails.query.filter_by(epoch=epoch).all() diff --git a/backend/app/infrastructure/external_api/common.py b/backend/app/infrastructure/external_api/common.py index 462fd66e48..eaa545c7d7 100644 --- a/backend/app/infrastructure/external_api/common.py +++ b/backend/app/infrastructure/external_api/common.py @@ -1,4 +1,5 @@ import time +from http import HTTPStatus from typing import Callable, Dict from app.exceptions import ExternalApiException @@ -6,7 +7,7 @@ def retry_request( req_func: Callable, - status_code: int, + status_code: HTTPStatus, no_retries: int = 3, sleep_time: int = 1, **kwargs, diff --git a/backend/app/infrastructure/routes/epochs.py b/backend/app/infrastructure/routes/epochs.py index 5c1968b12a..1c4b30fca2 100644 --- a/backend/app/infrastructure/routes/epochs.py +++ b/backend/app/infrastructure/routes/epochs.py @@ -3,7 +3,10 @@ from app.extensions import api, epochs from app.infrastructure import OctantResource, graphql -from app.modules.octant_rewards.controller import get_octant_rewards +from app.modules.octant_rewards.controller import ( + get_octant_rewards, + get_epoch_rewards_rate, +) ns = Namespace("epochs", description="Octant epochs") api.add_namespace(ns) @@ -106,6 +109,19 @@ def get(self): required=False, description="Community fund for the given epoch. It's calculated from staking proceeds directly.", ), + "donatedToProjects": fields.String( + required=False, + description="The amount of funds donated to projects. Includes MR and allocations.", + ), + }, +) + +epoch_rewards_rate_model = api.model( + "EpochRewardsRate", + { + "rewardsRate": fields.Float( + required=True, description="Rewards rate for the given epoch." + ) }, ) @@ -127,3 +143,18 @@ def get(self, epoch: int): app.logger.debug(f"Got: {stats}") return stats.to_dict() + + +@ns.route("/rewards-rate/") +@ns.doc( + description="Returns a rewards rate for given epoch. Returns data for all states of epochs.", + params={ + "epoch": "Epoch number", + }, +) +class EpochRewardsRate(OctantResource): + @ns.marshal_with(epoch_rewards_rate_model) + @ns.response(200, "Epoch's rewards rate successfully retrieved.") + def get(self, epoch: int): + app.logger.debug(f"Getting rewards rate for epoch {epoch}") + return {"rewardsRate": get_epoch_rewards_rate(epoch)} diff --git a/backend/app/infrastructure/routes/projects.py b/backend/app/infrastructure/routes/projects.py index bd2489a943..30263c8d86 100644 --- a/backend/app/infrastructure/routes/projects.py +++ b/backend/app/infrastructure/routes/projects.py @@ -1,4 +1,4 @@ -from flask import current_app as app +from flask import current_app as app, request from flask_restx import Namespace, fields from app.extensions import api @@ -7,6 +7,10 @@ from app.modules.projects.metadata.controller import ( get_projects_metadata, ) +from app.modules.projects.details import controller as projects_details_controller +from app.infrastructure.routes.validations.project_details_input import ( + validate_project_details_input, +) ns = Namespace("projects", description="Octant projects") api.add_namespace(ns) @@ -23,6 +27,26 @@ }, ) +project_model = api.model( + "Project", + { + "name": fields.String(required=True, description="Project name"), + "address": fields.String(required=True, description="Project address"), + "epoch": fields.String(required=True, description="Project epoch"), + }, +) + +projects_details_model = api.model( + "ProjectsDetails", + { + "projectsDetails": fields.List( + fields.Nested(project_model), + required=False, + description="Projects details", + ), + }, +) + @ns.route("/epoch/") @ns.doc( @@ -43,3 +67,40 @@ def get(self, epoch): "projectsAddresses": projects_metadata.projects_addresses, "projectsCid": projects_metadata.projects_cid, } + + +@ns.route("/details") +@ns.doc( + description="Returns projects details for a given epoch and searchPhrase.", + params={ + "epochs": "Epochs numbers (query parameter)", + "searchPhrases": "Search phrase (query parameter)", + }, +) +class ProjectsDetails(OctantResource): + @ns.marshal_with(projects_details_model) + @ns.response(200, "Projects metadata is successfully retrieved") + def get(self): + search_phrases = request.args.get("searchPhrases", "").split(",") + epochs = validate_project_details_input(request.args.get("epochs", "")) + + app.logger.debug( + f"Getting projects details for epochs {epochs} and search phrase {search_phrases}" + ) + projects_details = ( + projects_details_controller.get_projects_details_for_multiple_params( + epochs, search_phrases + ) + ) + app.logger.debug(f"Projects details for epochs {epochs}: {projects_details}") + + return { + "projectsDetails": [ + { + "name": project["name"], + "address": project["address"], + "epoch": project["epoch"], + } + for project in projects_details + ] + } diff --git a/backend/app/infrastructure/routes/user.py b/backend/app/infrastructure/routes/user.py index e43cfd2297..046b63fcf0 100644 --- a/backend/app/infrastructure/routes/user.py +++ b/backend/app/infrastructure/routes/user.py @@ -24,13 +24,17 @@ "UserAntisybilStatus", { "status": fields.String(required=True, description="Unknown or Known"), - "expires_at": fields.String( + "expiresAt": fields.String( required=False, description="Expiry date, unix timestamp" ), "score": fields.String( required=False, description="Score, parses as a float", ), + "isOnTimeOutList": fields.Boolean( + required=False, + description="Flag indicating whether user is on timeout list", + ), }, ) @@ -192,15 +196,25 @@ class AntisybilStatus(OctantResource): @ns.response(200, "User's cached antisybil status retrieved") def get(self, user_address: str): app.logger.debug(f"Getting user {user_address} cached antisybil status") + antisybil_status = get_user_antisybil_status(user_address) + app.logger.debug(f"User {user_address} antisybil status: {antisybil_status}") + if antisybil_status is None: return {"status": "Unknown"}, 404 - score, expires_at = antisybil_status + + score, expires_at, is_on_timeout_list = ( + antisybil_status.score, + antisybil_status.expires_at, + antisybil_status.is_on_timeout_list, + ) + return { "status": "Known", "score": score, - "expires_at": int(expires_at.timestamp()), + "expiresAt": int(expires_at.timestamp()), + "isOnTimeOutList": is_on_timeout_list, }, 200 @ns.doc( @@ -211,9 +225,9 @@ def get(self, user_address: str): @ns.response(504, "Could not refresh antisybil status. Upstream is unavailable.") def put(self, user_address: str): app.logger.info(f"Updating user {user_address} antisybil status") - score, expires_at = update_user_antisybil_status(user_address) + result = update_user_antisybil_status(user_address) app.logger.info( - f"User {user_address} antisybil status refreshed {[score, expires_at]}" + f"User {user_address} antisybil status refreshed {[result.score, result.expires_at]}" ) return {}, 204 diff --git a/backend/app/infrastructure/routes/validations/project_details_input.py b/backend/app/infrastructure/routes/validations/project_details_input.py new file mode 100644 index 0000000000..cdb28b7d83 --- /dev/null +++ b/backend/app/infrastructure/routes/validations/project_details_input.py @@ -0,0 +1,12 @@ +from typing import List + +from app.exceptions import InvalidProjectDetailsInput + + +def validate_project_details_input(epochs: str) -> List[int]: + try: + epochs = list(map(int, epochs.split(","))) + except ValueError: + raise InvalidProjectDetailsInput + + return epochs diff --git a/backend/app/modules/common/synchronized.py b/backend/app/modules/common/synchronized.py new file mode 100644 index 0000000000..9a242c4d4d --- /dev/null +++ b/backend/app/modules/common/synchronized.py @@ -0,0 +1,14 @@ +from threading import Lock +from functools import wraps +from typing import Callable + + +def synchronized(wrapped: Callable): + lock = Lock() + + @wraps(wrapped) + def _wrap(*args, **kwargs): + with lock: + return wrapped(*args, **kwargs) + + return _wrap diff --git a/backend/app/modules/dto.py b/backend/app/modules/dto.py index 0bdbcf87c2..31247420be 100644 --- a/backend/app/modules/dto.py +++ b/backend/app/modules/dto.py @@ -53,6 +53,8 @@ class OctantRewardsDTO(JSONWizard): # Data available starting from Epoch 3 ppf: Optional[int] = None community_fund: Optional[int] = None + # Added after moving metrics from FE to BE + donated_to_projects: Optional[int] = None @dataclass(frozen=True) diff --git a/backend/app/modules/facades/confirm_multisig.py b/backend/app/modules/facades/confirm_multisig.py index a867db91dc..4281ad2ce4 100644 --- a/backend/app/modules/facades/confirm_multisig.py +++ b/backend/app/modules/facades/confirm_multisig.py @@ -7,8 +7,10 @@ ) from app.modules.user.allocations import controller as allocations_controller from app.modules.user.tos import controller as tos_controller +from app.modules.common.synchronized import synchronized +@synchronized def confirm_multisig(): """ This is a facade function that is used to confirm (i.e approve and apply) multisig approvals. diff --git a/backend/app/modules/modules_factory/current.py b/backend/app/modules/modules_factory/current.py index a95a8fa744..4e0ef5d014 100644 --- a/backend/app/modules/modules_factory/current.py +++ b/backend/app/modules/modules_factory/current.py @@ -15,6 +15,7 @@ UserAllocationNonceProtocol, ScoreDelegation, UniquenessQuotients, + ProjectsDetailsService, ) from app.modules.modules_factory.protocols import SimulatePendingSnapshots from app.modules.multisig_signatures.service.offchain import OffchainMultisigSignatures @@ -44,7 +45,15 @@ from app.modules.withdrawals.service.finalized import FinalizedWithdrawals from app.pydantic import Model from app.shared.blockchain_types import compare_blockchain_types, ChainTypes -from app.constants import UQ_THRESHOLD_MAINNET, UQ_THRESHOLD_NOT_MAINNET +from app.constants import ( + UQ_THRESHOLD_MAINNET, + UQ_THRESHOLD_NOT_MAINNET, + TIMEOUT_LIST_NOT_MAINNET, + TIMEOUT_LIST, +) +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, +) class CurrentUserDeposits(UserEffectiveDeposits, TotalEffectiveDeposits, Protocol): @@ -61,6 +70,7 @@ class CurrentServices(Model): simulated_pending_snapshot_service: SimulatePendingSnapshots multisig_signatures_service: MultisigSignatures projects_metadata_service: ProjectsMetadataService + projects_details_service: ProjectsDetailsService user_budgets_service: UpcomingUserBudgets score_delegation_service: ScoreDelegation uniqueness_quotients: UniquenessQuotients @@ -97,7 +107,10 @@ def create(chain_id: int) -> "CurrentServices": user_allocations = SavedUserAllocations() user_allocations_nonce = SavedUserAllocationsNonce() user_withdrawals = FinalizedWithdrawals() - user_antisybil_service = GitcoinPassportAntisybil() + + timeout_list = TIMEOUT_LIST if is_mainnet else TIMEOUT_LIST_NOT_MAINNET + user_antisybil_service = GitcoinPassportAntisybil(timeout_list=timeout_list) + tos_verifier = InitialUserTosVerifier() user_tos = InitialUserTos(verifier=tos_verifier) patron_donations = EventsBasedUserPatronMode() @@ -129,7 +142,7 @@ def create(chain_id: int) -> "CurrentServices": ) uq_threshold = UQ_THRESHOLD_MAINNET if is_mainnet else UQ_THRESHOLD_NOT_MAINNET uniqueness_quotients = PreliminaryUQ( - antisybil=GitcoinPassportAntisybil(), + antisybil=GitcoinPassportAntisybil(timeout_list=timeout_list), budgets=user_budgets, uq_threshold=uq_threshold, ) @@ -144,6 +157,7 @@ def create(chain_id: int) -> "CurrentServices": user_tos_service=user_tos, user_antisybil_service=user_antisybil_service, projects_metadata_service=StaticProjectsMetadataService(), + projects_details_service=StaticProjectsDetailsService(), user_budgets_service=user_budgets, score_delegation_service=score_delegation, uniqueness_quotients=uniqueness_quotients, diff --git a/backend/app/modules/modules_factory/finalized.py b/backend/app/modules/modules_factory/finalized.py index d5b470c3d6..20cb433f2b 100644 --- a/backend/app/modules/modules_factory/finalized.py +++ b/backend/app/modules/modules_factory/finalized.py @@ -13,6 +13,7 @@ WithdrawalsService, SavedProjectRewardsService, ProjectsMetadataService, + ProjectsDetailsService, ) from app.modules.octant_rewards.general.service.finalized import FinalizedOctantRewards from app.modules.projects.rewards.service.saved import SavedProjectRewards @@ -26,6 +27,9 @@ StaticProjectsMetadataService, ) from app.pydantic import Model +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, +) class FinalizedOctantRewardsProtocol(OctantRewards, Leverage, Protocol): @@ -52,6 +56,7 @@ class FinalizedServices(Model): withdrawals_service: WithdrawalsService project_rewards_service: SavedProjectRewardsService projects_metadata_service: ProjectsMetadataService + projects_details_service: ProjectsDetailsService @staticmethod def create() -> "FinalizedServices": @@ -75,4 +80,5 @@ def create() -> "FinalizedServices": withdrawals_service=withdrawals_service, project_rewards_service=SavedProjectRewards(), projects_metadata_service=StaticProjectsMetadataService(), + projects_details_service=StaticProjectsDetailsService(), ) diff --git a/backend/app/modules/modules_factory/finalizing.py b/backend/app/modules/modules_factory/finalizing.py index 44e409221c..3c38584c7f 100644 --- a/backend/app/modules/modules_factory/finalizing.py +++ b/backend/app/modules/modules_factory/finalizing.py @@ -13,6 +13,7 @@ WithdrawalsService, SavedProjectRewardsService, ProjectsMetadataService, + ProjectsDetailsService, ) from app.modules.octant_rewards.general.service.pending import PendingOctantRewards from app.modules.octant_rewards.matched.pending import PendingOctantMatchedRewards @@ -29,6 +30,9 @@ StaticProjectsMetadataService, ) from app.pydantic import Model +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, +) class FinalizingOctantRewards(OctantRewards, Leverage, Protocol): @@ -50,6 +54,7 @@ class FinalizingServices(Model): withdrawals_service: WithdrawalsService project_rewards_service: SavedProjectRewardsService projects_metadata_service: ProjectsMetadataService + projects_details_service: ProjectsDetailsService @staticmethod def create() -> "FinalizingServices": @@ -90,4 +95,5 @@ def create() -> "FinalizingServices": withdrawals_service=withdrawals_service, project_rewards_service=SavedProjectRewards(), projects_metadata_service=StaticProjectsMetadataService(), + projects_details_service=StaticProjectsDetailsService(), ) diff --git a/backend/app/modules/modules_factory/future.py b/backend/app/modules/modules_factory/future.py index 51fd029936..64ea69165c 100644 --- a/backend/app/modules/modules_factory/future.py +++ b/backend/app/modules/modules_factory/future.py @@ -1,4 +1,8 @@ -from app.modules.modules_factory.protocols import OctantRewards, ProjectsMetadataService +from app.modules.modules_factory.protocols import ( + OctantRewards, + ProjectsMetadataService, + ProjectsDetailsService, +) from app.modules.octant_rewards.general.service.calculated import ( CalculatedOctantRewards, ) @@ -10,11 +14,15 @@ StaticProjectsMetadataService, ) from app.pydantic import Model +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, +) class FutureServices(Model): octant_rewards_service: OctantRewards projects_metadata_service: ProjectsMetadataService + projects_details_service: ProjectsDetailsService @staticmethod def create() -> "FutureServices": @@ -24,4 +32,5 @@ def create() -> "FutureServices": effective_deposits=ContractBalanceUserDeposits(), ), projects_metadata_service=StaticProjectsMetadataService(), + projects_details_service=StaticProjectsDetailsService(), ) diff --git a/backend/app/modules/modules_factory/pending.py b/backend/app/modules/modules_factory/pending.py index 5989d42d98..6b76c46406 100644 --- a/backend/app/modules/modules_factory/pending.py +++ b/backend/app/modules/modules_factory/pending.py @@ -20,6 +20,7 @@ MultisigSignatures, ProjectsMetadataService, UniquenessQuotients, + ProjectsDetailsService, ) from app.modules.multisig_signatures.service.offchain import OffchainMultisigSignatures from app.modules.octant_rewards.general.service.pending import PendingOctantRewards @@ -48,7 +49,15 @@ from app.modules.withdrawals.service.pending import PendingWithdrawals from app.pydantic import Model from app.shared.blockchain_types import compare_blockchain_types, ChainTypes -from app.constants import UQ_THRESHOLD_MAINNET, UQ_THRESHOLD_NOT_MAINNET +from app.constants import ( + UQ_THRESHOLD_MAINNET, + UQ_THRESHOLD_NOT_MAINNET, + TIMEOUT_LIST, + TIMEOUT_LIST_NOT_MAINNET, +) +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, +) class PendingOctantRewardsService(OctantRewards, Leverage, Protocol): @@ -87,6 +96,7 @@ class PendingServices(Model): project_rewards_service: PendingProjectRewardsProtocol multisig_signatures_service: MultisigSignatures projects_metadata_service: ProjectsMetadataService + projects_details_service: ProjectsDetailsService uniqueness_quotients: UniquenessQuotients @staticmethod @@ -98,8 +108,9 @@ def create(chain_id: int) -> "PendingServices": is_mainnet = compare_blockchain_types(chain_id, ChainTypes.MAINNET) uq_threshold = UQ_THRESHOLD_MAINNET if is_mainnet else UQ_THRESHOLD_NOT_MAINNET + timeout_list = TIMEOUT_LIST if is_mainnet else TIMEOUT_LIST_NOT_MAINNET uniqueness_quotients = PreliminaryUQ( - antisybil=GitcoinPassportAntisybil(), + antisybil=GitcoinPassportAntisybil(timeout_list=timeout_list), budgets=saved_user_budgets, uq_threshold=uq_threshold, ) @@ -159,5 +170,6 @@ def create(chain_id: int) -> "PendingServices": project_rewards_service=project_rewards, multisig_signatures_service=multisig_signatures, projects_metadata_service=StaticProjectsMetadataService(), + projects_details_service=StaticProjectsDetailsService(), uniqueness_quotients=uniqueness_quotients, ) diff --git a/backend/app/modules/modules_factory/pre_pending.py b/backend/app/modules/modules_factory/pre_pending.py index 46867a8be2..e5bfd8c63b 100644 --- a/backend/app/modules/modules_factory/pre_pending.py +++ b/backend/app/modules/modules_factory/pre_pending.py @@ -9,6 +9,7 @@ UserEffectiveDeposits, SavedProjectRewardsService, ProjectsMetadataService, + ProjectsDetailsService, ) from app.modules.octant_rewards.general.service.calculated import ( CalculatedOctantRewards, @@ -24,6 +25,9 @@ ) from app.pydantic import Model from app.shared.blockchain_types import compare_blockchain_types, ChainTypes +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, +) class PrePendingUserDeposits(UserEffectiveDeposits, AllUserEffectiveDeposits, Protocol): @@ -36,6 +40,7 @@ class PrePendingServices(Model): pending_snapshots_service: PendingSnapshots project_rewards_service: SavedProjectRewardsService projects_metadata_service: ProjectsMetadataService + projects_details_service: ProjectsDetailsService @staticmethod def create(chain_id: int) -> "PrePendingServices": @@ -63,4 +68,5 @@ def create(chain_id: int) -> "PrePendingServices": pending_snapshots_service=pending_snapshots_service, project_rewards_service=SavedProjectRewards(), projects_metadata_service=StaticProjectsMetadataService(), + projects_details_service=StaticProjectsDetailsService(), ) diff --git a/backend/app/modules/modules_factory/protocols.py b/backend/app/modules/modules_factory/protocols.py index f761b02585..0bb8f4957d 100644 --- a/backend/app/modules/modules_factory/protocols.py +++ b/backend/app/modules/modules_factory/protocols.py @@ -19,6 +19,7 @@ ) from app.modules.history.dto import UserHistoryDTO from app.modules.multisig_signatures.dto import Signature +from app.modules.projects.details.service.projects_details import ProjectsDetailsDTO @runtime_checkable @@ -226,6 +227,14 @@ def get_projects_metadata(self, context: Context) -> ProjectsMetadata: ... +@runtime_checkable +class ProjectsDetailsService(Protocol): + def get_projects_details_by_search_phrase( + self, context: Context, search_phrase: str + ) -> ProjectsDetailsDTO: + ... + + @runtime_checkable class UserAllocationNonceProtocol(Protocol): def get_user_next_nonce(self, user_address: str) -> int: diff --git a/backend/app/modules/multisig_signatures/service/offchain.py b/backend/app/modules/multisig_signatures/service/offchain.py index 6a5c50c141..f0716f7b07 100644 --- a/backend/app/modules/multisig_signatures/service/offchain.py +++ b/backend/app/modules/multisig_signatures/service/offchain.py @@ -1,4 +1,5 @@ -from typing import List +from http import HTTPStatus +from typing import List, Dict from app.context.manager import Context from app.exceptions import InvalidMultisigSignatureRequest, InvalidMultisigAddress @@ -6,8 +7,8 @@ from app.infrastructure import database from app.infrastructure.database.models import MultisigSignatures from app.infrastructure.database.multisig_signature import SigStatus, MultisigFilters - from app.infrastructure.external_api.common import retry_request +from app.infrastructure.external_api.safe.message_details import get_message_details from app.modules.common.allocations.deserializer import deserialize_payload from app.modules.common.crypto.eip1271 import get_message_hash from app.modules.common.crypto.signature import ( @@ -23,16 +24,13 @@ ) from app.modules.multisig_signatures.dto import Signature from app.pydantic import Model -from app.infrastructure.external_api.safe.message_details import get_message_details class OffchainMultisigSignatures(Model): is_mainnet: bool = False - verifiers: dict[SignatureOpType, Verifier] + verifiers: Dict[SignatureOpType, Verifier] - staged_signatures: list[ - MultisigSignatures - ] = [] # TODO make it invulnerable for data race & race conditions + staged_signatures: List[MultisigSignatures] = [] def get_last_pending_signature( self, _: Context, user_address: str, op_type: SignatureOpType @@ -104,7 +102,7 @@ def save_pending_signature( def _verify_owner(self, user_address: str, message_hash: str): message_details = retry_request( req_func=get_message_details, - status_code=404, + status_code=HTTPStatus.NOT_FOUND, message_hash=message_hash, is_mainnet=self.is_mainnet, ) diff --git a/backend/app/modules/octant_rewards/controller.py b/backend/app/modules/octant_rewards/controller.py index b6cdd22cb0..7f92a4ec0d 100644 --- a/backend/app/modules/octant_rewards/controller.py +++ b/backend/app/modules/octant_rewards/controller.py @@ -3,6 +3,7 @@ from app.exceptions import NotImplementedForGivenEpochState from app.modules.dto import OctantRewardsDTO from app.modules.registry import get_services +from app.modules.octant_rewards import core def get_octant_rewards(epoch_num: int) -> OctantRewardsDTO: @@ -24,3 +25,8 @@ def get_last_finalized_epoch_leverage() -> float: service = get_services(context.epoch_state).octant_rewards_service return service.get_leverage(context) + + +def get_epoch_rewards_rate(epoch_num: int) -> float: + context = epoch_context(epoch_num) + return core.get_rewards_rate(context.epoch_details.epoch_num) diff --git a/backend/app/modules/octant_rewards/core.py b/backend/app/modules/octant_rewards/core.py index f6cb96d699..37476b43ce 100644 --- a/backend/app/modules/octant_rewards/core.py +++ b/backend/app/modules/octant_rewards/core.py @@ -7,6 +7,7 @@ from app.engine.octant_rewards.operational_cost import OperationalCostPayload from app.engine.octant_rewards.ppf import PPFPayload from app.engine.octant_rewards.total_and_individual import TotalAndAllIndividualPayload +from app.modules.staking.proceeds.core import ESTIMATED_STAKING_REWARDS_RATE @dataclass @@ -66,3 +67,11 @@ def calculate_rewards( ppf_value=ppf_value, community_fund=cf_value, ) + + +def get_rewards_rate(_: int) -> float: + """ + Returns the rewards rate for the given epoch. + """ + # TODO Staking Rewards Rate is a static value for now but it may be calculated dynamically in the future: https://linear.app/golemfoundation/issue/OCT-1916/make-apr-a-dynamically-computed-one + return ESTIMATED_STAKING_REWARDS_RATE diff --git a/backend/app/modules/octant_rewards/general/service/finalized.py b/backend/app/modules/octant_rewards/general/service/finalized.py index ddb3bda5e0..2edfb3d85e 100644 --- a/backend/app/modules/octant_rewards/general/service/finalized.py +++ b/backend/app/modules/octant_rewards/general/service/finalized.py @@ -4,6 +4,8 @@ from app.infrastructure import database from app.modules.dto import OctantRewardsDTO from app.pydantic import Model +from app.engine.octant_rewards.leftover import LeftoverPayload +from app.engine.projects.rewards.allocations import ProjectAllocationsPayload class FinalizedOctantRewards(Model): @@ -15,6 +17,37 @@ def get_octant_rewards(self, context: Context) -> OctantRewardsDTO: context.epoch_details.epoch_num ) + leftover_payload = LeftoverPayload( + staking_proceeds=int(pending_snapshot.eth_proceeds), + operational_cost=int(pending_snapshot.operational_cost), + community_fund=pending_snapshot.validated_community_fund, + ppf=pending_snapshot.validated_ppf, + total_withdrawals=int(finalized_snapshot.total_withdrawals), + ) + + unused_matched_rewards = context.epoch_settings.octant_rewards.leftover.extract_unused_matched_rewards( + int(finalized_snapshot.leftover), leftover_payload + ) + + allocations_for_epoch = database.allocations.get_all_by_epoch( + context.epoch_details.epoch_num + ) + grouped_allocations = context.epoch_settings.project.rewards.projects_allocations.segregate_allocations( + ProjectAllocationsPayload(allocations=allocations_for_epoch) + ) + allocations_result = context.epoch_settings.project.rewards.get_total_allocations_below_threshold( + grouped_allocations + ) + allocations_sum = allocations_result.total + allocations_below_threshold = allocations_result.below_threshold + + donated_to_projects = ( + int(finalized_snapshot.matched_rewards) + - unused_matched_rewards + + allocations_sum + - allocations_below_threshold + ) + return OctantRewardsDTO( staking_proceeds=int(pending_snapshot.eth_proceeds), locked_ratio=Decimal(pending_snapshot.locked_ratio), @@ -28,6 +61,7 @@ def get_octant_rewards(self, context: Context) -> OctantRewardsDTO: total_withdrawals=int(finalized_snapshot.total_withdrawals), ppf=pending_snapshot.validated_ppf, community_fund=pending_snapshot.validated_community_fund, + donated_to_projects=donated_to_projects, ) def get_leverage(self, context: Context) -> float: diff --git a/backend/app/modules/octant_rewards/general/service/pending.py b/backend/app/modules/octant_rewards/general/service/pending.py index 16717819ad..52a13f54ad 100644 --- a/backend/app/modules/octant_rewards/general/service/pending.py +++ b/backend/app/modules/octant_rewards/general/service/pending.py @@ -27,8 +27,8 @@ class ProjectRewards(Protocol): def get_finalized_project_rewards( self, context: Context, - allocations: list[AllocationDTO], - all_projects: list[str], + allocations: List[AllocationDTO], + all_projects: List[str], matched_rewards: int, ) -> FinalizedProjectRewards: ... @@ -51,6 +51,7 @@ def get_octant_rewards(self, context: Context) -> OctantRewardsDTO: context.epoch_details.epoch_num ) matched_rewards = self.octant_matched_rewards.get_matched_rewards(context) + project_rewards = self._get_project_rewards(context, matched_rewards) return OctantRewardsDTO( staking_proceeds=int(pending_snapshot.eth_proceeds), @@ -63,7 +64,10 @@ def get_octant_rewards(self, context: Context) -> OctantRewardsDTO: ppf=pending_snapshot.validated_ppf, matched_rewards=matched_rewards, patrons_rewards=self.patrons_mode.get_patrons_rewards(context), - leftover=self.get_leftover(context, pending_snapshot, matched_rewards), + leftover=self._get_leftover( + context, pending_snapshot, matched_rewards, project_rewards + ), + donated_to_projects=self._get_donated_to_projects(project_rewards), ) def get_matched_rewards(self, context: Context) -> int: @@ -79,19 +83,14 @@ def get_leverage(self, context: Context) -> float: matched_rewards, allocations_sum ) - def get_leftover( + def _get_leftover( self, context: Context, pending_snapshot: PendingEpochSnapshot, matched_rewards: int, + project_rewards: FinalizedProjectRewards, ) -> int: - allocations = database.allocations.get_all_with_uqs( - context.epoch_details.epoch_num - ) _, user_rewards = self.user_rewards.get_claimed_rewards(context) - project_rewards = self.project_rewards.get_finalized_project_rewards( - context, allocations, context.projects_details.projects, matched_rewards - ) return context.epoch_settings.octant_rewards.leftover.calculate_leftover( LeftoverPayload( @@ -106,3 +105,19 @@ def get_leftover( used_matched_rewards=sum(r.matched for r in project_rewards.rewards), ) ) + + def _get_donated_to_projects(self, project_rewards: FinalizedProjectRewards) -> int: + total_user_donations_with_used_matched_rewards = sum( + r.amount for r in project_rewards.rewards + ) + + return total_user_donations_with_used_matched_rewards + + def _get_project_rewards(self, context: Context, matched_rewards: int): + allocations = database.allocations.get_all_with_uqs( + context.epoch_details.epoch_num + ) + project_rewards = self.project_rewards.get_finalized_project_rewards( + context, allocations, context.projects_details.projects, matched_rewards + ) + return project_rewards diff --git a/backend/app/modules/projects/details/controller.py b/backend/app/modules/projects/details/controller.py new file mode 100644 index 0000000000..ffb7ba1eee --- /dev/null +++ b/backend/app/modules/projects/details/controller.py @@ -0,0 +1,34 @@ +from typing import List, Dict + +from app.context.manager import epoch_context +from app.modules.registry import get_services +from app.modules.projects.details.service.projects_details import ProjectsDetailsDTO + + +def get_projects_details_for_multiple_params( + epochs: List[int], search_phrases: List[str] +) -> List[Dict[str, str]]: + searched_projects = [] + for epoch in epochs: + for search_phrase in search_phrases: + project_details = get_projects_details(epoch, search_phrase) + searched_projects.extend(project_details) + return searched_projects + + +def get_projects_details(epoch: int, search_phrase: str) -> List[Dict[str, str]]: + context = epoch_context(epoch) + + service = get_services(context.epoch_state).projects_details_service + + filtered_projects: ProjectsDetailsDTO = ( + service.get_projects_details_by_search_phrase(context, search_phrase) + ) + return [ + { + "name": project["name"], + "address": project["address"], + "epoch": project["epoch"], + } + for project in filtered_projects.projects_details + ] diff --git a/backend/app/modules/projects/details/core.py b/backend/app/modules/projects/details/core.py new file mode 100644 index 0000000000..42d30dbd56 --- /dev/null +++ b/backend/app/modules/projects/details/core.py @@ -0,0 +1,20 @@ +from typing import List + +from app.infrastructure.database.models import ProjectsDetails + + +def filter_projects_details( + projects_details: List[ProjectsDetails], search_phrase: str +) -> List[ProjectsDetails]: + search_phrase = search_phrase.strip().lower() + + filtered_project_details = [] + + for project_details in projects_details: + if ( + search_phrase in project_details.name.lower() + or search_phrase in project_details.address.lower() + ): + filtered_project_details.append(project_details) + + return filtered_project_details diff --git a/backend/app/modules/projects/details/service/projects_details.py b/backend/app/modules/projects/details/service/projects_details.py new file mode 100644 index 0000000000..b179c8217c --- /dev/null +++ b/backend/app/modules/projects/details/service/projects_details.py @@ -0,0 +1,32 @@ +from dataclasses import dataclass +from typing import List, Dict + +from app.pydantic import Model +from app.context.manager import Context +from app.infrastructure.database.projects_details import get_projects_details_for_epoch +from app.modules.projects.details.core import filter_projects_details + + +@dataclass +class ProjectsDetailsDTO: + projects_details: List[Dict[str, str]] + + +class StaticProjectsDetailsService(Model): + def get_projects_details_by_search_phrase( + self, context: Context, search_phrase: str + ) -> ProjectsDetailsDTO: + epoch = context.epoch_details.epoch_num + + projects_details = get_projects_details_for_epoch(epoch) + + filtered_projects_details = filter_projects_details( + projects_details, search_phrase + ) + + return ProjectsDetailsDTO( + projects_details=[ + {"name": project.name, "address": project.address, "epoch": str(epoch)} + for project in filtered_projects_details + ] + ) diff --git a/backend/app/modules/projects/rewards/service/finalizing.py b/backend/app/modules/projects/rewards/service/finalizing.py index 726256fb6c..90a9331f16 100644 --- a/backend/app/modules/projects/rewards/service/finalizing.py +++ b/backend/app/modules/projects/rewards/service/finalizing.py @@ -1,3 +1,5 @@ +from typing import List + from app.context.manager import Context from app.modules.common.project_rewards import get_projects_rewards, AllocationsPayload from app.modules.dto import AllocationDTO, ProjectAccountFundsDTO @@ -9,8 +11,8 @@ class FinalizingProjectRewards(Model): def get_finalized_project_rewards( self, context: Context, - allocations: list[AllocationDTO], - all_projects: list[str], + allocations: List[AllocationDTO], + all_projects: List[str], matched_rewards: int, ) -> FinalizedProjectRewards: allocations_payload = AllocationsPayload( diff --git a/backend/app/modules/staking/proceeds/core.py b/backend/app/modules/staking/proceeds/core.py index 5067607990..3bcb293953 100644 --- a/backend/app/modules/staking/proceeds/core.py +++ b/backend/app/modules/staking/proceeds/core.py @@ -8,17 +8,17 @@ ESTIMATED_STAKED_AMOUNT = 100000_000000000_000000000 # TODO call an API to get a real value instead of hardcoding: https://linear.app/golemfoundation/issue/OCT-902/api-call-to-get-validators-api -ESTIMATED_STAKING_APR = 0.038 +ESTIMATED_STAKING_REWARDS_RATE = 0.038 def estimate_staking_proceeds( epoch_duration_sec: int, staked_amount=ESTIMATED_STAKED_AMOUNT, - apr=ESTIMATED_STAKING_APR, + staking_rewards=ESTIMATED_STAKING_REWARDS_RATE, ) -> int: if epoch_duration_sec <= 0: return 0 - return int(int(staked_amount * apr) / 31536000 * epoch_duration_sec) + return int(int(staked_amount * staking_rewards) / 31536000 * epoch_duration_sec) def sum_mev( diff --git a/backend/app/modules/uq/core.py b/backend/app/modules/uq/core.py index b5943ef9d4..c327415326 100644 --- a/backend/app/modules/uq/core.py +++ b/backend/app/modules/uq/core.py @@ -1,11 +1,16 @@ from decimal import Decimal from typing import List, Tuple -from app.constants import LOW_UQ_SCORE, MAX_UQ_SCORE +from app.constants import LOW_UQ_SCORE, MAX_UQ_SCORE, NULLIFIED_UQ_SCORE from app.infrastructure.database.uniqueness_quotient import get_all_uqs_by_epoch -def calculate_uq(gp_score: float, uq_threshold: int) -> Decimal: +def calculate_uq( + gp_score: float, uq_threshold: int, *, is_on_timeout_list: bool = False +) -> Decimal: + if is_on_timeout_list is True: + return NULLIFIED_UQ_SCORE + if gp_score >= uq_threshold: return MAX_UQ_SCORE diff --git a/backend/app/modules/uq/service/preliminary.py b/backend/app/modules/uq/service/preliminary.py index 3779f34bc1..fc9e293e2c 100644 --- a/backend/app/modules/uq/service/preliminary.py +++ b/backend/app/modules/uq/service/preliminary.py @@ -1,6 +1,5 @@ -from datetime import datetime from decimal import Decimal -from typing import Protocol, Optional, Tuple, runtime_checkable +from typing import Protocol, Optional, runtime_checkable, Tuple from app.context.manager import Context from app.infrastructure.database.uniqueness_quotient import ( @@ -8,6 +7,7 @@ save_uq_from_address, ) from app.modules.uq.core import calculate_uq +from app.modules.user.antisybil.dto import AntisybilStatusDTO from app.pydantic import Model @@ -15,7 +15,7 @@ class Antisybil(Protocol): def get_antisybil_status( self, _: Context, user_address: str - ) -> Optional[Tuple[float, datetime]]: + ) -> Optional[AntisybilStatusDTO]: ... @@ -51,12 +51,14 @@ def retrieve( return uq_score def calculate(self, context: Context, user_address: str) -> Decimal: - gp_score = self._get_gp_score(context, user_address) + gp_score, is_on_timeout_list = self._get_gp_score(context, user_address) - return calculate_uq(gp_score, self.uq_threshold) + return calculate_uq( + gp_score, self.uq_threshold, is_on_timeout_list=is_on_timeout_list + ) - def _get_gp_score(self, context: Context, address: str) -> float: + def _get_gp_score(self, context: Context, address: str) -> Tuple[float, bool]: antisybil_status = self.antisybil.get_antisybil_status(context, address) if antisybil_status is None: - return 0.0 - return antisybil_status[0] + return 0.0, False + return antisybil_status.score, antisybil_status.is_on_timeout_list diff --git a/backend/app/modules/user/antisybil/controller.py b/backend/app/modules/user/antisybil/controller.py index 34951e20e7..537b92fca1 100644 --- a/backend/app/modules/user/antisybil/controller.py +++ b/backend/app/modules/user/antisybil/controller.py @@ -1,18 +1,18 @@ -from datetime import datetime -from typing import Tuple +from typing import Optional from app.context.epoch_state import EpochState from app.context.manager import state_context from app.modules.registry import get_services +from app.modules.user.antisybil.dto import AntisybilStatusDTO -def get_user_antisybil_status(user_address: str) -> Tuple[int, datetime]: +def get_user_antisybil_status(user_address: str) -> Optional[AntisybilStatusDTO]: context = state_context(EpochState.CURRENT) service = get_services(context.epoch_state).user_antisybil_service return service.get_antisybil_status(context, user_address) -def update_user_antisybil_status(user_address: str) -> Tuple[int, datetime]: +def update_user_antisybil_status(user_address: str) -> Optional[AntisybilStatusDTO]: context = state_context(EpochState.CURRENT) service = get_services(context.epoch_state).user_antisybil_service diff --git a/backend/app/modules/user/antisybil/core.py b/backend/app/modules/user/antisybil/core.py new file mode 100644 index 0000000000..e62a01de78 --- /dev/null +++ b/backend/app/modules/user/antisybil/core.py @@ -0,0 +1,65 @@ +import json +from typing import Optional + +from app.modules.user.antisybil.dto import AntisybilStatusDTO +from app.constants import ( + GUEST_LIST, + GUEST_LIST_STAMP_PROVIDERS, + GTC_STAKING_STAMP_PROVIDERS_AND_SCORES, +) +from app.infrastructure.database.models import GPStamps + + +def determine_antisybil_score( + score: GPStamps, user_address: str, timeout_list: set +) -> Optional[AntisybilStatusDTO]: + """ + Determine the antisybil score for a user. + 1. Timeout list users will always have a score of 0.0 and has a higher priority than guest list users. + 2. Guest list users will have a score increased by 21.0 if they have not been stamped by a guest list provider. + 3. If user has any stamps related to GTC staking, scores of those stamps are subtracted. + """ + if score is None: + return None + + potential_score = _apply_gtc_staking_stamp_nullification(score.score, score) + + if user_address in timeout_list: + return AntisybilStatusDTO( + score=0.0, expires_at=score.expires_at, is_on_timeout_list=True + ) + elif user_address in GUEST_LIST and not _has_guest_stamp_applied_by_gp(score): + return AntisybilStatusDTO( + score=potential_score + 21.0, + expires_at=score.expires_at, + is_on_timeout_list=False, + ) + + return AntisybilStatusDTO( + score=potential_score, expires_at=score.expires_at, is_on_timeout_list=False + ) + + +def _get_provider(stamp) -> str: + return stamp["credential"]["credentialSubject"]["provider"] + + +def _has_guest_stamp_applied_by_gp(score: GPStamps) -> bool: + all_stamps = json.loads(score.stamps) + stamps = [ + stamp + for stamp in all_stamps + if _get_provider(stamp) in GUEST_LIST_STAMP_PROVIDERS + ] + return len(stamps) > 0 + + +def _apply_gtc_staking_stamp_nullification(score: int, stamps: GPStamps) -> int: + "Take score and stamps as returned by Passport and remove score associated with GTC staking" + delta = 0 + all_stamps = json.loads(stamps.stamps) + providers = [_get_provider(stamp) for stamp in all_stamps] + for provider in providers: + if provider in GTC_STAKING_STAMP_PROVIDERS_AND_SCORES.keys(): + delta = delta + GTC_STAKING_STAMP_PROVIDERS_AND_SCORES[provider] + return score - delta diff --git a/backend/app/modules/user/antisybil/dto.py b/backend/app/modules/user/antisybil/dto.py new file mode 100644 index 0000000000..eba87c843e --- /dev/null +++ b/backend/app/modules/user/antisybil/dto.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass +from datetime import datetime + + +@dataclass +class AntisybilStatusDTO: + score: float + expires_at: datetime + is_on_timeout_list: bool diff --git a/backend/app/modules/user/antisybil/service/guest_list.py b/backend/app/modules/user/antisybil/service/guest_list.py deleted file mode 100644 index 6343a683e7..0000000000 --- a/backend/app/modules/user/antisybil/service/guest_list.py +++ /dev/null @@ -1,471 +0,0 @@ -guest_list = set( - [ - "0x16f3f2f0ba34973937A1ebb989a295Ca106b67C7", - "0xBB5935dAaFbacAE82c8D2CA8377F16073D70061a", - "0xba84B5cA750b33DfAdDBFdD1B7C6887885a34977", - "0x4e9A05226993F094A56A3472C8c816F2599423A6", - "0x40DE3299Bd8a10D8Ac3f32C1A55DE40640cF9B75", - "0xC33F87697EF41e0E95e7a55d1ec8180F04088578", - "0xCBc924183Bc32D02746Fa8D38843B5Ce08662eB4", - "0xDc9C5e34959eC3643AF1e1D34A83D6b251AAb1eF", - "0x762BBc211990D0a356F35E4D500843F59d223C2e", - "0x55187a1165EBB441A1BF227fff1EB0D32a65bc46", - "0x7aE59f3F2B2E5f3842B50a15bCb5247c5De881Be", - "0x59072B3a3287F4a75cadfb36D671A2f0d1959B09", - "0x4A5da2a1D3258dF8FFb431Cf0110FE9b98ADeEbf", - "0x514A9771Af8Afe71057666b680238dFBeA578d65", - "0xE70055e9575f15A6f51F3068901D73ac63952adF", - "0x9e831B58001e2b69F70C892e4F8ce9d2118B7E00", - "0x51c1C7f1e168a36Bf1FaBFD91E98b43476a6B14D", - "0x33878e070db7f70D2953Fe0278Cd32aDf8104572", - "0x3df13B9bd79158f0cccDDd0833cF774178e3d2e9", - "0xB9573982875b83aaDc1296726E2ae77D13D9B98F", - "0xE862E2C1ca94eAcfEDe3c95a217c15EF0086a29D", - "0x0442A9aBbc93058a873c371F21CC366338254A88", - "0x0194325BF525Be0D4fBB0856894cEd74Da3B8356", - "0x399e0Ae23663F27181Ebb4e66Ec504b3AAB25541", - "0x9f729294b308f79243285348A7Be3f58ae5ED31A", - "0xb62E762Af637b49Eb4870BCe8fE21bffF189e495", - "0x5725a458b319d73B8Ec84c47de80620E7B191B0C", - "0x57Ccc081824b43B75986727875929AF3A6Ad721C", - "0xf13e477365B0FAa64130DA2FF663aAb20d32d929", - "0xFD868dB0696ef762351F8421535cC5f9F423B23C", - "0x30043aAbBCeBbD887437Ec4F0Cfe6d4c0eB5CC64", - "0xAa01DeC5307CF17F20881A3286dcaA062578cea7", - "0x3FFD0C300fa4a021364Ae7e85a7b0d3a02133f99", - "0xBEa26DE685Ef828b60cA53b40Ecc9Bab35645fDF", - "0x4103CFcb300599dFcB31dBc95d919592619B4EAc", - "0x22bAac1E95efC010E35D5eD643BB16c9dB254a11", - "0x686A484bc2E2bE79f358c7055e8539A69413A3Ed", - "0x073a360C372FD51Bd6E56B4a4d73790fDAec4641", - "0xdd0206010CA82fF22303b58863b3a6f3006C86C4", - "0x25FA68A4c340202737EDBC67fD1a2Ec8DE872dB6", - "0x5b655EDa7D101f98934392Cc3610BcB25b633789", - "0x32cEfb2dC869BBfe636f7547CDa43f561Bf88d5A", - "0xA4369e39e3ED13593Adb0142A1ea5d08AbdF99C4", - "0xA8F0048A0d1A04663Ca5010d0bEaC5BCAEeA0eef", - "0x65F632cfe8015B7ae6976e549645ed04cde60fe4", - "0xb35E0a0D00c640ab75fAD3cf3B83264bC64D23eC", - "0xafA3E6E29D99337b166b83fB24bA17b19764B49D", - "0x57DD1517c12659365E59F71129Fa9B1611Dd18AF", - "0x9120FfD5d04ca4B26AaBCe611989A8F026dc099a", - "0x2AC6A3561a43f06d62602eF9728C2B9eEc393326", - "0x297Aa50D0557c865F6C9B0AA0a91f41C26E55eE6", - "0x9Ff46343d0b652D6e766F85f9aE91653869349a5", - "0xEd36bf0b2b17768E782Db2ece6A327055b2f3e9C", - "0xC28D2fDFE6d5a482d32f855457Bb5F8cAcdB32b1", - "0x1d44404C1C53991Ec33095225da173d544Cd4Af3", - "0x5d9fbd984B9CeC714a4B14c38Ea83bBC82d06d69", - "0x5Bc0AEbdbab698e12FD33A2E133e6858fe6Cdd76", - "0x66805D8B82664Acab4CbE0C0498889dDE9aF7841", - "0xaF7610578F54c7De7563655AaF461E2CbeCB08C6", - "0x6c3F373Baec5D2d0Fb3C82C4f3Db5E48873ae363", - "0x015122A625b45f68E6D795C0Ab99fC7107e4c3B9", - "0x508A4F07B60BA0940283Cd4e32d5DEb0CC38AdF7", - "0xb150c9bEd10a8C62997d58a81c4e1fA75160643e", - "0x212647c56BA10ee429a838bc567dFb03A8D054Ba", - "0x73306dAb0D39A4D47df4972c7022CB2cac075D4e", - "0x914D5d84aAA064207C2c31014426227405edab41", - "0x3FBcEC42405391B1fb377664daA5AE7Bc9Ba7BF5", - "0x8c8296a0042E842Cb865DfFD94678c941fD24bAE", - "0xf5c2087877218AA979Dd0e2e5108837199aF44D2", - "0x529dc928E67D8A43133D10769B308F1D5A629401", - "0xF1bb436c29E46B1987bC825879ffc9c34Ab97f99", - "0xFDDE7aE208B3596f1982D66F6BAe4cDabF29244b", - "0x02e4Cc9ffF7566563618fb21B3BB10Eab4B3D726", - "0xd8821dbbcb8ea0c14Bd1F0aCbFBeBB3Fd984269b", - "0x31d23825aFbda5B6B1690Bbdbbb8117B5ea0f8E4", - "0x731022D6De647991864203a35dFaD1A192240d07", - "0xEb5e0B8e80FCe271c13F533fA728D7bB03cafa4c", - "0xFC967DE4e029fdcD16B418DaC2147d282C93085b", - "0x801a6d6dBC1e40466E131aA21D951629A9efAB4e", - "0x4892139De0e73141438D9E55D593171C0Cc6B143", - "0x8124eFC94c951cF41D4B0B42794C678458a00726", - "0x81cc36DdA894256aa95458F78B4029381b09BDfb", - "0x4Dcb2BCA3450B427F3d1b424C885259D05363080", - "0x7Dd8030F9d33Af4a40ee074f990892E825132e61", - "0x432C53218A11bEd08d238Cf84ff547CE4fe933ab", - "0xE77ad9c5af60332D24E5531B51A6B7f61D0B8703", - "0x0f792e55668AD78476d4B563E6EB1228D636a71e", - "0x583bBaDA56bb535BCBb31877A620A6ff2A25CeA5", - "0x5C0E777dC8F3De6b0911b44DbBDD8Bf71b2E8e38", - "0x8a4a50B13Fd2cb36FeB96c408CB98B4c9F2b8F25", - "0x1e55C85801a2C4F0beC57c84742a8eF3d72dE57B", - "0x26d3bE736aB6b5D8A3266fFCC0895dDc1bc19a38", - "0x809C9f8dd8CA93A41c3adca4972Fa234C28F7714", - "0xfB94f39B150Ae661F85762154c0CadC65E083791", - "0x4B7C0Da1C299Ce824f55A0190Efb13663442FA2c", - "0x77E64560Bd6C323c075F206a5AB9dD6850F31609", - "0x0F46540678c7e6D2eF983B382CC07Fa815AB148c", - "0x82073f802547fEeEc0fd49719a3D7697fB66076a", - "0xBAab83De8DbA764bF02a530cad33555bD23eba22", - "0x890a0047f8D573347872cB6C019F86552f2367d6", - "0x14D92832265eeAFDEF9e526356FEfc90105966c3", - "0x512B436cB2Ed6016e80d4F89ca578F99DBBccb61", - "0x696Ee4AE0b15feae8ED1AfC865930e0ea65b1f3F", - "0xbb4D885fD41c807e8eCC2dD9e6295a7F96Adb0EB", - "0xB1dE969883b1FdD90a43fF475A5171a3CfEfe76d", - "0x7DBF6820D32cFBd5D656bf9BFf0deF229B37cF0E", - "0xfE2e3cCEE9714b29Ab2FB4E940e52672194815fC", - "0x57fb3f4b027fbaDbd8d20Eb5E48feb1e2b02DF30", - "0x9AE494FBAc34682c122A1D4e3D6ae1Eb5404A469", - "0xb2b9300475aF157676C44eE64d39a5eB3C294DbD", - "0x01Bc28E036b6e75247Fe8F49f0a8b9410b19d851", - "0xcCE9A28b570946123f392Cf1DbfA6D2D5e636a1f", - "0xb2a3b5B9d2C0f07cBA328b58737147cfc172EB9f", - "0xCC3d7F9fE6946979215A901BbA385a88FdabBBf4", - "0x38f80f8f76B1C44B2beeFB63bb561F570fb6ddB6", - "0xd82803b7B9A5EB1D5FC558FD619afC6c031cd0B1", - "0x844AeeD1B294Ef9632c18E73F57ef77D0A23D0e2", - "0x9cD7D1981B3e15a2DEE4d512ac60E0579Ae18546", - "0xEBCd250474C27cBaD3C56f3F34e08F97b370AC2d", - "0xDA47bdcC48f26FB4709f90316341D9104cB1fb89", - "0x5cbB6ad79008908aA125667D1300558D9253B589", - "0x1078DaA844CDF1EDB51E5189c8b113B80a6A6957", - "0x8341c4106523b49fc247f84e412Bb2AF5597038f", - "0xCe57ebEd9aC38402DcAA44f65a1c9b04e26b8283", - "0x2dd2036C9Db2ADA2739509AF0047c00C8b9291EF", - "0xa77294828d42B538890fa6E97AdFfE9305536171", - "0x8dA48e5846c06B558970ACd42EDc7Da8799481E4", - "0x50418699cB44BfDa9c9afc9B7a0b0d244d8927D2", - "0x936d69AbCD9acdC89455EEFAf744044fFC1CA660", - "0x90C32e6B29794Fd7f5BbA2BBEE74e924078B3f9b", - "0x362B7e0599E950b921ca9D86336ca409208FFDEC", - "0xd98aD1Fd4aa0E1c876d91968D1385aa9E1Aa98df", - "0xD2602C7bDFC9F413974e944280BbFae275d1B1b6", - "0x731A2e51ebfAeBacF8477E992CDEB1E8eacf519C", - "0x072d63796C4FE69B306a23E1D01156d51F7B3e16", - "0x051010142A0B9de7F0Fd8fb31d085407287F6381", - "0x8498843f6D9046f7b59482978E152D61869203bC", - "0xB48ef8e4e7Bef79ddF64d4424151f003a59BfbfB", - "0xb423A138fD171c28d90A5883A01ec92fF3D63609", - "0xAfA3a2528E8baAd576a83ffC52dB9f100dEbe307", - "0x055fdA7Eb509cc338C898b0F698B7624387AB813", - "0x0B3BD83E857997b370FaDC8504fB712244F6786C", - "0x8D12A71Cb933A4222d42feCBb4ba9c15e455305b", - "0xDEF3D19ff35a42F5b8E3c706c8fD287De72e6D15", - "0x19a2BC678785BAD6A947A87494D480DAD57711c6", - "0x2c3E79D3DCE90FB0886C89Ec602E61757E589a94", - "0xe8aa836a597a66724D678860D105561c13E95bFa", - "0x3352a3277d2B74A773Fa6E68a625FcB18E4Fc282", - "0x2df292AF809Fd693D94C7D17E36BE352e15Bb98a", - "0x269Aa10398Aaa695259C3E8211ab27a15004110C", - "0x02d9c84a495986b8b3C3347Ad16849DCB1b9793e", - "0x8FDA1Daa6a674C1726d1896E3054B9a82d123F12", - "0x1021e61f2cDd8bB295b0e64A20eBB7D8ec3734bf", - "0x58d7d9c971A613117E493062bEC1A6A5484f2780", - "0x2bb96f44b9709b02189A50B377755edC30bc65C7", - "0x7bE20B02095944657275eD608615A39931d783F2", - "0x4AA51a723882ee676FeC444D4561c5eE16c339E9", - "0x1B243D42F53924118646EFaec5b3f6116b563960", - "0x01b7348EC3fb20Ab1f40b97Cc82df44aeD360768", - "0xbf4C0104dbfb028f3484CfAC17BB22aa15E5c7E2", - "0xCc3B817D4ABa7698EaafB4C68E7688CF61B0BF46", - "0x572E1b86471c900Cd16AFa9cBB7701862D0e70cB", - "0x602Ac8C3f61b351be325FEeb58842EF557431c2e", - "0x8d0CD1AB81EaDa4F92C7cb5c8DBc25C69cc296AD", - "0xAE2C7AB762317DB453317b70f1f40145755fAfb7", - "0x7bdae9AAbE238188c4882D48a3aEE21288A38eD0", - "0x96e4152f00894f677d860023b9784d578bC1c145", - "0xF572C9b11E757d3580C7C7310630cd488E8EA736", - "0x3769092DBfa6eb34434fB5B7cf0eB06E710728F3", - "0xCA72c93172BA6EfF168E59e7F17C3C7A8FeA9B62", - "0x1c0AcCc24e1549125b5b3c14D999D3a496Afbdb1", - "0x7fC80faD32Ec41fd5CfcC14EeE9C31953b6B4a8B", - "0x5d36a202687fD6Bd0f670545334bF0B4827Cc1E2", - "0xe64113140960528f6AF928d7cA4f45d192286a7a", - "0xf6B6F07862A02C85628B3A9688beae07fEA9C863", - "0xD779aFEE481e3Df5cd0544F0e4353Cf534FD99Db", - "0x183bDB344A07Ee3D27f07AC4799A56E4A2fE5439", - "0xA8cadC2268B01395f8573682fb9DD00Bd582E8A0", - "0x75535661Ab25a468Dfb3137320a7568FeCda4832", - "0xd37ED782323A82e5BD55A92500E48FF5eFcc415E", - "0x03bB5bC3c8fdAB212A6b2B347a049133DfCB3A47", - "0x61987699055394c65355F2797D3e4e589f7FaBf4", - "0x2bC12061C8912505978472C21d4a23dB43AF62aA", - "0xad7575AEFd4d64520c3269FD24eae1b0E13dbE7B", - "0x0D89421D6eec0A4385F95f410732186A2Ab45077", - "0x04c0cD38B8c203b14ef2b7B8d736D69B938AFF71", - "0x0CF30daf2Fb962Ed1d5D19C97F5f6651F3b691c1", - "0x6EEb37b9757DcA963120f61c7E0e0160469A44D3", - "0x616caD18642F45d3fa5FCaaD0a2d81764A9cBa84", - "0xdC1d963D21C9c1bFf7b6Bea6e10080dAa9b4fc51", - "0x8073639B11994C549eDa58fC3cd7132a72aaDF10", - "0xe52C39327FF7576bAEc3DBFeF0787bd62dB6d726", - "0x8f21bD39FcAeA3A729D46339A383081ecB7E84E0", - "0x8Fb7087336678F36E42313f6130567A109a8e73d", - "0x276E69CdD336001afEF07075859A93078496C3c1", - "0x954F716e6de059360d278B773138f8e046696721", - "0x997D410b26CdD17b0750F2c1751e59cBcfaE446f", - "0xE8b6b71f3b1E6d2ad406D2cf36B1f2C567342dF1", - "0x83108A0653a14EAeB8301E7b10a37CfAc39C82f6", - "0xF95D9549b3Ab9470d306a6413Aa45082e8B66043", - "0x82d92494f6fFFB17A1DDFfd9B7d88D1d0a360009", - "0xA19947DA8B916f64Ac6F362cEC9001D8BCBeEe93", - "0x7ef5e4062dcCaD29A6F8d5458590160536056C81", - "0x653d973b36137A5cB2fc304996E0af1F1afCC628", - "0x5F319CA6Ecf072A4d183edAa711Cd04dC225df19", - "0x4D32D90D6535bD4e7eaBaa27EE72932cB214BbfA", - "0x73b9f6a6e52aCE2797F0a6E52AAc530Ed1F2a2Af", - "0xaA3600788b72863ff51C8f0dB5F10bB65fbFeAB4", - "0xf93F0b770784602fC3079eb1D2fB1Ff488Bb02B0", - "0xC8Ddd59c496D04C4C060Ab5038d03d661DDC2617", - "0xc42c77b6B2A2B220b9502F357bBf51334Db3C93f", - "0x2615214F8200B526a7B1eACe03971F2672B48CF2", - "0x9d8d7220D060fd12Ca33336B7239688e366327dE", - "0x9e602c1920443F01Cb100a57A7F894df8Eb42f66", - "0x7e651F5f597436cD0fa941F5FF2cD45Ef3F2Fda8", - "0x8e30Dc2AEF957B1F7dd67B1b7bC651fFe7E17a06", - "0x597dC4159a4b85c086c3C679a0B6c8Fe2836886F", - "0x7fBdE8B27D2B4F164B66F2a9dc02bbD6697e5b19", - "0xf5819cC26F0481c9b86294B4c24027518a04BD5B", - "0x8e7D20638947132B0e6E1aFdE2da1B103aFF9280", - "0xCBA711BEF21496Cfd66323d9AEA8C8EFd0F43e9d", - "0xdfBaeeF21396BF205D4B7D23345155489072Cf9B", - "0x3B981fA5dD50237dAb6F96A417A6690B6f20FcC4", - "0x6C31212a23040998E1D1c157ACe3982aBDBE3154", - "0xCDdF772F8A3295C89DC37510E16e360ee2d29789", - "0x002B5dfB3C71E1dC97A2e5A0A7f69F3e7b83F269", - "0xAD7A185b2456d5AFD85838A50C7d8aCE3aB2f871", - "0x7993F18C91A9f68593d308C5846f380A2a374F46", - "0xc5d82775c9bc5272B1225DB8D62b7034e064BA91", - "0x8bfcF8cb383149D4Ef37e7A609cEc195CDCbE099", - "0xA515F7fB260095eebC860425493b8761B4FC9abd", - "0xaA95cA26c92b0634dF7a1A1504f579F13bFB7f9d", - "0xC2812325caD4C4C782CbbC1164e9373371D31dB2", - "0x4831DdB6502ca45dbEEDf58B47292061Cdb6050B", - "0x6733c60E6E02f9C8FA221Db1aeA018d80D949861", - "0xCaD3887923B39cD2b0B6d13538C4ecB7C5EE9825", - "0x4520cD8BC085B962eF8c0ec696ac9D3Ef1d8bf55", - "0x7D85fCbB505D48E6176483733b62b51704e0bF95", - "0x27259b0F4209e76f8C6Cf27106C9FF83BdC2E831", - "0xE04885c3f1419C6E8495C33bDCf5F8387cd88846", - "0x23ee51e614cBF138e4cAbA9EC5ed4fF7D27A8596", - "0x2cab4d881962D247218356B32aBc4AA5c46bA0d2", - "0x1c0A032954f20761E59138feE236204bECbb8bdb", - "0x701d0ECB3BA780De7b2b36789aEC4493A426010a", - "0x1Ec3C1f70E1D6bBDC84092ae86eAaDE495fdDB9b", - "0xB53b0255895c4F9E3a185E484e5B674bCCfbc076", - "0x770569f85346B971114e11E4Bb5F7aC776673469", - "0x8289432ACD5EB0214B1C2526A5EDB480Aa06A9ab", - "0xdca6F7CB3cF361C8dF8FDE119370F1b21b2fFf63", - "0x117e1EbB7D05545064850513021dF6ADe3C1690B", - "0x7fb43C99a26a9EA8ba841d58390BF1C2996EDFB0", - "0x84B5a60Df2d7e3397B3A4A3c6282f090304Aca26", - "0x72F434Fa010929656AeF58695dab85447E51Fbc6", - "0xA29b0D2F3b4555359A1bF684d700753b1b06cBc4", - "0x4318cC449b1cbE6d64dd82E16abE58C79E076C2B", - "0x8F48282e50B0210bd7c7DD69C54205E98b9Ef5D9", - "0xa305B293e44A82f3Cd489b5fB26084647bb5D8ae", - "0xd9e5De13eF1dBC4DFE0Ee1BB76276228b9B23d0f", - "0x4AcEEB7bF9ec8104CC2379f1E8D648Ee47249FCb", - "0x0743542070891051861f8D0a4550f97B43B0B89a", - "0x58aD805f26272C5Ba06D24Bd0E43c8a2d1c634D9", - "0xE6ED9C681967a4EA7Cef4486942b800139DfB000", - "0x51b9C1Df35B044b5c0099D1fD07EAb7cE38f325d", - "0x55DFFA17578e6bAcE42e4Bf8687A11A85cCfEF97", - "0x1FAE8f99E9F932BdBA910061590C2156eE512A91", - "0xA25207Bb8f8EC2423E2ddf2686A0CD2048352f3E", - "0x746bb7beFD31D9052BB8EbA7D5dD74C9aCf54C6d", - "0x38bc91AA6Aa434c4fae7E666F68C859292deEd95", - "0xA3aD5CFb4FF4B68e37A338Da200BA441C1850B5b", - "0x4bfb2c232F70c83136a3F206cd26Df2A0B605cEC", - "0xf5AB6B4a8d578807491ef59cE855982990932617", - "0x1Fdd220E14b59E26bf1888e8267C4C221983a0A6", - "0xE2D6AFF297b41881c1aEA9599F68AEDFAB38C651", - "0x7d547666209755FB833f9B37EebEa38eBF513Abb", - "0xb681B19bb1F7e9F3C2AE0EDeab368c2afaa4e590", - "0x7Eb84E42059F0D44269C50f4D3A280Fd307a6824", - "0x84f0620A547a4D14A7987770c4F5C25d488d6335", - "0x4Ae6a8A28c87b75e935a90D6128F2649C969c0D8", - "0xb79223E868871DBAc27E8E301f73734abd4Cc628", - "0x6F219Bd1167568aB67494A9067CbbB5679bf0022", - "0x9Ff548c1B3eA3dd123AFE39C759dDA548009B6C8", - "0x3085051F89666E7124e7Ab95b693Fc1E09770907", - "0xa25211B64D041F690C0c818183E32f28ba9647Dd", - "0x6166E1964447E0959bC7c8d543DB3ab82dB65044", - "0x76E059C6FF6bf9FFFD5f33AFdf4AB2FD511C9DF4", - "0x4CC9E6fABb800F083a2685501d1A30CdAbb4B2De", - "0x5f3371793285920351344a1EaaAA48d45e600652", - "0xAFE2b51592b89095A4cFb18da2B5914b528f4c01", - "0xe3F4F3aD70C1190EC480554bbc3Ed30285aE0610", - "0xE0D8926A51F9A1dD8E089D9a3DD88F88fFb2F1Dc", - "0xa6c366D97cb64708211f24310dFAd5363BC96a04", - "0xB7562F12E41C762CeCDA99d62Bd6EAC7b0C3B4c1", - "0x301605C95acbED7A1fD9C2c0DeEe964e2AFBd0C3", - "0x5d47e5D242a8F66a6286b0a2353868875F5d6068", - "0x0ea26051F7657d59418da186137141CeA90D0652", - "0x88f1706c20d94A4d1551C5F799C9E3380A24C3AC", - "0xFB40932271Fc9Db9DbF048E80697E2Da4AA57250", - "0x40Db8365d1252bcb06598927698238a99D39228E", - "0xaCf4C2950107eF9b1C37faA1F9a866C8F0da88b9", - "0x144c4E5027B69f7798B2B162D924BcAE5c149f15", - "0xeeE844540644b204f0005c063Ae95F244BF06a84", - "0x014607F2d6477bADD9d74bF2c5D6356e29a9b957", - "0x1E8eE48D0621289297693fC98914DA2EfDcE1477", - "0x4AdA1B9D9fe28aBd9585f58cfEeD2169A39e1c6b", - "0x31460f49EEA93Ef8255b42be019FB96F89Cf0c49", - "0x63A32F1595a68E811496D820680B74A5ccA303c5", - "0x022ca32d31da3Ef85922AAFD9aD29C5b2418172C", - "0x93B109C3c279bcBbB673Ed1ae1A8BB2dE8eEf9da", - "0x689476323Eb5e9A5DEd342F54B562fc2c156A522", - "0x1C9F765C579F94f6502aCd9fc356171d85a1F8D0", - "0xe0144FA05A0d32B5B1De10CcEe7211616B3E3EF0", - "0x6C965b656C450259a6D4d95A2E68Fb4319EecBc0", - "0xE36BD8C15a83b89E2E49806d7312846069755C63", - "0x59DDA36bD196Ec849838CE2163E6821f946b37Dc", - "0xDd31dB93082a3A71b98D37ba26230f8734Bd63C3", - "0x83c98211C50480e457a0dF930d2A56a891BC4d4b", - "0x11FA934f6754076AEb7Cf0A72a1c2D2518aA4C77", - "0x2B888954421b424C5D3D9Ce9bB67c9bD47537d12", - "0x2383A8b8cC8561a65871F1d2783B7C52e22B62c1", - "0xCED608Aa29bB92185D9b6340Adcbfa263DAe075b", - "0x841AD0AbAb2D33520ca236A2F5D8b038adDc12BA", - "0x76d2DDCe6b781e66c4B184C82Fbf4F94346Cfb0D", - "0xf21e38ac177B48fDE02dB7F2CA97466AE8Eae87D", - "0x7537Cb0AEe6a3483a7601ebf1084eD4df73166Ab", - "0x5f0bD06A71E038206ef3e5090eB448E9a9773772", - "0x3C0c7B44c1F9366271F5c491121a1F7d55d33eF5", - "0xa96a437eFb71bAF50A59027C340FA3362ef703F7", - "0x55bA9c90c37e3206570AC9dc872c0f053d155F77", - "0xC68bba423525576C7684e7ea25E7D5F079b1361E", - "0x78E87757861185Ec5e8C0EF6BF0C69Fa7832df6C", - "0xCb36F8580A36788A48518dEC95Ea458357E64E30", - "0x25854e2a49A6CDAeC7f0505b4179834509038549", - "0x639749b7b08aEe65039c21d8a411103C6ceBEBF0", - "0xF517529866d371F04780885923F739bc17694BFb", - "0xC728DEa8B2972E6e07493BE8DC2F0314F7dC3E98", - "0x33f6EE932cEa603Fafd6854827259bE172C91Da4", - "0x6D97d65aDfF6771b31671443a6b9512104312d3D", - "0xB7BaBe35CE543e2Cf2F615CB1c792a2025feb572", - "0x4D9e86a5AC368Aa4Df0473eF07e13Ec2Fbe04025", - "0xaa79B87DC8B046A5E4f7D03F1562D7fe5BF98737", - "0xE71FbB197BC8fD11090FA657C100d52Dbb407662", - "0xB22981bA3FE1De2325935c91a3B717168fB86714", - "0xf389dD1F828525b449D63D14157f2d3A25eE0a41", - "0x877B37D3E5467B4aAE7687Dd3480A46C8D3e16Be", - "0xf9e1D1e9F22c96752356AdFd377231528c7E851E", - "0x187089B33E5812310Ed32A57F53B3fAD0383a19D", - "0xF1659A2FD5007192314F9676e6a4a39FD1202160", - "0xFdd210ce1b829E837D9e034DAE0F0312F176cef6", - "0xaCE1f1c6c5c89AE3Fc3209ff92e7120fb74445aA", - "0x6Ceb397b68059Ca73049874D0a30c62500aE9877", - "0xC46c67Bb7E84490D7EbdD0b8ecDaca68Cf3823F4", - "0xbb2eb4c7eB36ECce7A3E6bc501590CE12c9c1050", - "0x9Cf251A782cE7310D5bec0fe0a1C2B826d962545", - "0x43930Ff04D18a5B59805151c1Eb403C55870641E", - "0xA270f5649A42feDfE66ddb3b0b50bebAe1e3DDB0", - "0xd3488EA0c1DC99a5d72F75c84004224f8b58694E", - "0x7aBa691D12D8eF8793F1643eBa66b69C70EC72f1", - "0x8558f502887a9a52c4B265d72327E0E529Ff790d", - "0xA906c85B7e809b79c5e69d485693B44d65B1B252", - "0x3abdC9ed5f5dE6A74CFeb42a82087C853E160E76", - "0x30C7F4F7504D6366916f669cd8E731ED4dF6C702", - "0xed8DB37778804A913670d9367aAf4F043AAd938b", - "0xc191a29203a83eec8e846c26340f828C68835715", - "0xa32aECda752cF4EF89956e83d60C04835d4FA867", - "0x059F7da59Ad1EB412B4d2fFc12E9B50Da91cFdb6", - "0x85BEad65c61dB8cF230b3ec30552B8b3E6388570", - "0xF3Ad97364bcCC3eA0582Ede58C363888f8C4ec85", - "0x3F87755E2974534888Ddb20A52dCE45Ef9f204AB", - "0x757CC91CcBB88cB0d78d6798D20051d39E5A7296", - "0xF553C8223cA8542Af9Db7b916Fe9dc7c28b73751", - "0x40f9bf922c23c43acdad71Ab4425280C0ffBD697", - "0x9600e2eE6377DAdad7299B120026661c336A5e6d", - "0x516fCA170bfE24BFC54e01F215EF85Fe9B5B798A", - "0x61C820e261717A5A0555488872F78ac7b9CE77Ec", - "0xEb263241eB948Cc0eB53A58bf743289D074F474F", - "0x841C11b14c428dd591093348B8Afa2652C863988", - "0x3c114973c0260290C2dbD40323327d996972FCeB", - "0x765a16ca391A6b9249cfA65bf2D14C38722198e3", - "0xC3268DDB8E38302763fFdC9191FCEbD4C948fe1b", - "0x6B92686c40747C85809a6772D0eda8e22a77C60c", - "0xc799bE8De03F20B2D3b101E6F6516D614e6fFe06", - "0x40Dc654af5cE40C122ffDC679fa8E8ca8b91556A", - "0xCE8D52c38d74B77a0aA361c48Fdce6b220A3370e", - "0xEfa4c696Ea2505ec038c9dDC849b1bf817d7f69d", - "0xf7253A0E87E39d2cD6365919D4a3D56D431D0041", - "0xcf79C7EaEC5BDC1A9e32D099C5D6BdF67E4cF6e8", - "0xff75E131c711e4310C045317779d39B3B4f718C4", - "0xdE2BE7C9C542c55a7a77489A3A7745493988947F", - "0xFeB3E0f50107f6cfB2EC8C2bC8287f2707E0E2Ea", - "0x6b759Bf480407D19c8903c16023c706868c29a2A", - "0x6E38911dA6Dd0379F1CaC396F74387c95A1f0D21", - "0x5a5D9aB7b1bD978F80909503EBb828879daCa9C3", - "0xe96056A9936C58e89D1703cF6bD97F134341EE44", - "0x4dD6720D2Bb8721A46bdF9a528704164578E03B9", - "0xE83B9A1B9056B21a01b85162E77AD76a42A1c64B", - "0xbeC48f1cCf82d8e4C983Ee00Ac2eC6B03B81d710", - "0xEFEdaf9c07E6eB56BB8F82f30018e4461B1c5F4c", - "0xB68da7fbF71383Afab240839287878539cFFf20b", - "0xfBDDB719cC7c795a1D9b7EA7aC11494A19b3231F", - "0x07506a5F48D71fDB34D3900fB086D43EF1B58FF9", - "0xa85cdd5478B7E525a808eF9707c3e33432cE1e7F", - "0xCf7C21DeD40f2Df85A564207A89b3379780d9CE3", - "0xd26b76e50f6510cdD4bf45d59279705f36946d23", - "0xdb7a41e39807E8C988859f150296Db92674b7dc7", - "0x719028736f10164c838Ef129936779eD739312f2", - "0xaBCdef0AbbA5D0106595174213156797bc0DB33E", - "0x3D2b8879f97e413b2609F9844A5fc8dB8FE4f6F8", - "0x81EbE8Ee7b51741fD5DaD31F6987E626A9bb8111", - "0x1D45c8fa65F6b18E7dAe04b2efEa332c55696DaA", - "0x978eB534b26CB8749D352a2C94EC21e659e4248d", - "0xa7CA400d49BBa87EB606ee05af93689BD21FaB99", - "0x65ad2BF7E09af2597C140dF6386a3003d0F5f8Ee", - "0x835918a3fBDf946364a9aee3114173865b712663", - "0x6073cFfc1D46b1eA57BA89A28074cA734aCD7003", - "0x2B13D52dFd33E2eBd13232866fDf96088e77d596", - "0x55F5601357f6e0B10a3386914c93916c6C9A368A", - "0xA1D5D2d931b532A0503e97f540f65ed256f374e6", - "0x6C9258895FFBE2178b3EdEfE09AF304a1e99bF2F", - "0x973375b099943cDdFd390022CeA90D4F1d0c493c", - "0x8A8C879D39A74fCE0593714956bB7Ed048A5c1BF", - "0x9c42B0c70D0dAF1211f3aab2A1E6EC5E717dE12a", - "0x81a6383041593c556d1c8e69e2749b35b5008F09", - "0xF41b98a4C32bB61468C8001E0C69Ef64ce6DEa57", - "0x8FaE81bb674c89cCDE35a386587333D074b57786", - "0xa8258ED271BB9be9d7E16c5818E45eF6F2577d92", - "0x1e90474D2E83e7B7dD45553156Beb316845E66A4", - "0x2cCbbC4c10F5d807FDd447219B57D0b883a28DC8", - "0x1bBeAc736875c5043486A8a4374E6B5616eC8883", - "0x95add3DfEF3AE0A832607Dc71C4A9C6A6C2D7Eb7", - "0x744c6Eed427aF293b0106B46700fdDD3C9f62Eef", - "0x743Ec55fc166D24D2FD0211fb6Ce53926D0Ff3b1", - "0xDd03d2434C02c6BfFb097b7130528F9568b6C70d", - "0x97C12EFA574923E3ee445370d2dE432332857110", - "0xB69951a0642b55CD5731535ed5B290Fa49D3454A", - "0xBA56878729540404dE2aa14561b451aE2350744a", - "0x8D247f4Fbbe81429d3D164a5c9Ae0063210edBdC", - "0x850a146D7478dAAa98Fc26Fd85e6A24e50846A9d", - "0xfE1552DA65FAcAaC5B50b73CEDa4C993e16d4694", - "0x9705FE3586a7D768Fee061aAfE9384b1D4B8F2D8", - "0x5554672e67bA866B9861701D0e0494AB324aD19A", - "0xacc5c1e73d70F7F9622De2d574885Ce8E6981033", - "0xbe9E7b0ed19526544B55b697107231f9467a805f", - "0x172DBab6f5E62A1FE7E2bA5eA1624ADB33e0aa14", - "0x96725Fa2F9A0b5bAf80fC36C20C2cA79d86424ed", - "0xa392cCadABFf735dbFF69dC93d7C13f34A30611b", - "0xEbF0e04E47F726D0f44801dFEC5e705aDcd6694b", - "0xC0891e8FCeA09680BFe9170809fad1BCCa10b96b", - "0xA21000E7A5A2A2Bd9329428A859f9d7dcE0f0961", - "0x9A387307F7508DE113092BaFC5CB4B3AE0706521", - "0xBA719E0197470A790726075fD98EDEF04E2467af", - "0xda08BE028304db1A73a13Bce7C943127C2E393dB", - "0xfB4a965A35603010FeAcC648cA022Cb6A12D33F5", - "0x3Aa73ed90e9f0CEd87ff99CB60cA79019279e6CE", - "0x150bB505A9259b0be44FFb15415C79199E83c445", - "0xB170A41F2523220A12F84f17A54bD31953D98027", - "0x2Fcd65d9c8078644adCf1CB0cd70A1b61F3F9C5b", - "0x73006C818880d07dD510e165C3De3E74F2407187", - "0x747e6ABc102222f1dF65C662540dDf471241a644", - "0xeeEe5D271A56Aa09C4F8862aF514ADD3E882857c", - "0x98Ad82AB467bc8c70e0CC183a5826d903751b7d8", - "0xC624434420f6CbE835D6358A8223b78432773cEd", - "0x848e313d4b25bC0B48CaFdB6A72391E892E6A247", - "0x0025Ab2d69F6c2C3Ffac32Ab6A16e18c807518B8", - "0x2efe744ecc4F6BD55538da57D09DAE895C95b223", - "0xBc6d82D8d6632938394905Bb0217Ad9c673015d1", - "0xe1555c6EE61366a3f90135Dc704Acd25C3247ACA", - "0x2f51E78ff8aeC6A941C4CEeeb26B4A1f03737c50", - ] -) diff --git a/backend/app/modules/user/antisybil/service/initial.py b/backend/app/modules/user/antisybil/service/initial.py index a9ed1f5a5a..7fc1f39164 100644 --- a/backend/app/modules/user/antisybil/service/initial.py +++ b/backend/app/modules/user/antisybil/service/initial.py @@ -1,32 +1,33 @@ -from flask import current_app as app - -from eth_utils.address import to_checksum_address - -from typing import Dict, Optional, Tuple - -import json from datetime import datetime +from http import HTTPStatus from typing import List +from typing import Optional, Tuple + +from eth_utils.address import to_checksum_address +from flask import current_app as app -from app.constants import GUEST_LIST, GUEST_LIST_STAMP_PROVIDERS -from app.extensions import db -from app.exceptions import ExternalApiException, UserNotFound, AddressAlreadyDelegated from app.context.manager import Context +from app.exceptions import ExternalApiException, UserNotFound, AddressAlreadyDelegated +from app.extensions import db from app.infrastructure import database -from app.modules.common.delegation import get_hashed_addresses -from app.pydantic import Model from app.infrastructure.external_api.common import retry_request from app.infrastructure.external_api.gc_passport.score import ( issue_address_for_scoring, fetch_score, fetch_stamps, ) +from app.modules.common.delegation import get_hashed_addresses +from app.modules.user.antisybil.core import determine_antisybil_score +from app.modules.user.antisybil.dto import AntisybilStatusDTO +from app.pydantic import Model class GitcoinPassportAntisybil(Model): + timeout_list: set + def get_antisybil_status( self, _: Context, user_address: str - ) -> Optional[Tuple[float, datetime]]: + ) -> Optional[AntisybilStatusDTO]: user_address = to_checksum_address(user_address) try: score = database.user_antisybil.get_score_by_address(user_address) @@ -35,11 +36,8 @@ def get_antisybil_status( f"User {user_address} antisybil status: except UserNotFound" ) raise ex - if score is not None: - if user_address in GUEST_LIST and not _has_guest_stamp_applied_by_gp(score): - score.score = score.score + 21.0 - return score.score, score.expires_at - return None + + return determine_antisybil_score(score, user_address, self.timeout_list) def fetch_antisybil_status( self, _: Context, user_address: str @@ -52,7 +50,7 @@ def _retry_fetch(): raise ExternalApiException("GP: scoring is not completed yet", 503) if score["status"] != "DONE": - score = retry_request(_retry_fetch, 200) + score = retry_request(_retry_fetch, HTTPStatus.OK) all_stamps = fetch_stamps(user_address)["items"] cutoff = datetime.now() @@ -83,19 +81,6 @@ def _verify_address_is_not_delegated(self, user_address: str): raise AddressAlreadyDelegated() -def _has_guest_stamp_applied_by_gp(score: Dict) -> bool: - def get_provider(stamp) -> str: - return stamp["credential"]["credentialSubject"]["provider"] - - all_stamps = json.loads(score.stamps) - stamps = [ - stamp - for stamp in all_stamps - if get_provider(stamp) in GUEST_LIST_STAMP_PROVIDERS - ] - return len(stamps) > 0 - - def _parse_expiration_date(timestamp_str: str) -> datetime: gp_api_formats = ["%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ"] for format_str in gp_api_formats: diff --git a/backend/app/settings.py b/backend/app/settings.py index 58988673a5..75c37cb388 100644 --- a/backend/app/settings.py +++ b/backend/app/settings.py @@ -28,7 +28,6 @@ class Config(object): GC_PASSPORT_SCORER_ID = os.getenv("GC_PASSPORT_SCORER_ID") GC_PASSPORT_SCORER_API_KEY = os.getenv("GC_PASSPORT_SCORER_API_KEY") SCHEDULER_ENABLED = parse_bool(os.getenv("SCHEDULER_ENABLED")) - CACHE_TYPE = "SimpleCache" DELEGATION_SALT = os.getenv("DELEGATION_SALT") DELEGATION_SALT_PRIMARY = os.getenv("DELEGATION_SALT_PRIMARY") @@ -93,6 +92,11 @@ class ProdConfig(Config): "pool_pre_ping": True, } X_REAL_IP_REQUIRED = parse_bool(os.getenv("X_REAL_IP_REQUIRED", "true")) + CACHE_TYPE = "RedisCache" + CACHE_REDIS_HOST = os.getenv("CACHE_REDIS_HOST") + CACHE_REDIS_PORT = os.getenv("CACHE_REDIS_PORT") + CACHE_REDIS_PASSWORD = os.getenv("CACHE_REDIS_PASSWORD") + CACHE_REDIS_DB = os.getenv("CACHE_REDIS_DB") class DevConfig(Config): @@ -108,6 +112,7 @@ class DevConfig(Config): SQLALCHEMY_DATABASE_URI = f"sqlite:///{DB_PATH}" SUBGRAPH_RETRY_TIMEOUT_SEC = 2 X_REAL_IP_REQUIRED = parse_bool(os.getenv("X_REAL_IP_REQUIRED", "false")) + CACHE_TYPE = "SimpleCache" class ComposeConfig(Config): @@ -117,6 +122,7 @@ class ComposeConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = os.getenv("DB_URI") X_REAL_IP_REQUIRED = parse_bool(os.getenv("X_REAL_IP_REQUIRED", "false")) + CACHE_TYPE = "SimpleCache" class TestConfig(Config): @@ -141,6 +147,7 @@ class TestConfig(Config): DELEGATION_SALT = "salt" DELEGATION_SALT_PRIMARY = "salt_primary" SUBGRAPH_RETRY_TIMEOUT_SEC = 2 + CACHE_TYPE = "SimpleCache" def get_config(): diff --git a/backend/migrations/__init__.py b/backend/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/migrations/ipfs_integration/config.py b/backend/migrations/ipfs_integration/config.py new file mode 100644 index 0000000000..c9cc5b4669 --- /dev/null +++ b/backend/migrations/ipfs_integration/config.py @@ -0,0 +1,17 @@ +from enum import StrEnum + +from core import build_filename + + +class Config: + FILENAME_PREFIX = "ipfs_projects_details" + EPOCH = 4 # change corresponding to the epoch + JSON_FILEPATH = f"files/{build_filename(FILENAME_PREFIX, EPOCH)}" + CID = [ + "QmXomSdCCwt4FtBp3pidqSz3PtaiV2EyQikU6zRGWeCAsf" + ] # change corresponding to the epoch + GATEWAY_URL = "https://octant.infura-ipfs.io/ipfs/" + + +class ProjectDetails(StrEnum): + NAME = "name" diff --git a/backend/migrations/ipfs_integration/core.py b/backend/migrations/ipfs_integration/core.py new file mode 100644 index 0000000000..e1895f10bb --- /dev/null +++ b/backend/migrations/ipfs_integration/core.py @@ -0,0 +1,19 @@ +import re + + +def build_filename(prefix: str, epoch: int) -> str: + json_filename = f"{prefix}_epoch_{epoch}.json" + + return json_filename + + +def is_valid_ethereum_address(address: str) -> bool: + """ + Validates if the provided string is a valid Ethereum address. + + :param address: The address string to validate. + :return: True if valid, False otherwise. + """ + # Ethereum addresses are 42 characters long (including '0x') and hexadecimal + pattern = re.compile(r"^0x[a-fA-F0-9]{40}$") + return bool(pattern.match(address)) diff --git a/backend/migrations/ipfs_integration/files/cids.md b/backend/migrations/ipfs_integration/files/cids.md new file mode 100644 index 0000000000..cc3d54ad43 --- /dev/null +++ b/backend/migrations/ipfs_integration/files/cids.md @@ -0,0 +1,5 @@ +# CIDs in history +- Epoch1: QmSQEFD35gKxdPEmngNt1CWe3kSwiiGqBn1Z3FZvWb8mvK +- Epoch2: Qmds9N5y2vkMuPTD6M4EBxNXnf3bjTDmzWBGnCkQGsMMGe +- Epoch3: QmSXcT18anMXKACTueom8GXw8zrxTBbHGB71atitf6gZ9V +- Epoch4: QmXomSdCCwt4FtBp3pidqSz3PtaiV2EyQikU6zRGWeCAsf diff --git a/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_1.json b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_1.json new file mode 100644 index 0000000000..0fb5c23963 --- /dev/null +++ b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_1.json @@ -0,0 +1,100 @@ +[ + [ + { + "name": "Ethereum Cat Herders", + "address": "0x02Cb3C150BEdca124d0aE8CcCb72fefbe705c953" + }, + { + "name": "Praise", + "address": "0x0B7246eF74Ca7b37Fdc3D15be4f0b49876622F95" + }, + { + "name": "RADAR Launch", + "address": "0x149D46eC060e75AE188876AdB6b24024637003C7" + }, + { + "name": "Tor Project", + "address": "0x15c941a44a343B8c46a28F2BB9aFc7a54E255A4f" + }, + { + "name": "DAO Drops", + "address": "0x1c01595f9534E33d411035AE99a4317faeC4f6Fe" + }, + { + "name": "OnionDAO", + "address": "0x20a1B17087482de88Fac6D7B5aE23A7175fd1395" + }, + { + "name": "Hypercerts", + "address": "0x2DCDF80f439843D7E0aD1fEF9E7a439B7917eAc9" + }, + { + "name": "MetaGame", + "address": "0x3455FbB4D34C6b47999B66c83aA7BD8FDDade638" + }, + { + "name": "PublicHAUS", + "address": "0x4A9a27d614a74Ee5524909cA27bdBcBB7eD3b315" + }, + { + "name": "Unitap", + "address": "0x4ADc8CC149A03F44386Bee80bab36F9e8022b195" + }, + { + "name": "Giveth", + "address": "0x6e8873085530406995170Da467010565968C7C62" + }, + { + "name": "BrightID", + "address": "0x78e084445C3F1006617e1f36794dd2261ecE4AE3" + }, + { + "name": "Kernel", + "address": "0x7DAC9Fc15C1Db4379D75A6E3f330aE849dFfcE18" + }, + { + "name": "Glo Dollar", + "address": "0x8c89a6bf53cCF63e7B4465Cc1b1330723B4BdcB7" + }, + { + "name": "Rotki", + "address": "0x9531C059098e3d194fF87FebB587aB07B30B1306" + }, + { + "name": "Clr.fund", + "address": "0xAb6D6a37c5110d1377832c451C33e4fA16A9BA05" + }, + { + "name": "BanklessDAO", + "address": "0xCf3efCE169acEC1B281C05E863F78acCF62BD944" + }, + { + "name": "EthStaker", + "address": "0xD165df4296C85e780509fa1eace0150d945d49Fd" + }, + { + "name": "GoodDollar", + "address": "0xF0652a820dd39EC956659E0018Da022132f2f40a" + }, + { + "name": "Protocol Guild", + "address": "0xF6CBDd6Ea6EC3C4359e33de0Ac823701Cc56C6c4" + }, + { + "name": "Drips", + "address": "0xcC7d34C76A9d08aa0109F7Bae35f29C1CE35355A" + }, + { + "name": "Pairwise", + "address": "0xd1B8dB70Ded72dB850713b2ce7e1A4FfAfAD95d1" + }, + { + "name": "Gitcoin", + "address": "0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6" + }, + { + "name": "Gravity DAO", + "address": "0xfFbD35255008F86322051F2313D4b343540e0e00" + } + ] +] \ No newline at end of file diff --git a/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_2.json b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_2.json new file mode 100644 index 0000000000..8b373b2a63 --- /dev/null +++ b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_2.json @@ -0,0 +1,100 @@ +[ + [ + { + "name": "Ethereum Cat Herders", + "address": "0x02Cb3C150BEdca124d0aE8CcCb72fefbe705c953" + }, + { + "name": "Praise", + "address": "0x0B7246eF74Ca7b37Fdc3D15be4f0b49876622F95" + }, + { + "name": "L2BEAT", + "address": "0x0cbF31Ef6545EE30f47651D1A991Bf0aeB03DF29" + }, + { + "name": "Tor Project", + "address": "0x15c941a44a343B8c46a28F2BB9aFc7a54E255A4f" + }, + { + "name": "DAO Drops", + "address": "0x1c01595f9534E33d411035AE99a4317faeC4f6Fe" + }, + { + "name": "Hypercerts", + "address": "0x2DCDF80f439843D7E0aD1fEF9E7a439B7917eAc9" + }, + { + "name": "MetaGame", + "address": "0x3455FbB4D34C6b47999B66c83aA7BD8FDDade638" + }, + { + "name": "PublicHAUS", + "address": "0x4A9a27d614a74Ee5524909cA27bdBcBB7eD3b315" + }, + { + "name": "Funding the Commons", + "address": "0x576edCed7475D8F64a5e2D5227c93Ca57d7f5d20" + }, + { + "name": "Giveth", + "address": "0x6e8873085530406995170Da467010565968C7C62" + }, + { + "name": "BrightID", + "address": "0x78e084445C3F1006617e1f36794dd2261ecE4AE3" + }, + { + "name": "Kernel", + "address": "0x7DAC9Fc15C1Db4379D75A6E3f330aE849dFfcE18" + }, + { + "name": "Open Source Observer", + "address": "0x87fEEd6162CB7dFe6B62F64366742349bF4D1B05" + }, + { + "name": "Glo Dollar", + "address": "0x8c89a6bf53cCF63e7B4465Cc1b1330723B4BdcB7" + }, + { + "name": "Rotki", + "address": "0x9531C059098e3d194fF87FebB587aB07B30B1306" + }, + { + "name": "Clr.fund", + "address": "0xAb6D6a37c5110d1377832c451C33e4fA16A9BA05" + }, + { + "name": "Shielded Voting", + "address": "0xB476Ee7D610DAe7B23B671EBC7Bd6112E9772969" + }, + { + "name": "EthStaker", + "address": "0xD165df4296C85e780509fa1eace0150d945d49Fd" + }, + { + "name": "Token Engineering Commons", + "address": "0xE2f413190Bb5D6AAcB4A056F1B5E1fD5B8141045" + }, + { + "name": "Protocol Guild", + "address": "0xF6CBDd6Ea6EC3C4359e33de0Ac823701Cc56C6c4" + }, + { + "name": "Drips", + "address": "0xcC7d34C76A9d08aa0109F7Bae35f29C1CE35355A" + }, + { + "name": "Pairwise", + "address": "0xd1B8dB70Ded72dB850713b2ce7e1A4FfAfAD95d1" + }, + { + "name": "Gitcoin", + "address": "0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6" + }, + { + "name": "Revoke.cash", + "address": "0xe126b3E5d052f1F575828f61fEBA4f4f2603652a" + } + ] +] \ No newline at end of file diff --git a/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_3.json b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_3.json new file mode 100644 index 0000000000..3c3ff16819 --- /dev/null +++ b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_3.json @@ -0,0 +1,124 @@ +[ + [ + { + "name": "StateOfEth", + "address": "0x0194325BF525Be0D4fBB0856894cEd74Da3B8356" + }, + { + "name": "Ethereum Cat Herders", + "address": "0x02Cb3C150BEdca124d0aE8CcCb72fefbe705c953" + }, + { + "name": "Praise", + "address": "0x0B7246eF74Ca7b37Fdc3D15be4f0b49876622F95" + }, + { + "name": "L2BEAT", + "address": "0x0cbF31Ef6545EE30f47651D1A991Bf0aeB03DF29" + }, + { + "name": "Tor Project", + "address": "0x15c941a44a343B8c46a28F2BB9aFc7a54E255A4f" + }, + { + "name": "DAO Drops", + "address": "0x1c01595f9534E33d411035AE99a4317faeC4f6Fe" + }, + { + "name": "Hypercerts", + "address": "0x2DCDF80f439843D7E0aD1fEF9E7a439B7917eAc9" + }, + { + "name": "MetaGame", + "address": "0x3455FbB4D34C6b47999B66c83aA7BD8FDDade638" + }, + { + "name": "Web3.js", + "address": "0x4C6fd545fc18C6538eC304Ae549717CA58f0D6eb" + }, + { + "name": "Boring Security", + "address": "0x52C45Bab6d0827F44a973899666D9Cd18Fd90bCF" + }, + { + "name": "Web3.py", + "address": "0x5597cD8d55D2Db56b10FF4F8fe69C8922BF6C537" + }, + { + "name": "Funding the Commons", + "address": "0x576edCed7475D8F64a5e2D5227c93Ca57d7f5d20" + }, + { + "name": "Giveth", + "address": "0x6e8873085530406995170Da467010565968C7C62" + }, + { + "name": "ReFi DAO", + "address": "0x7340F1a1e4e38F43d2FCC85cdb2b764de36B40c0" + }, + { + "name": "Gardens", + "address": "0x809C9f8dd8CA93A41c3adca4972Fa234C28F7714" + }, + { + "name": "Open Source Observer", + "address": "0x87fEEd6162CB7dFe6B62F64366742349bF4D1B05" + }, + { + "name": "Glo Dollar", + "address": "0x8c89a6bf53cCF63e7B4465Cc1b1330723B4BdcB7" + }, + { + "name": "GrowThePie", + "address": "0x9438b8B447179740cD97869997a2FCc9b4AA63a2" + }, + { + "name": "Rotki", + "address": "0x9531C059098e3d194fF87FebB587aB07B30B1306" + }, + { + "name": "MetaGov", + "address": "0x9be7267002CAD0b8501f7322d50612CB13788Bcf" + }, + { + "name": "NiceNode", + "address": "0x9cce47E9cF12C6147c9844adBB81fE85880c4df4" + }, + { + "name": "Shielded Voting", + "address": "0xB476Ee7D610DAe7B23B671EBC7Bd6112E9772969" + }, + { + "name": "Ethereum Attestation Service", + "address": "0xBCA48834b3653ec795411EB0FCBE4038F8527d62" + }, + { + "name": "ETH Daily", + "address": "0xEB40A065854bd90126A4E697aeA0976BA51b2eE7" + }, + { + "name": "EthStaker", + "address": "0xF01CEe26213d1A6eaF16422241AE81f7C17B9f98" + }, + { + "name": "Protocol Guild", + "address": "0xF6CBDd6Ea6EC3C4359e33de0Ac823701Cc56C6c4" + }, + { + "name": "Drips", + "address": "0xcC7d34C76A9d08aa0109F7Bae35f29C1CE35355A" + }, + { + "name": "Pairwise", + "address": "0xd1B8dB70Ded72dB850713b2ce7e1A4FfAfAD95d1" + }, + { + "name": "Gitcoin", + "address": "0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6" + }, + { + "name": "Revoke.cash", + "address": "0xe126b3E5d052f1F575828f61fEBA4f4f2603652a" + } + ] +] \ No newline at end of file diff --git a/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_4.json b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_4.json new file mode 100644 index 0000000000..d28f6e8686 --- /dev/null +++ b/backend/migrations/ipfs_integration/files/ipfs_projects_details_epoch_4.json @@ -0,0 +1,124 @@ +[ + [ + { + "name": "BuidlGuidl", + "address": "0x00080706a7D99CBC163D52dcF435205B1aD940D1" + }, + { + "name": "Ethereum Cat Herders", + "address": "0x02Cb3C150BEdca124d0aE8CcCb72fefbe705c953" + }, + { + "name": "Praise", + "address": "0x0B7246eF74Ca7b37Fdc3D15be4f0b49876622F95" + }, + { + "name": "Vyper", + "address": "0x0c9dc7622aE5f56491aB4cCe060d6002450B79D2" + }, + { + "name": "L2BEAT", + "address": "0x0cbF31Ef6545EE30f47651D1A991Bf0aeB03DF29" + }, + { + "name": "Tor Project", + "address": "0x15c941a44a343B8c46a28F2BB9aFc7a54E255A4f" + }, + { + "name": "DAO Drops", + "address": "0x1c01595f9534E33d411035AE99a4317faeC4f6Fe" + }, + { + "name": "Blockscout", + "address": "0x242ba6d68FfEb4a098B591B32d370F973FF882B7" + }, + { + "name": "Hypercerts", + "address": "0x2DCDF80f439843D7E0aD1fEF9E7a439B7917eAc9" + }, + { + "name": "Protocol Guild", + "address": "0x3250c2CEE20FA34D1c4F68eAA87E53512e95A62a" + }, + { + "name": "Web3.js", + "address": "0x4C6fd545fc18C6538eC304Ae549717CA58f0D6eb" + }, + { + "name": "Dappnode", + "address": "0x53390590476dC98860316e4B46Bb9842AF55efc4" + }, + { + "name": "Web3.py", + "address": "0x5597cD8d55D2Db56b10FF4F8fe69C8922BF6C537" + }, + { + "name": "Funding the Commons", + "address": "0x576edCed7475D8F64a5e2D5227c93Ca57d7f5d20" + }, + { + "name": "TogetherCrew", + "address": "0x6612213880f80b298aB66375789E8Ef15e98604E" + }, + { + "name": "EcosynthesisX", + "address": "0x7380A42137D16a0E7684578d8b3d32e1fbD021B5" + }, + { + "name": "DeSci LATAM", + "address": "0x7Dd488f03E0A043b550E82D3C2685aA83B96407C" + }, + { + "name": "Gardens", + "address": "0x809C9f8dd8CA93A41c3adca4972Fa234C28F7714" + }, + { + "name": "Open Source Observer", + "address": "0x87fEEd6162CB7dFe6B62F64366742349bF4D1B05" + }, + { + "name": "growthepie", + "address": "0x9438b8B447179740cD97869997a2FCc9b4AA63a2" + }, + { + "name": "Rotki", + "address": "0x9531C059098e3d194fF87FebB587aB07B30B1306" + }, + { + "name": "NiceNode", + "address": "0x9cce47E9cF12C6147c9844adBB81fE85880c4df4" + }, + { + "name": "Shielded Voting", + "address": "0xB476Ee7D610DAe7B23B671EBC7Bd6112E9772969" + }, + { + "name": "Ethereum Attestation Service", + "address": "0xBCA48834b3653ec795411EB0FCBE4038F8527d62" + }, + { + "name": "EthStaker", + "address": "0xF01CEe26213d1A6eaF16422241AE81f7C17B9f98" + }, + { + "name": "PizzaDAO", + "address": "0xF41a98D4F2E52aa1ccB48F0b6539e955707b8F7a" + }, + { + "name": "Abundance Protocol", + "address": "0xc6FD734790E83820e311211B6d9A682BCa4ac97b" + }, + { + "name": "Pairwise", + "address": "0xd1B8dB70Ded72dB850713b2ce7e1A4FfAfAD95d1" + }, + { + "name": "Revoke.cash", + "address": "0xe126b3E5d052f1F575828f61fEBA4f4f2603652a" + }, + { + "name": "Trustful", + "address": "0xf7253A0E87E39d2cD6365919D4a3D56D431D0041" + } + ] +] diff --git a/backend/migrations/ipfs_integration/logic.py b/backend/migrations/ipfs_integration/logic.py new file mode 100644 index 0000000000..af677bd9af --- /dev/null +++ b/backend/migrations/ipfs_integration/logic.py @@ -0,0 +1,127 @@ +import json +import os +from typing import List, Optional, Dict + +import requests +from bs4 import BeautifulSoup + +from config import Config, ProjectDetails +from core import is_valid_ethereum_address + + +def get_addresses_from_cid(cid: str) -> List[str]: + """ + Retrieves a list of Ethereum addresses from the CID by parsing the HTML content. + + :param cid: The CID to fetch. + :param gateway_url: The base URL of the IPFS gateway. + :return: A list of Ethereum addresses found under the CID. + """ + gateway_url = Config.GATEWAY_URL + url = f"{gateway_url}{cid}/" # Ensure the trailing slash + try: + response = requests.get(url) + response.raise_for_status() + html_content = response.text + + soup = BeautifulSoup(html_content, "html.parser") + addresses = [] + for link in soup.find_all("a"): + href = link.get("href") + if href and href not in ("../", "?", ""): + address = os.path.basename(href) + address = address.strip("/") + + if is_valid_ethereum_address(address): + addresses.append(address) + return addresses + except requests.exceptions.RequestException as e: + print(f"Error fetching CID {cid}: {e}") + return [] + + +def get_json_data_from_address( + cid: str, address: str, gateway_url: str = Config.GATEWAY_URL +) -> Optional[str]: + """ + Fetches the JSON data from the given Ethereum address under the CID. + + :param cid: The CID under which the address resides. + :param address: The Ethereum address (file name) to fetch. + :param gateway_url: The base URL of the IPFS gateway. + :return: A dictionary containing the JSON data, or None if an error occurs. + """ + url = f"{gateway_url}{cid}/{address}" + try: + response = requests.get(url) + response.raise_for_status() + content_type = response.headers.get("Content-Type", "") + + if "application/json" in content_type: + json_data = response.json() + else: + json_data = json.loads(response.text) + return json_data + except requests.exceptions.RequestException as e: + print(f"Error fetching address {address} under CID {cid}: {e}") + return None + except json.JSONDecodeError as e: + print(f"Error decoding JSON from address {address} under CID {cid}: {e}") + return None + + +def extract_details_from_json( + json_data: Dict, *, details_to_extract: List[ProjectDetails] +) -> Dict: + """ + Extracts the 'name' field from the JSON data. + + :param json_data: The JSON data dictionary. + :return: The 'name' value, or None if not found. + """ + output = {} + for detail_to_extract in details_to_extract: + detail_to_extract = detail_to_extract.value + output[detail_to_extract] = json_data.get(detail_to_extract) + return output + + +def main(): + all_projects_details = [] + + for cid in Config.CID: + print(f"\nProcessing CID: {cid}") + addresses = get_addresses_from_cid(cid) + if not addresses: + print(f"No Ethereum addresses found under CID {cid}.") + continue + + projects_details = [] + for address in addresses: + json_data = get_json_data_from_address(cid, address) + + if not json_data: + print(f"Failed to retrieve JSON data for address: {address}") + continue + + project_details = extract_details_from_json( + json_data, details_to_extract=[ProjectDetails.NAME] + ) + project_details["address"] = address + + name = project_details.get(ProjectDetails.NAME.value) + projects_details.append(project_details) + + print(f"Project name for address {address}: {name}") + + print(f"Number of projects for {cid}", len(projects_details)) + all_projects_details.append(projects_details) + + print(f"All projects details that are saved in a JSON file: {all_projects_details}") + + with open(Config.JSON_FILEPATH, "w") as f: + json.dump(all_projects_details, f, indent=4) + + +if __name__ == "__main__": + main() diff --git a/backend/migrations/ipfs_integration/migration_helpers.py b/backend/migrations/ipfs_integration/migration_helpers.py new file mode 100644 index 0000000000..433a31caf1 --- /dev/null +++ b/backend/migrations/ipfs_integration/migration_helpers.py @@ -0,0 +1,53 @@ +import json +import os +from datetime import datetime +from typing import Dict +from alembic import op + + +def _load_json_data(filepath: str) -> Dict: + with open(filepath, "r") as file: + data = json.load(file) + return data[0] + + +def _get_json_path(filename: str) -> str: + json_filename = os.path.join(os.path.dirname(__file__), "files", filename) + return json_filename + + +def _prepare_project_upsert_query(project: dict, epoch: int) -> str: + current_time = datetime.utcnow() + + return f""" + INSERT INTO project_details (name, address, created_at, epoch) + VALUES ('{project["name"]}', '{project["address"]}', '{current_time}', {epoch}) + ON CONFLICT (address, epoch) + DO UPDATE SET + name = EXCLUDED.name, + created_at = '{current_time}'; + """ + + +def _prepare_delete_project_query(project: dict, epoch: int) -> str: + return f""" + DELETE FROM project_details WHERE address = '{project["address"]}' AND epoch = {epoch} + """ + + +def upgrade(filename: str, epoch: int): + json_filepath = _get_json_path(filename) + project_data = _load_json_data(json_filepath) + + for project in project_data: + query = _prepare_project_upsert_query(project, epoch) + op.execute(query) + + +def downgrade(filename: str, epoch: int): + json_filepath = _get_json_path(filename) + project_data = _load_json_data(json_filepath) + + for project in project_data: + query = _prepare_delete_project_query(project, epoch) + op.execute(query) diff --git a/backend/migrations/versions/87b2cefcfa11_add_projectsdetails_table.py b/backend/migrations/versions/87b2cefcfa11_add_projectsdetails_table.py new file mode 100644 index 0000000000..823fa45ec9 --- /dev/null +++ b/backend/migrations/versions/87b2cefcfa11_add_projectsdetails_table.py @@ -0,0 +1,32 @@ +"""Add ProjectsDetails table + +Revision ID: 87b2cefcfa11 +Revises: 8b425b454a86 +Create Date: 2024-09-20 11:12:33.753739 + +""" +from alembic import op +import sqlalchemy as sa + + +revision = "87b2cefcfa11" +down_revision = "8b425b454a86" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "project_details", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("address", sa.String(length=42), nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.Column("epoch", sa.Integer(), nullable=False), + sa.Column("created_at", sa.TIMESTAMP(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("address", "epoch", name="uq_address_epoch"), + ) + + +def downgrade(): + op.drop_table("project_details") diff --git a/backend/migrations/versions/8b425b454a86_fix_created_at_field.py b/backend/migrations/versions/8b425b454a86_fix_created_at_field.py new file mode 100644 index 0000000000..0d8c01b0aa --- /dev/null +++ b/backend/migrations/versions/8b425b454a86_fix_created_at_field.py @@ -0,0 +1,28 @@ +"""Fix created_at field + +Revision ID: 8b425b454a86 +Revises: 999999999999 +Create Date: 2024-08-02 12:32:50.490759 + +""" +from alembic import op +import sqlalchemy as sa + +revision = "8b425b454a86" +down_revision = "999999999999" +branch_labels = None +depends_on = None + +HASH1 = "a1ee927c11efc35ffef40fa51547e0770df76aab9085da332311ac9d629fa518" +HASH2 = "f6b78725294faab4442f38aedb97ff7bc8fcaf9d73edf9845e1c57496e6d2913" +HASH3 = "93bf4d5bb695b96edd45c0d4eae59fe3f5ecc657f7137407288fd82834476a0b" + + +def upgrade(): + query = f"UPDATE score_delegation SET created_at = make_date(2024, 7, 17) WHERE hashed_addr IN ('{HASH1}', '{HASH2}', '{HASH3}');" + op.execute(query) + + +def downgrade(): + query = f"UPDATE score_delegation SET created_at = NULL WHERE hashed_addr IN ('{HASH1}', '{HASH2}', '{HASH3}');" + op.execute(query) diff --git a/backend/migrations/versions/c34003767fa8_add_projects_details_from_ipfs_for_.py b/backend/migrations/versions/c34003767fa8_add_projects_details_from_ipfs_for_.py new file mode 100644 index 0000000000..45519e4156 --- /dev/null +++ b/backend/migrations/versions/c34003767fa8_add_projects_details_from_ipfs_for_.py @@ -0,0 +1,28 @@ +"""Add projects details from IPFS for Epoch 1, 2, 3 and 4 + +Revision ID: c34003767fa8 +Revises: 87b2cefcfa11 +Create Date: 2024-09-20 11:14:31.965331 + +""" +from migrations.ipfs_integration import migration_helpers as ipfs_migration + +revision = "c34003767fa8" +down_revision = "87b2cefcfa11" +branch_labels = None +depends_on = None + +FILENAME = "ipfs_projects_details_epoch_{}.json" +EPOCHS = (1, 2, 3, 4) + + +def upgrade(): + for epoch in EPOCHS: + filename = FILENAME.format(epoch) + ipfs_migration.upgrade(filename, epoch) + + +def downgrade(): + for epoch in EPOCHS: + filename = FILENAME.format(epoch) + ipfs_migration.downgrade(filename, epoch) diff --git a/backend/poetry.lock b/backend/poetry.lock index 6aa8c0c014..209ea55a9f 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.9.5" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -100,7 +99,6 @@ speedups = ["Brotli", "aiodns", "brotlicffi"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -115,7 +113,6 @@ frozenlist = ">=1.1.0" name = "alembic" version = "1.13.1" description = "A database migration tool for SQLAlchemy." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -135,7 +132,6 @@ tz = ["backports.zoneinfo"] name = "aniso8601" version = "9.0.1" description = "A library for parsing ISO 8601 strings." -category = "main" optional = false python-versions = "*" files = [ @@ -150,7 +146,6 @@ dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] name = "annotated-types" version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -162,7 +157,6 @@ files = [ name = "anyio" version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -183,7 +177,6 @@ trio = ["trio (>=0.23)"] name = "apscheduler" version = "3.10.4" description = "In-process task scheduler with Cron-like capabilities" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -194,7 +187,7 @@ files = [ [package.dependencies] pytz = "*" six = ">=1.4.0" -tzlocal = ">=2.0,<3.0.0 || >=4.0.0" +tzlocal = ">=2.0,<3.dev0 || >=4.dev0" [package.extras] doc = ["sphinx", "sphinx-rtd-theme"] @@ -208,11 +201,21 @@ tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + [[package]] name = "attrs" version = "23.2.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -232,7 +235,6 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p name = "backoff" version = "2.2.1" description = "Function decoration for backoff and retry" -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -244,7 +246,6 @@ files = [ name = "bidict" version = "0.23.1" description = "The bidirectional mapping library for Python." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -256,7 +257,6 @@ files = [ name = "bitarray" version = "2.9.2" description = "efficient arrays of booleans -- C extension" -category = "main" optional = false python-versions = "*" files = [ @@ -388,7 +388,6 @@ files = [ name = "black" version = "23.12.1" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -433,7 +432,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "blinker" version = "1.8.2" description = "Fast, simple object-to-object and broadcast signaling" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -445,7 +443,6 @@ files = [ name = "cachelib" version = "0.9.0" description = "A collection of cache libraries in the same API interface." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -457,7 +454,6 @@ files = [ name = "certifi" version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -469,7 +465,6 @@ files = [ name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -569,7 +564,6 @@ files = [ name = "ckzg" version = "1.0.2" description = "Python bindings for C-KZG-4844" -category = "main" optional = false python-versions = "*" files = [ @@ -664,7 +658,6 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -679,7 +672,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -691,7 +683,6 @@ files = [ name = "coverage" version = "7.5.3" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -756,7 +747,6 @@ toml = ["tomli"] name = "cytoolz" version = "0.12.3" description = "Cython implementation of Toolz: High performance functional utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -876,7 +866,6 @@ cython = ["cython"] name = "dataclass-wizard" version = "0.22.3" description = "Marshal dataclasses to/from JSON. Use field properties with initial values. Construct a dataclass schema with JSON input." -category = "main" optional = false python-versions = "*" files = [ @@ -893,7 +882,6 @@ yaml = ["PyYAML (>=5.3)"] name = "dnspython" version = "2.6.1" description = "DNS toolkit" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -914,7 +902,6 @@ wmi = ["wmi (>=1.5.1)"] name = "epc" version = "0.0.5" description = "EPC (RPC stack for Emacs Lisp) implementation in Python" -category = "dev" optional = false python-versions = "*" files = [ @@ -928,7 +915,6 @@ sexpdata = ">=0.0.3" name = "eth-abi" version = "4.2.1" description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" -category = "main" optional = false python-versions = ">=3.7.2, <4" files = [ @@ -952,7 +938,6 @@ tools = ["hypothesis (>=4.18.2,<5.0.0)"] name = "eth-account" version = "0.11.2" description = "eth-account: Sign Ethereum transactions and messages with local private keys" -category = "main" optional = false python-versions = "<4,>=3.8" files = [ @@ -980,7 +965,6 @@ test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=7.0.0)", "pytest-xdis name = "eth-hash" version = "0.7.0" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" -category = "main" optional = false python-versions = ">=3.8, <4" files = [ @@ -1002,7 +986,6 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] name = "eth-keyfile" version = "0.8.1" description = "eth-keyfile: A library for handling the encrypted keyfiles used to store ethereum private keys" -category = "main" optional = false python-versions = "<4,>=3.8" files = [ @@ -1024,7 +1007,6 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] name = "eth-keys" version = "0.5.1" description = "eth-keys: Common API for Ethereum key operations" -category = "main" optional = false python-versions = "<4,>=3.8" files = [ @@ -1046,7 +1028,6 @@ test = ["asn1tools (>=0.146.2)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "h name = "eth-rlp" version = "1.0.1" description = "eth-rlp: RLP definitions for common Ethereum objects in Python" -category = "main" optional = false python-versions = ">=3.8, <4" files = [ @@ -1069,7 +1050,6 @@ test = ["eth-hash[pycryptodome]", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] name = "eth-typing" version = "4.3.1" description = "eth-typing: Common type annotations for ethereum python packages" -category = "main" optional = false python-versions = "<4,>=3.8" files = [ @@ -1089,7 +1069,6 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] name = "eth-utils" version = "4.1.1" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" -category = "main" optional = false python-versions = "<4,>=3.8" files = [ @@ -1112,7 +1091,6 @@ test = ["hypothesis (>=4.43.0)", "mypy (==1.5.1)", "pytest (>=7.0.0)", "pytest-x name = "eventlet" version = "0.33.3" description = "Highly concurrent networking library" -category = "main" optional = false python-versions = "*" files = [ @@ -1129,7 +1107,6 @@ six = ">=1.10.0" name = "flake8" version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -1146,7 +1123,6 @@ pyflakes = ">=3.1.0,<3.2.0" name = "flake8-bugbear" version = "23.12.2" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." -category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -1165,7 +1141,6 @@ dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "pytest", name = "flake8-pyproject" version = "1.2.3" description = "Flake8 plug-in loading the configuration from pyproject.toml" -category = "dev" optional = false python-versions = ">= 3.6" files = [ @@ -1182,7 +1157,6 @@ dev = ["pyTest", "pyTest-cov"] name = "flask" version = "2.3.3" description = "A simple framework for building complex web applications." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1205,7 +1179,6 @@ dotenv = ["python-dotenv"] name = "flask-apscheduler" version = "1.13.1" description = "Adds APScheduler support to Flask" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1221,7 +1194,6 @@ python-dateutil = ">=2.4.2" name = "flask-caching" version = "2.3.0" description = "Adds caching support to Flask applications." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1237,7 +1209,6 @@ Flask = "*" name = "flask-cors" version = "4.0.1" description = "A Flask extension adding a decorator for CORS support" -category = "main" optional = false python-versions = "*" files = [ @@ -1252,7 +1223,6 @@ Flask = ">=0.9" name = "flask-migrate" version = "4.0.7" description = "SQLAlchemy database migrations for Flask applications using Alembic." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1269,7 +1239,6 @@ Flask-SQLAlchemy = ">=1.0" name = "flask-restx" version = "1.3.0" description = "Fully featured framework for fast, easy and documented API development with Flask" -category = "main" optional = false python-versions = "*" files = [ @@ -1294,7 +1263,6 @@ test = ["Faker (==2.0.0)", "blinker", "invoke (==2.2.0)", "mock (==3.0.5)", "pyt name = "flask-socketio" version = "5.3.6" description = "Socket.IO integration for Flask applications" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1313,7 +1281,6 @@ docs = ["sphinx"] name = "flask-sqlalchemy" version = "3.1.1" description = "Add SQLAlchemy support to your Flask application." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1329,7 +1296,6 @@ sqlalchemy = ">=2.0.16" name = "freezegun" version = "1.5.1" description = "Let your Python tests travel through time" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1344,7 +1310,6 @@ python-dateutil = ">=2.7" name = "frozenlist" version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1431,7 +1396,6 @@ files = [ name = "gmpy2" version = "2.1.5" description = "gmpy2 interface to GMP/MPIR, MPFR, and MPC for Python 2.7 and 3.5+" -category = "main" optional = false python-versions = "*" files = [ @@ -1492,7 +1456,6 @@ files = [ name = "gql" version = "3.5.0" description = "GraphQL client for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -1525,7 +1488,6 @@ websockets = ["websockets (>=10,<12)"] name = "graphql-core" version = "3.2.3" description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." -category = "main" optional = false python-versions = ">=3.6,<4" files = [ @@ -1537,7 +1499,6 @@ files = [ name = "greenlet" version = "3.0.3" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1609,7 +1570,6 @@ test = ["objgraph", "psutil"] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1621,7 +1581,6 @@ files = [ name = "hexbytes" version = "0.3.1" description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" -category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -1639,7 +1598,6 @@ test = ["eth-utils (>=1.0.1,<3)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>= name = "idna" version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1651,7 +1609,6 @@ files = [ name = "importlib-resources" version = "6.4.0" description = "Read resources from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1667,7 +1624,6 @@ testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "p name = "importmagic" version = "0.1.7" description = "Python Import Magic - automagically add, remove and manage imports" -category = "dev" optional = false python-versions = "*" files = [ @@ -1681,7 +1637,6 @@ setuptools = ">=0.6b1" name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1693,7 +1648,6 @@ files = [ name = "itsdangerous" version = "2.2.0" description = "Safely pass data to untrusted environments and back." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1705,7 +1659,6 @@ files = [ name = "jinja2" version = "3.1.4" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1723,7 +1676,6 @@ i18n = ["Babel (>=2.7)"] name = "jsonschema" version = "4.17.3" description = "An implementation of JSON Schema validation for Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1743,7 +1695,6 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "lru-dict" version = "1.2.0" description = "An Dict like LRU container." -category = "main" optional = false python-versions = "*" files = [ @@ -1838,7 +1789,6 @@ test = ["pytest"] name = "mako" version = "1.3.5" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1858,7 +1808,6 @@ testing = ["pytest"] name = "markupsafe" version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1928,7 +1877,6 @@ files = [ name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1940,7 +1888,6 @@ files = [ name = "multidict" version = "6.0.5" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2040,7 +1987,6 @@ files = [ name = "multiproof" version = "0.1.2" description = "A Python library to generate merkle trees and merkle proofs." -category = "main" optional = false python-versions = "^3.10" files = [] @@ -2060,7 +2006,6 @@ resolved_reference = "e1f3633a10cb5929cc08d4f261effd170976e7b9" name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2072,7 +2017,6 @@ files = [ name = "nodeenv" version = "1.9.1" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -2084,7 +2028,6 @@ files = [ name = "numpy" version = "2.0.0" description = "Fundamental package for array computing in Python" -category = "main" optional = false python-versions = ">=3.9" files = [ @@ -2139,7 +2082,6 @@ files = [ name = "packaging" version = "24.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2151,7 +2093,6 @@ files = [ name = "pandas" version = "2.2.2" description = "Powerful data structures for data analysis, time series, and statistics" -category = "main" optional = false python-versions = ">=3.9" files = [ @@ -2224,7 +2165,6 @@ xml = ["lxml (>=4.9.2)"] name = "parsimonious" version = "0.9.0" description = "(Soon to be) the fastest pure-Python PEG parser I could muster" -category = "main" optional = false python-versions = "*" files = [ @@ -2238,7 +2178,6 @@ regex = ">=2022.3.15" name = "pathspec" version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2250,7 +2189,6 @@ files = [ name = "platformdirs" version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2267,7 +2205,6 @@ type = ["mypy (>=1.8)"] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2283,7 +2220,6 @@ testing = ["pytest", "pytest-benchmark"] name = "protobuf" version = "5.27.1" description = "" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2304,7 +2240,6 @@ files = [ name = "psycopg2-binary" version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2386,7 +2321,6 @@ files = [ name = "pycodestyle" version = "2.11.1" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2398,7 +2332,6 @@ files = [ name = "pycryptodome" version = "3.20.0" description = "Cryptographic library for Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2440,7 +2373,6 @@ files = [ name = "pydantic" version = "2.7.4" description = "Data validation using Python type hints" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2460,7 +2392,6 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.18.4" description = "Core functionality for Pydantic validation and serialization" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2552,7 +2483,6 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pyflakes" version = "3.1.0" description = "passive checker of Python programs" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2564,7 +2494,6 @@ files = [ name = "pylookup" version = "0.2.2" description = "PyLookup - Fuzzy-matching table autofill tool" -category = "dev" optional = false python-versions = "*" files = [ @@ -2581,7 +2510,6 @@ rapidfuzz = "*" name = "pyright" version = "1.1.368" description = "Command line wrapper for pyright" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2600,7 +2528,6 @@ dev = ["twine (>=3.4.1)"] name = "pyrsistent" version = "0.20.0" description = "Persistent/Functional/Immutable data structures" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2642,7 +2569,6 @@ files = [ name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2663,7 +2589,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2682,7 +2607,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-mock" version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2700,7 +2624,6 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "python-dateutil" version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -2715,7 +2638,6 @@ six = ">=1.5" name = "python-dotenv" version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2730,7 +2652,6 @@ cli = ["click (>=5.0)"] name = "python-engineio" version = "4.9.1" description = "Engine.IO server and client for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2750,7 +2671,6 @@ docs = ["sphinx"] name = "python-socketio" version = "5.11.3" description = "Socket.IO server and client for Python" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2771,7 +2691,6 @@ docs = ["sphinx"] name = "pytz" version = "2024.1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -2783,7 +2702,6 @@ files = [ name = "pyunormalize" version = "15.1.0" description = "Unicode normalization forms (NFC, NFKC, NFD, NFKD). A library independent from the Python core Unicode database." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2794,7 +2712,6 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" -category = "main" optional = false python-versions = "*" files = [ @@ -2818,7 +2735,6 @@ files = [ name = "rapidfuzz" version = "3.9.3" description = "rapid fuzzy string matching" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2920,11 +2836,28 @@ files = [ [package.extras] full = ["numpy"] +[[package]] +name = "redis" +version = "5.0.7" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.7" +files = [ + {file = "redis-5.0.7-py3-none-any.whl", hash = "sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db"}, + {file = "redis-5.0.7.tar.gz", hash = "sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + [[package]] name = "regex" version = "2024.5.15" description = "Alternative regular expression module, to replace re." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3013,7 +2946,6 @@ files = [ name = "requests" version = "2.32.3" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3035,7 +2967,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-toolbelt" version = "1.0.0" description = "A utility belt for advanced users of python-requests" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3050,7 +2981,6 @@ requests = ">=2.0.1,<3.0.0" name = "rlp" version = "4.0.1" description = "rlp: A package for Recursive Length Prefix encoding and decoding" -category = "main" optional = false python-versions = "<4,>=3.8" files = [ @@ -3071,7 +3001,6 @@ test = ["hypothesis (==5.19.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] name = "sentry-sdk" version = "2.6.0" description = "Python client for Sentry (https://sentry.io)" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3125,7 +3054,6 @@ tornado = ["tornado (>=5)"] name = "setuptools" version = "70.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3141,7 +3069,6 @@ testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metad name = "sexpdata" version = "1.0.2" description = "S-expression parser for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3153,7 +3080,6 @@ files = [ name = "simple-websocket" version = "1.0.0" description = "Simple WebSocket server and client for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3171,7 +3097,6 @@ docs = ["sphinx"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3183,7 +3108,6 @@ files = [ name = "sniffio" version = "1.3.1" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3195,7 +3119,6 @@ files = [ name = "sqlalchemy" version = "2.0.31" description = "Database Abstraction Library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3251,7 +3174,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and platform_machine == \"aarch64\" or python_version < \"3.13\" and platform_machine == \"ppc64le\" or python_version < \"3.13\" and platform_machine == \"x86_64\" or python_version < \"3.13\" and platform_machine == \"amd64\" or python_version < \"3.13\" and platform_machine == \"AMD64\" or python_version < \"3.13\" and platform_machine == \"win32\" or python_version < \"3.13\" and platform_machine == \"WIN32\""} +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} typing-extensions = ">=4.6.0" [package.extras] @@ -3283,7 +3206,6 @@ sqlcipher = ["sqlcipher3_binary"] name = "toolz" version = "0.12.1" description = "List processing tools and functional utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3295,7 +3217,6 @@ files = [ name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3307,7 +3228,6 @@ files = [ name = "tzdata" version = "2024.1" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -3319,7 +3239,6 @@ files = [ name = "tzlocal" version = "5.2" description = "tzinfo object for the local timezone" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3337,7 +3256,6 @@ devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3) name = "urllib3" version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3355,7 +3273,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "web3" version = "6.19.0" description = "web3.py" -category = "main" optional = false python-versions = ">=3.7.2" files = [ @@ -3390,7 +3307,6 @@ tester = ["eth-tester[py-evm] (>=0.11.0b1,<0.12.0b1)", "eth-tester[py-evm] (>=0. name = "websockets" version = "12.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3472,7 +3388,6 @@ files = [ name = "werkzeug" version = "3.0.3" description = "The comprehensive WSGI web application library." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3490,7 +3405,6 @@ watchdog = ["watchdog (>=2.3)"] name = "wsproto" version = "1.2.0" description = "WebSockets state-machine based protocol implementation" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -3505,7 +3419,6 @@ h11 = ">=0.9.0,<1" name = "yarl" version = "1.9.4" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3608,4 +3521,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "cc98480e58adeae7f2c86a6a5ead4e27e1844c026f069cae21d1e19f4d2937b5" +content-hash = "8beb2e0b06481e87b431a937a21c11d21f06810f5b7497781c4be087d48e4b44" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index bdf79fe88d..683adf29af 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -28,6 +28,7 @@ pydantic = "^2.6.0" pandas = "^2.2.0" gmpy2 = "^2.1.5" sentry-sdk = {extras = ["flask"], version = "^2.5.1"} +redis = "^5.0.7" [tool.poetry.group.dev.dependencies] pytest = "^7.3.1" diff --git a/backend/startup.py b/backend/startup.py index f100cc84d9..861c38017a 100644 --- a/backend/startup.py +++ b/backend/startup.py @@ -20,12 +20,35 @@ if os.getenv("SENTRY_DSN"): import sentry_sdk + def sentry_before_send(event, hint): + exceptions = event.get("exception", []) + if not exceptions: + return event + + exc = exceptions[-1] + mechanism = exc.get("mechanism", {}) + + if mechanism.get("handled"): + return None + + return event + exceptions = event["exception"] + if exceptions: + exc = exceptions[-1] + mechanism = exc.get("mechanism") + if mechanism: + if mechanism.get("handled"): + return None + + return event + print("[+] Starting sentry") sentry_sdk.init( traces_sample_rate=1.0, profiles_sample_rate=1.0, enable_tracing=True, + before_send=sentry_before_send, ) from app import create_app # noqa diff --git a/backend/tests/api-e2e/conftest.py b/backend/tests/api-e2e/conftest.py new file mode 100644 index 0000000000..21c5da8ba0 --- /dev/null +++ b/backend/tests/api-e2e/conftest.py @@ -0,0 +1,14 @@ +import pytest + +from app.modules.dto import ScoreDelegationPayload +from tests.helpers.constants import USER1_ADDRESS, USER2_ADDRESS + + +@pytest.fixture() +def payload(): + return ScoreDelegationPayload( + primary_addr=USER1_ADDRESS, + secondary_addr=USER2_ADDRESS, + primary_addr_signature="0x4c7f3b8d06ef3abbe6f5c0762fda01517c62709a3e0bde7ae19a945d3359b0673197db2dabeb20babb9b71c2cbb7e83cfa4cb3078c9bcdc284dcd605ebe89ddc1b", + secondary_addr_signature="0x5e7e86d5acea5cc431b8d148842e21584a7afe16b7de3b5586d20f5de97179f549726baa021dcaf6220ee5116c579df9d40375fa58d3480390289df6a088b9ec1b", + ) diff --git a/backend/tests/api-e2e/test_api_allocations.py b/backend/tests/api-e2e/test_api_allocations.py index 730912bab8..ceba1d6ba0 100644 --- a/backend/tests/api-e2e/test_api_allocations.py +++ b/backend/tests/api-e2e/test_api_allocations.py @@ -1,9 +1,9 @@ import pytest - from flask import current_app as app + from app.legacy.core.projects import get_projects_addresses from tests.conftest import Client, UserAccount -from tests.helpers.constants import STARTING_EPOCH +from tests.helpers.constants import STARTING_EPOCH, LOW_UQ_SCORE @pytest.mark.api @@ -49,24 +49,21 @@ def test_allocations( assert len(unique_proposals) == 3 -@pytest.mark.api -def test_allocations_basics( - client: Client, - deployer: UserAccount, - ua_alice: UserAccount, - ua_bob: UserAccount, - setup_funds, +def _check_allocations_logic( + client: Client, ua_alice: UserAccount, target_pending_epoch: int ): alice_proposals = get_projects_addresses(1)[:3] - # lock GLM from one account - ua_alice.lock(10000) + i = 0 + for i in range(0, target_pending_epoch + 1): + if i > 0: + client.move_to_next_epoch(STARTING_EPOCH + i) - # forward time to the beginning of the epoch 2 - client.move_to_next_epoch(STARTING_EPOCH + 1) + if STARTING_EPOCH + i == target_pending_epoch: + ua_alice.lock(10000) # wait for indexer to catch up - epoch_no = client.wait_for_sync(STARTING_EPOCH + 1) + epoch_no = client.wait_for_sync(STARTING_EPOCH + i) app.logger.debug(f"indexed epoch: {epoch_no}") # make a snapshot @@ -84,7 +81,7 @@ def test_allocations_basics( allocation_response_code == 201 ), "Allocation status code is different than 201" - epoch_allocations, status_code = client.get_epoch_allocations(STARTING_EPOCH) + epoch_allocations, status_code = client.get_epoch_allocations(target_pending_epoch) assert len(epoch_allocations["allocations"]) == len(alice_proposals) for allocation in epoch_allocations["allocations"]: @@ -97,14 +94,14 @@ def test_allocations_basics( # Check user donations user_allocations, status_code = client.get_user_allocations( - STARTING_EPOCH, ua_alice.address + target_pending_epoch, ua_alice.address ) app.logger.debug(f"User allocations: {user_allocations}") assert user_allocations["allocations"], "User allocations for given epoch are empty" assert status_code == 200, "Status code is different than 200" # Check donors - donors, status_code = client.get_donors(STARTING_EPOCH) + donors, status_code = client.get_donors(target_pending_epoch) app.logger.debug(f"Donors: {donors}") for donor in donors["donors"]: assert donor == ua_alice.address, "Donor address is wrong" @@ -113,7 +110,7 @@ def test_allocations_basics( proposal_address = alice_proposals[0] # Check donors of particular proposal proposal_donors, status_code = client.get_proposal_donors( - STARTING_EPOCH, proposal_address + target_pending_epoch, proposal_address ) app.logger.debug(f"Proposal donors: {proposal_donors}") for proposal_donor in proposal_donors: @@ -133,3 +130,31 @@ def test_allocations_basics( assert matched["value"], "Leverage value is empty" assert status_code == 200, "Status code is different than 200" + + +@pytest.mark.api +def test_allocations_basics( + client: Client, + deployer: UserAccount, + ua_alice: UserAccount, + ua_bob: UserAccount, + setup_funds, +): + _check_allocations_logic(client, ua_alice, target_pending_epoch=1) + + +@pytest.mark.api +def test_qf_and_uq_allocations(client: Client, ua_alice: UserAccount): + """ + Test for QF and UQ allocations. + This test checks if we use the QF alongside with UQ functionality properly. + Introduced in E4. + """ + PENDING_EPOCH = STARTING_EPOCH + 3 + + _check_allocations_logic(client, ua_alice, target_pending_epoch=PENDING_EPOCH) + + # Check if UQ is saved in the database after the allocation properly + res, code = client.get_user_uq(ua_alice.address, 4) + assert code == 200 + assert res["uniquenessQuotient"] == str(LOW_UQ_SCORE) diff --git a/backend/tests/api-e2e/test_api_antisybil.py b/backend/tests/api-e2e/test_api_antisybil.py index afbd8a5cec..cc57ee2480 100644 --- a/backend/tests/api-e2e/test_api_antisybil.py +++ b/backend/tests/api-e2e/test_api_antisybil.py @@ -19,7 +19,7 @@ def test_antisybil(client: Client, ua_alice: UserAccount): assert code == 200 # score available assert score["status"] == "Known" assert float(score["score"]) > 0 - assert int(score["expires_at"]) > 0 + assert int(score["expiresAt"]) > 0 # flow for a brand new address, which couldn't be scored by GP yet ua_jane = UserAccount(w3.eth.account.create(), client) @@ -34,4 +34,4 @@ def test_antisybil(client: Client, ua_alice: UserAccount): assert code == 200 # score available assert score["status"] == "Known" assert float(score["score"]) == 0.0 - assert int(score["expires_at"]) > 0 + assert int(score["expiresAt"]) > 0 diff --git a/backend/tests/api-e2e/test_api_delegation.py b/backend/tests/api-e2e/test_api_delegation.py new file mode 100644 index 0000000000..ccb0bc774a --- /dev/null +++ b/backend/tests/api-e2e/test_api_delegation.py @@ -0,0 +1,155 @@ +import pytest +from flask import current_app as app + +from app.modules.dto import ScoreDelegationPayload +from app.infrastructure import database +from tests.conftest import Client +from tests.helpers.constants import STARTING_EPOCH, USER1_ADDRESS, USER2_ADDRESS + + +@pytest.mark.api +def test_delegation(client: Client, payload: ScoreDelegationPayload): + client.move_to_next_epoch(STARTING_EPOCH + 1) + client.move_to_next_epoch(STARTING_EPOCH + 2) + client.move_to_next_epoch(STARTING_EPOCH + 3) + + epoch_no = client.wait_for_sync(STARTING_EPOCH + 3) + app.logger.debug(f"indexed epoch: {epoch_no}") + + database.user.add_user(USER1_ADDRESS) + database.user.add_user(USER2_ADDRESS) + + # refresh scores for users + _, code = client.refresh_antisybil_score(USER1_ADDRESS) + assert code == 204 + + _, code = client.refresh_antisybil_score(USER2_ADDRESS) + assert code == 204 + + # retrieve a delegator's score + delegator_score, code = client.get_antisybil_score(USER2_ADDRESS) + assert code == 200 + assert delegator_score["status"] == "Known" + assert float(delegator_score["score"]) > 0 + assert int(delegator_score["expiresAt"]) > 0 + + # retrieve a delegatee's score + delegatee_score, code = client.get_antisybil_score(USER1_ADDRESS) + assert code == 200 + assert delegator_score["status"] == "Known" + assert float(delegator_score["score"]) > 0 + assert int(delegator_score["expiresAt"]) > 0 + + # check that scores are different before delegation + assert float(delegator_score["score"]) != float(delegatee_score["score"]) + + _, status = client.delegate( + primary_address=payload.primary_addr, + secondary_address=payload.secondary_addr, + primary_address_signature=payload.primary_addr_signature, + secondary_address_signature=payload.secondary_addr_signature, + ) + assert status == 201 + + # check that scores are the same after delegation + delegatee_score, code = client.get_antisybil_score(payload.secondary_addr) + assert code == 200 + assert delegatee_score["status"] == "Known" + assert float(delegatee_score["score"]) == float(delegator_score["score"]) + + # check if the secondary address is actually used off + resp, code = client.delegate( + primary_address=payload.primary_addr, + secondary_address=payload.secondary_addr, + primary_address_signature=payload.primary_addr_signature, + secondary_address_signature=payload.secondary_addr_signature, + ) + + assert code == 400 + assert resp["message"] == "Delegation already exists" + + +@pytest.mark.api +def test_recalculate_in_delegation(client: Client, payload: ScoreDelegationPayload): + """ + Recalculation can actually return two different results: + - if the delegation does not exist, it will return 400 + - if the delegation exists, i.e. secondary address exists in the database, it will return 400 + it's due to the fact that the recalculation is already stoned for a secondary address in our implementation + """ + client.move_to_next_epoch(STARTING_EPOCH + 1) + client.move_to_next_epoch(STARTING_EPOCH + 2) + client.move_to_next_epoch(STARTING_EPOCH + 3) + + epoch_no = client.wait_for_sync(STARTING_EPOCH + 3) + app.logger.debug(f"indexed epoch: {epoch_no}") + + database.user.add_user(USER1_ADDRESS) + database.user.add_user(USER2_ADDRESS) + + # try to recalculate before delegation + data, status = client.delegation_recalculate( + primary_address=payload.primary_addr, + secondary_address=payload.secondary_addr, + primary_address_signature=payload.primary_addr_signature, + secondary_address_signature=payload.secondary_addr_signature, + ) + assert data["message"] == "Delegation does not exists" + assert status == 400 + + # make a delegation + _, status = client.delegate( + primary_address=payload.primary_addr, + secondary_address=payload.secondary_addr, + primary_address_signature=payload.primary_addr_signature, + secondary_address_signature=payload.secondary_addr_signature, + ) + assert status == 201 + + # recalculate after delegation + data, status = client.delegation_recalculate( + primary_address=payload.primary_addr, + secondary_address=payload.secondary_addr, + primary_address_signature=payload.primary_addr_signature, + secondary_address_signature=payload.secondary_addr_signature, + ) + + assert data["message"] == "Invalid recalculation request" + assert status == 400 + + +@pytest.mark.api +def test_check_delegation(client: Client, payload: ScoreDelegationPayload): + client.move_to_next_epoch(STARTING_EPOCH + 1) + client.move_to_next_epoch(STARTING_EPOCH + 2) + client.move_to_next_epoch(STARTING_EPOCH + 3) + + epoch_no = client.wait_for_sync(STARTING_EPOCH + 3) + app.logger.debug(f"indexed epoch: {epoch_no}") + + database.user.add_user(USER1_ADDRESS) + database.user.add_user(USER2_ADDRESS) + + # check if invalid request is handled correctly + addresses = [payload.primary_addr] * 12 + _, status = client.check_delegation(*addresses) + assert status == 400 + + # check that obfuscated delegation does not exist + _, status = client.check_delegation(payload.primary_addr, payload.secondary_addr) + assert status == 400 + + # conduct a delegation + _, status = client.delegate( + primary_address=payload.primary_addr, + secondary_address=payload.secondary_addr, + primary_address_signature=payload.primary_addr_signature, + secondary_address_signature=payload.secondary_addr_signature, + ) + assert status == 201 + + # check if given addresses are used for delegation + resp, status = client.check_delegation(payload.primary_addr, payload.secondary_addr) + assert status == 200 + assert resp["primary"] == payload.primary_addr + assert resp["secondary"] == payload.secondary_addr diff --git a/backend/tests/api-e2e/test_api_history.py b/backend/tests/api-e2e/test_api_history.py new file mode 100644 index 0000000000..f5e719b2d2 --- /dev/null +++ b/backend/tests/api-e2e/test_api_history.py @@ -0,0 +1,50 @@ +import pytest + +from tests.conftest import Client, UserAccount +from app.legacy.core.projects import get_projects_addresses +from tests.helpers.constants import STARTING_EPOCH +from flask import current_app as app + + +@pytest.mark.api +def test_history_basics( + client: Client, + deployer: UserAccount, + ua_alice: UserAccount, +): + # Check user history before allocation + user_history, status_code = client.get_user_history(ua_alice.address) + assert len(user_history["history"]) == 0, "User history should be empty" + assert status_code == 200 + + # Get alice proposals + alice_proposals = get_projects_addresses(1)[:3] + + # lock GLM for one account + ua_alice.lock(10000) + + # forward time to the beginning of the epoch 2 + client.move_to_next_epoch(STARTING_EPOCH + 1) + + # wait for indexer to catch up + epoch_no = client.wait_for_sync(STARTING_EPOCH + 1) + app.logger.debug(f"indexed epoch: {epoch_no}") + + # make a snapshot + res = client.pending_snapshot() + assert res["epoch"] == STARTING_EPOCH + + allocation_response_code = ua_alice.allocate(1000, alice_proposals) + assert ( + allocation_response_code == 201 + ), "Allocation status code is different than 201" + + # Check user history after allocation + user_history, status_code = client.get_user_history(ua_alice.address) + assert ( + user_history["history"][0]["type"] == "allocation" + ), "Type of history record should be 'allocation'" + assert ( + len(user_history["history"][0]["eventData"]["allocations"]) == 3 + ), "Number of allocations should be 3" + assert status_code == 200 diff --git a/backend/tests/api-e2e/test_api_info.py b/backend/tests/api-e2e/test_api_info.py new file mode 100644 index 0000000000..01bd44fcd2 --- /dev/null +++ b/backend/tests/api-e2e/test_api_info.py @@ -0,0 +1,35 @@ +import pytest + +from tests.conftest import Client +from tests.helpers.constants import STARTING_EPOCH + + +@pytest.mark.api +def test_info_basics( + client: Client, +): + # Check chain_info + chain_info, status_code = client.get_chain_info() + assert "chainName" in chain_info + assert "chainId" in chain_info + assert "smartContracts" in chain_info + assert status_code == 200 + + # Check healthcheck + healthcheck, status_code = client.get_healthcheck() + assert healthcheck["blockchain"] == "UP" + assert healthcheck["db"] == "UP" + assert healthcheck["subgraph"] == "UP" + assert status_code == 200 + + # Check version + version, status_code = client.get_version() + assert "id" in version + assert "env" in version + assert "chain" in version + assert status_code == 200 + + # Check sync_status + sync_status, status_code = client.sync_status() + assert sync_status["blockchainEpoch"] == STARTING_EPOCH + assert sync_status["indexedEpoch"] == STARTING_EPOCH diff --git a/backend/tests/api-e2e/test_api_rewards.py b/backend/tests/api-e2e/test_api_rewards.py new file mode 100644 index 0000000000..58fd8fad8d --- /dev/null +++ b/backend/tests/api-e2e/test_api_rewards.py @@ -0,0 +1,118 @@ +import pytest +import time + +from flask import current_app as app +from app.extensions import vault +from app.legacy.core.projects import get_projects_addresses +from tests.conftest import Client, UserAccount +from tests.helpers.constants import STARTING_EPOCH +from app.legacy.core import vault as vault_core + + +@pytest.mark.api +def test_rewards_basic( + client: Client, + deployer: UserAccount, + ua_alice: UserAccount, + ua_bob: UserAccount, + setup_funds, +): + alice_proposals = get_projects_addresses(1)[:3] + + # lock GLM from two accounts + ua_alice.lock(10000) + ua_bob.lock(15000) + + # forward time to the beginning of the epoch 2 + client.move_to_next_epoch(STARTING_EPOCH + 1) + + # fund the vault (amount here is arbitrary) + vault.fund(deployer._account, 1000 * 10**18) + + # wait for indexer to catch up + epoch_no = client.wait_for_sync(STARTING_EPOCH + 1) + app.logger.debug(f"indexed epoch: {epoch_no}") + + # make a snapshot + res = client.pending_snapshot() + assert res["epoch"] > 0 + + # check if both users have a budget + res = client.get_user_rewards_in_epoch( + address=ua_alice.address, epoch=STARTING_EPOCH + ) + alice_budget = int(res["budget"]) + assert alice_budget > 0 + + res = client.get_user_rewards_in_epoch(address=ua_bob.address, epoch=STARTING_EPOCH) + bob_budget = int(res["budget"]) + assert bob_budget > 0 + + # check if both users budgets are displayed in global budget endpoints + res = client.get_total_users_rewards_in_epoch(epoch=STARTING_EPOCH) + all_user_budgets = res["budgets"] + assert any(budget["amount"] == str(alice_budget) for budget in all_user_budgets) + assert any(budget["amount"] == str(bob_budget) for budget in all_user_budgets) + + ua_alice.allocate(1000, alice_proposals) + + # check estimated projects rewards before finalized snapshot + res = client.get_estimated_projects_rewards() + assert res["rewards"][0]["allocated"] == "1000" + + # TODO replace with helper to wait until end of voting + client.move_to_next_epoch(STARTING_EPOCH + 2) + epoch_no = client.wait_for_sync(STARTING_EPOCH + 2) + app.logger.debug(f"indexed epoch: {epoch_no}") + + # make a finalized snapshot + res = client.finalized_snapshot() + assert res["epoch"] == STARTING_EPOCH + + # get estimated budget by number of epochs + res = client.get_estimated_budget_by_epochs(1, 10000000000000000000000) + one_epoch_budget_estimation = int(res["budget"]) + assert one_epoch_budget_estimation > 0 + + res = client.get_estimated_budget_by_epochs(2, 10000000000000000000000) + two_epochs_budget_estimation = int(res["budget"]) + assert two_epochs_budget_estimation > 0 + assert two_epochs_budget_estimation > one_epoch_budget_estimation + + # get estimated budget by number of days + res = client.get_estimated_budget_by_days(200, 10000000000000000000000) + two_hundreds_days_budget_estimation = int(res["budget"]) + assert two_hundreds_days_budget_estimation > 0 + + res = client.get_estimated_budget_by_days(300, 10000000000000000000000) + three_hundreds_days_budget_estimation = int(res["budget"]) + assert three_hundreds_days_budget_estimation > 0 + assert three_hundreds_days_budget_estimation > two_hundreds_days_budget_estimation + + # write merkle root for withdrawals + vault_core.confirm_withdrawals() + + while not vault.is_merkle_root_set(STARTING_EPOCH): + time.sleep(1) + + # check rewards for all projects are returned in proper schema + res = client.get_projects_with_matched_rewards_in_epoch(epoch=STARTING_EPOCH) + assert len(res[0]["rewards"]) == 3 + for reward in res[0]["rewards"]: + assert "address" in reward + assert "allocated" in reward + assert "matched" in reward + + # check unused rewards + res = client.get_unused_rewards(epoch=STARTING_EPOCH) + assert int(res["value"]) == bob_budget + + # check epoch merkle root exists + res = client.get_rewards_merkle_tree(epoch=STARTING_EPOCH) + assert len(res["leaves"]) == 4 + assert res["leafEncoding"] == ["address", "uint256"] + + # check epoch leverage + res = client.get_rewards_leverage(epoch=STARTING_EPOCH) + epoch_leverage = int(res["leverage"]) + assert epoch_leverage > 0 diff --git a/backend/tests/api-e2e/test_api_snapshot.py b/backend/tests/api-e2e/test_api_snapshot.py index 9fd7a2fc2c..44a5c3cffa 100644 --- a/backend/tests/api-e2e/test_api_snapshot.py +++ b/backend/tests/api-e2e/test_api_snapshot.py @@ -30,11 +30,13 @@ def test_pending_snapshot( assert res["epoch"] > 0 # check if both users have a budget - res = client.get_rewards_budget(address=ua_alice.address, epoch=STARTING_EPOCH) + res = client.get_user_rewards_in_epoch( + address=ua_alice.address, epoch=STARTING_EPOCH + ) alice_budget = int(res["budget"]) assert alice_budget > 0 - res = client.get_rewards_budget(address=ua_bob.address, epoch=STARTING_EPOCH) + res = client.get_user_rewards_in_epoch(address=ua_bob.address, epoch=STARTING_EPOCH) bob_budget = int(res["budget"]) assert bob_budget > 0 diff --git a/backend/tests/api-e2e/test_api_uq.py b/backend/tests/api-e2e/test_api_uq.py new file mode 100644 index 0000000000..fecd24e2a0 --- /dev/null +++ b/backend/tests/api-e2e/test_api_uq.py @@ -0,0 +1,57 @@ +import pytest +from flask import current_app as app + +from app.infrastructure import database +from app.legacy.core.projects import get_projects_addresses +from tests.conftest import UserAccount, Client +from tests.helpers.constants import STARTING_EPOCH, LOW_UQ_SCORE, MAX_UQ_SCORE + + +@pytest.mark.api +def test_uq_for_user(client: Client, ua_alice: UserAccount): + client.move_to_next_epoch(STARTING_EPOCH + 1) + client.move_to_next_epoch(STARTING_EPOCH + 2) + client.move_to_next_epoch(STARTING_EPOCH + 3) + + epoch_no = client.wait_for_sync(STARTING_EPOCH + 3) + app.logger.debug(f"indexed epoch: {epoch_no}") + + USER_NOT_FOUND = 404 + _, code = client.get_user_uq(ua_alice.address, 4) + assert code == USER_NOT_FOUND + + database.user.add_user(ua_alice.address) + + res, code = client.get_user_uq(ua_alice.address, 4) + assert code == 200 + assert res["uniquenessQuotient"] in [str(LOW_UQ_SCORE), str(MAX_UQ_SCORE)] + + +@pytest.mark.api +def test_uq_for_all_users(client: Client, ua_alice: UserAccount, ua_bob, setup_funds): + client.move_to_next_epoch(STARTING_EPOCH + 1) + client.move_to_next_epoch(STARTING_EPOCH + 2) + client.move_to_next_epoch(STARTING_EPOCH + 3) + ua_alice.lock(10000) + ua_bob.lock(10000) + client.move_to_next_epoch(STARTING_EPOCH + 4) + + epoch_no = client.wait_for_sync(STARTING_EPOCH + 4) + app.logger.debug(f"indexed epoch: {epoch_no}") + + res = client.pending_snapshot() + assert res["epoch"] > 0 + + # make an allocation during AW since it saves the uq to the database + alice_bob_proposals = get_projects_addresses(4)[:3] + + ua_alice.allocate(1000, alice_bob_proposals) + ua_bob.allocate(1000, alice_bob_proposals) + + res, code = client.get_all_uqs(4) + assert code == 200 + assert type(res["uqsInfo"]) is list + assert len(res["uqsInfo"]) == 2 + + assert res["uqsInfo"][0]["userAddress"] == ua_alice.address + assert res["uqsInfo"][1]["userAddress"] == ua_bob.address diff --git a/backend/tests/api-e2e/test_api_withdrawals.py b/backend/tests/api-e2e/test_api_withdrawals.py index 352f10ca3f..2bbc290f29 100644 --- a/backend/tests/api-e2e/test_api_withdrawals.py +++ b/backend/tests/api-e2e/test_api_withdrawals.py @@ -43,21 +43,25 @@ def test_withdrawals( assert res["epoch"] > 0 # save account budget for assertion - res = client.get_rewards_budget(address=ua_alice.address, epoch=STARTING_EPOCH) + res = client.get_user_rewards_in_epoch( + address=ua_alice.address, epoch=STARTING_EPOCH + ) alice_budget = int(res["budget"]) # make empty vote to get personal rewards ua_alice.allocate(0, alice_proposals) # save account budget for assertion - res = client.get_rewards_budget(address=ua_bob.address, epoch=STARTING_EPOCH) + res = client.get_user_rewards_in_epoch(address=ua_bob.address, epoch=STARTING_EPOCH) bob_budget = int(res["budget"]) # make empty vote to get personal rewards ua_bob.allocate(0, bob_proposals) # save account budget for assertion - res = client.get_rewards_budget(address=ua_carol.address, epoch=STARTING_EPOCH) + res = client.get_user_rewards_in_epoch( + address=ua_carol.address, epoch=STARTING_EPOCH + ) carol_budget = int(res["budget"]) # make empty vote to get personal rewards diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 36febcaa09..285bd962ab 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -1,29 +1,30 @@ from __future__ import annotations import datetime -from http import HTTPStatus import json -import logging import os import time +import urllib.error import urllib.request +from http import HTTPStatus from unittest.mock import MagicMock, Mock import gql -from gql.transport.exceptions import TransportQueryError import pytest from flask import current_app from flask import g as request_context from flask.testing import FlaskClient +from gql.transport.exceptions import TransportQueryError from requests import RequestException from web3 import Web3 +import logging from app import create_app from app.engine.user.effective_deposit import DepositEvent, EventType, UserDeposit from app.exceptions import ExternalApiException from app.extensions import db, deposits, glm, gql_factory, w3, vault, epochs -from app.infrastructure import database from app.infrastructure import Client as GQLClient +from app.infrastructure import database from app.infrastructure.contracts.epochs import Epochs from app.infrastructure.contracts.erc20 import ERC20 from app.infrastructure.contracts.projects import Projects @@ -122,6 +123,13 @@ def mock_gitcoin_passport_issue_address_for_scoring(*args, **kwargs): "score": "22.0", "status": "DONE", } + # GTC staker, Carol + elif args[0] == "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC": + return { + "address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "score": str(4 + 0.5), + "status": "DONE", + } else: return {"status": "DONE", "score": "0.0"} @@ -153,225 +161,42 @@ def mock_gitcoin_passport_fetch_score(*args, **kwargs): "score": "22.0", "status": "DONE", } + # GTC staker, Carol + elif args[0] == "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC": + return { + "address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "score": str(4 + 0.5), + "status": "DONE", + } else: return {"status": "DONE", "score": "0.0"} def mock_gitcoin_passport_fetch_stamps(*args, **kwargs): + "Returns structure resembling GP stamps, but only with relevant fields" if args[0] == "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": return { - "next": None, - "prev": None, "items": [ { - "version": "1.0.0", "credential": { - "type": ["VerifiableCredential"], - "proof": { - "type": "EthereumEip712Signature2021", - "created": "2024-03-12T14:28:53.877Z", - "@context": "https://w3id.org/security/suites/eip712sig-2021/v1", - "proofValue": "0x5ef4c6d9ff1116c66d45c5bc65cf83ed1220b6faa4b6f78d8f057bb88470be8d4622f7bc8846accdd1057413c42408dbcce5cd4e55362fc6ac581b2f9536ec2c1b", - "eip712Domain": { - "types": { - "Proof": [ - {"name": "@context", "type": "string"}, - {"name": "created", "type": "string"}, - {"name": "proofPurpose", "type": "string"}, - {"name": "type", "type": "string"}, - { - "name": "verificationMethod", - "type": "string", - }, - ], - "@context": [ - {"name": "hash", "type": "string"}, - {"name": "provider", "type": "string"}, - ], - "Document": [ - {"name": "@context", "type": "string[]"}, - { - "name": "credentialSubject", - "type": "CredentialSubject", - }, - {"name": "expirationDate", "type": "string"}, - {"name": "issuanceDate", "type": "string"}, - {"name": "issuer", "type": "string"}, - {"name": "proof", "type": "Proof"}, - {"name": "type", "type": "string[]"}, - ], - "EIP712Domain": [ - {"name": "name", "type": "string"} - ], - "CredentialSubject": [ - {"name": "@context", "type": "@context"}, - {"name": "hash", "type": "string"}, - {"name": "id", "type": "string"}, - {"name": "provider", "type": "string"}, - ], - }, - "domain": {"name": "VerifiableCredential"}, - "primaryType": "Document", - }, - "proofPurpose": "assertionMethod", - "verificationMethod": "did:ethr:0xd6f8d6ca86aa01e551a311d670a0d1bd8577e5fb#controller", - }, - "issuer": "did:ethr:0xd6f8d6ca86aa01e551a311d670a0d1bd8577e5fb", - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://w3id.org/vc/status-list/2021/v1", - ], - "issuanceDate": "2024-03-12T14:28:53.876Z", "expirationDate": "2090-01-01T00:00:00.000Z", "credentialSubject": { - "id": "did:pkh:eip155:1:0x70997970C51812dc3A010C7d01b50e0d17dc79C8", - "hash": "v0.0.0:pQzBR3arZrlQXpJ6KRGxKEjhR03DyQ05ois9EmRNrAQ=", - "@context": { - "hash": "https://schema.org/Text", - "provider": "https://schema.org/Text", - }, "provider": "Linkedin", }, }, }, { - "version": "1.0.0", "credential": { - "type": ["VerifiableCredential"], - "proof": { - "type": "EthereumEip712Signature2021", - "created": "2024-03-12T14:24:07.018Z", - "@context": "https://w3id.org/security/suites/eip712sig-2021/v1", - "proofValue": "0x2547250aca7112a8488eb45a62dfabc8f5f6e4ecc1bf24f8e28839ce1ff7e786496cf5eb5ffb9eaa27bbcf58ecd66bc966d20844b7b5a7666d4fbbc38f609b641c", - "eip712Domain": { - "types": { - "Proof": [ - {"name": "@context", "type": "string"}, - {"name": "created", "type": "string"}, - {"name": "proofPurpose", "type": "string"}, - {"name": "type", "type": "string"}, - { - "name": "verificationMethod", - "type": "string", - }, - ], - "@context": [ - {"name": "hash", "type": "string"}, - {"name": "provider", "type": "string"}, - ], - "Document": [ - {"name": "@context", "type": "string[]"}, - { - "name": "credentialSubject", - "type": "CredentialSubject", - }, - {"name": "expirationDate", "type": "string"}, - {"name": "issuanceDate", "type": "string"}, - {"name": "issuer", "type": "string"}, - {"name": "proof", "type": "Proof"}, - {"name": "type", "type": "string[]"}, - ], - "EIP712Domain": [ - {"name": "name", "type": "string"} - ], - "CredentialSubject": [ - {"name": "@context", "type": "@context"}, - {"name": "hash", "type": "string"}, - {"name": "id", "type": "string"}, - {"name": "provider", "type": "string"}, - ], - }, - "domain": {"name": "VerifiableCredential"}, - "primaryType": "Document", - }, - "proofPurpose": "assertionMethod", - "verificationMethod": "did:ethr:0xd6f8d6ca86aa01e551a311d670a0d1bd8577e5fb#controller", - }, - "issuer": "did:ethr:0xd6f8d6ca86aa01e551a311d670a0d1bd8577e5fb", - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://w3id.org/vc/status-list/2021/v1", - ], - "issuanceDate": "2024-03-12T14:24:07.018Z", "expirationDate": "2099-01-01T00:00:00.000Z", "credentialSubject": { - "id": "did:pkh:eip155:1:0x70997970C51812dc3A010C7d01b50e0d17dc79C8", - "hash": "v0.0.0:PM/AuRacZWQ3McP8Dr6Ux+yb8PcVjeS7rlVcc6ry/2Q=", - "@context": { - "hash": "https://schema.org/Text", - "provider": "https://schema.org/Text", - }, "provider": "Discord", }, }, }, { - "version": "1.0.0", "credential": { - "type": ["VerifiableCredential"], - "proof": { - "type": "EthereumEip712Signature2021", - "created": "2024-03-12T14:24:07.018Z", - "@context": "https://w3id.org/security/suites/eip712sig-2021/v1", - "proofValue": "0x2547250aca7112a8488eb45a62dfabc8f5f6e4ecc1bf24f8e28839ce1ff7e786496cf5eb5ffb9eaa27bbcf58ecd66bc966d20844b7b5a7666d4fbbc38f609b641c", - "eip712Domain": { - "types": { - "Proof": [ - {"name": "@context", "type": "string"}, - {"name": "created", "type": "string"}, - {"name": "proofPurpose", "type": "string"}, - {"name": "type", "type": "string"}, - { - "name": "verificationMethod", - "type": "string", - }, - ], - "@context": [ - {"name": "hash", "type": "string"}, - {"name": "provider", "type": "string"}, - ], - "Document": [ - {"name": "@context", "type": "string[]"}, - { - "name": "credentialSubject", - "type": "CredentialSubject", - }, - {"name": "expirationDate", "type": "string"}, - {"name": "issuanceDate", "type": "string"}, - {"name": "issuer", "type": "string"}, - {"name": "proof", "type": "Proof"}, - {"name": "type", "type": "string[]"}, - ], - "EIP712Domain": [ - {"name": "name", "type": "string"} - ], - "CredentialSubject": [ - {"name": "@context", "type": "@context"}, - {"name": "hash", "type": "string"}, - {"name": "id", "type": "string"}, - {"name": "provider", "type": "string"}, - ], - }, - "domain": {"name": "VerifiableCredential"}, - "primaryType": "Document", - }, - "proofPurpose": "assertionMethod", - "verificationMethod": "did:ethr:0xd6f8d6ca86aa01e551a311d670a0d1bd8577e5fb#controller", - }, - "issuer": "did:ethr:0xd6f8d6ca86aa01e551a311d670a0d1bd8577e5fb", - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://w3id.org/vc/status-list/2021/v1", - ], - "issuanceDate": "2024-03-12T14:24:07.018Z", "expirationDate": "2024-06-10T14:24:07.018Z", "credentialSubject": { - "id": "did:pkh:eip155:1:0x70997970C51812dc3A010C7d01b50e0d17dc79C8", - "hash": "v0.0.0:PM/AuRacZWQ3McP8Dr6Ux+yb8PcVjeS7rlVcc6ry/2Q=", - "@context": { - "hash": "https://schema.org/Text", - "provider": "https://schema.org/Text", - }, "provider": "Discord", }, }, @@ -383,7 +208,6 @@ def mock_gitcoin_passport_fetch_stamps(*args, **kwargs): return { "items": [ { - "version": "1.0.0", "credential": { "expirationDate": "2099-09-22T15:04:05.073Z", "credentialSubject": {"provider": "AllowList#OctantEpochTwo"}, @@ -391,6 +215,24 @@ def mock_gitcoin_passport_fetch_stamps(*args, **kwargs): } ] } + # GTC staker, Carol + elif args[0] == "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC": + return { + "items": [ + { + "credential": { + "expirationDate": "2099-09-22T15:04:05.073Z", + "credentialSubject": {"provider": "Discord"}, + } + }, + { + "credential": { + "expirationDate": "2099-09-22T15:04:05.073Z", + "credentialSubject": {"provider": "TrustedCitizen"}, + }, + }, + ] + } else: return {"next": None, "prev": None, "items": []} @@ -703,7 +545,6 @@ def wait_for_sync(self, target, timeout_s=20, check_interval=0.5): timeout = datetime.timedelta(seconds=timeout_s) start = datetime.datetime.now() while True: - res = {} try: res, status_code = self.sync_status() current_app.logger.debug(f"sync_status returns {res}") @@ -804,6 +645,64 @@ def get_rewards_budget(self, address: str, epoch: int): rv = self._flask_client.get(f"/rewards/budget/{address}/epoch/{epoch}") return json.loads(rv.text) + def get_user_rewards_in_upcoming_epoch(self, address: str): + rv = self._flask_client.get(f"/rewards/budget/{address}/upcoming") + current_app.logger.debug( + "get_user_rewards_in_upcoming_epoch :", + self._flask_client.get(f"/rewards/budget/{address}/upcoming").request, + ) + return json.loads(rv.text) + + def get_user_rewards_in_epoch(self, address: str, epoch: int): + rv = self._flask_client.get(f"/rewards/budget/{address}/epoch/{epoch}") + current_app.logger.debug( + "get_rewards_budget :", + self._flask_client.get(f"/rewards/budget/{address}/epoch/{epoch}").request, + ) + return json.loads(rv.text) + + def get_total_users_rewards_in_epoch(self, epoch): + rv = self._flask_client.get(f"/rewards/budgets/epoch/{epoch}") + return json.loads(rv.text) + + def get_estimated_budget_by_days(self, number_of_days, amount): + rv = self._flask_client.post( + "/rewards/estimated_budget/by_days", + json={"days": number_of_days, "glmAmount": amount}, + ) + return json.loads(rv.text) + + def get_estimated_budget_by_epochs(self, number_of_epochs, amount): + rv = self._flask_client.post( + "/rewards/estimated_budget", + json={"numberOfEpochs": number_of_epochs, "glmAmount": amount}, + ) + return json.loads(rv.text) + + def get_rewards_leverage(self, epoch): + rv = self._flask_client.get(f"/rewards/leverage/{epoch}") + return json.loads(rv.text) + + def get_rewards_merkle_tree(self, epoch): + rv = self._flask_client.get(f"/rewards/merkle_tree/{epoch}") + return json.loads(rv.text) + + def get_projects_with_matched_rewards_in_epoch(self, epoch): + rv = self._flask_client.get(f"/rewards/projects/epoch/{epoch}") + return json.loads(rv.text), rv.status_code + + def get_estimated_projects_rewards(self): + rv = self._flask_client.get("/rewards/projects/estimated") + return json.loads(rv.text) + + def get_proposals_treshold_in_epoch(self, epoch) -> tuple[dict, int]: + rv = self._flask_client.get(f"/rewards/threshold/{epoch}") + return json.loads(rv.text), rv.status_code + + def get_unused_rewards(self, epoch): + rv = self._flask_client.get(f"/rewards/unused/{epoch}") + return json.loads(rv.text) + def get_withdrawals_for_address(self, address: str): rv = self._flask_client.get(f"/withdrawals/{address}").text return json.loads(rv) @@ -901,6 +800,18 @@ def accept_tos(self, user_address, signature): ) return json.loads(rv.text), rv.status_code + def get_user_history(self, user_address: str) -> tuple[dict, int]: + rv = self._flask_client.get(f"/history/{user_address}") + return json.loads(rv.text), rv.status_code + + def get_user_uq(self, user_address: str, epoch: int) -> tuple[dict, int]: + rv = self._flask_client.get(f"/user/{user_address}/uq/{epoch}") + return json.loads(rv.text), rv.status_code + + def get_all_uqs(self, epoch: int) -> tuple[dict, int]: + rv = self._flask_client.get(f"user/uq/{epoch}/all") + return json.loads(rv.text), rv.status_code + def get_antisybil_score(self, user_address: str) -> (any, int): rv = self._flask_client.get(f"/user/{user_address}/antisybil-status") return json.loads(rv.text), rv.status_code @@ -909,6 +820,59 @@ def refresh_antisybil_score(self, user_address: str) -> (str | None, int): rv = self._flask_client.put(f"/user/{user_address}/antisybil-status") return rv.text, rv.status_code + def get_chain_info(self) -> tuple[dict, int]: + rv = self._flask_client.get("/info/chain-info") + return json.loads(rv.text), rv.status_code + + def get_version(self) -> tuple[dict, int]: + rv = self._flask_client.get("/info/version") + return json.loads(rv.text), rv.status_code + + def get_healthcheck(self) -> tuple[dict, int]: + rv = self._flask_client.get("/info/healthcheck") + return json.loads(rv.text), rv.status_code + + def check_delegation(self, *addresses) -> tuple[dict, int]: + addresses = ",".join(addresses) + rv = self._flask_client.get(f"/delegation/check/{addresses}") + return json.loads(rv.text), rv.status_code + + def delegate( + self, + primary_address: str, + secondary_address: str, + primary_address_signature: str, + secondary_address_signature: str, + ) -> tuple[dict, int]: + rv = self._flask_client.post( + "/delegation/delegate", + json={ + "primaryAddr": primary_address, + "secondaryAddr": secondary_address, + "primaryAddrSignature": primary_address_signature, + "secondaryAddrSignature": secondary_address_signature, + }, + ) + return json.loads(rv.text), rv.status_code + + def delegation_recalculate( + self, + primary_address: str, + secondary_address: str, + primary_address_signature: str, + secondary_address_signature: str, + ) -> tuple[dict, int]: + rv = self._flask_client.put( + "/delegation/recalculate", + json={ + "primaryAddr": primary_address, + "secondaryAddr": secondary_address, + "primaryAddrSignature": primary_address_signature, + "secondaryAddrSignature": secondary_address_signature, + }, + ) + return json.loads(rv.text), rv.status_code + @property def config(self): return self._flask_client.application.config @@ -1249,10 +1213,14 @@ def mock_users_db(app, user_accounts): @pytest.fixture(scope="function") def mock_pending_epoch_snapshot_db_since_epoch3( - app, mock_users_db, ppf=PPF, cf=COMMUNITY_FUND + app, + mock_users_db, + ppf=PPF, + cf=COMMUNITY_FUND, + epoch=MOCKED_EPOCH_NO_AFTER_OVERHAUL, ): create_pending_snapshot( - epoch_nr=MOCKED_EPOCH_NO_AFTER_OVERHAUL, + epoch_nr=epoch, mock_users_db=mock_users_db, optional_ppf=ppf, optional_cf=cf, @@ -1280,22 +1248,9 @@ def mock_finalized_epoch_snapshot_db_since_epoch3(app, user_accounts): @pytest.fixture(scope="function") -def mock_finalized_epoch_snapshot_db(app, user_accounts): - database.finalized_epoch_snapshot.save_snapshot( - MOCKED_FINALIZED_EPOCH_NO, - MATCHED_REWARDS, - NO_PATRONS_REWARDS, - LEFTOVER, - total_withdrawals=TOTAL_WITHDRAWALS, - ) - - db.session.commit() - - -@pytest.fixture(scope="function") -def mock_allocations_db(app, mock_users_db, project_accounts): - prev_epoch_context = get_context(MOCKED_PENDING_EPOCH_NO - 1) - pending_epoch_context = get_context(MOCKED_PENDING_EPOCH_NO) +def mock_allocations_db(mock_users_db, project_accounts, epoch=MOCKED_PENDING_EPOCH_NO): + prev_epoch_context = get_context(epoch - 1) + pending_epoch_context = get_context(epoch) user1, user2, _ = mock_users_db user1_allocations = [ diff --git a/backend/tests/helpers/constants.py b/backend/tests/helpers/constants.py index b16ea4dfd9..d38f05ab40 100644 --- a/backend/tests/helpers/constants.py +++ b/backend/tests/helpers/constants.py @@ -7,6 +7,7 @@ MOCKED_PENDING_EPOCH_NO = 1 MOCKED_FINALIZED_EPOCH_NO = 1 MOCKED_EPOCH_NO_AFTER_OVERHAUL = 3 +MOCKED_EPOCH_NO_WITH_CAPPED_MR = 4 MOCKED_CURRENT_EPOCH_NO = 2 NO_PATRONS_REWARDS = 0 ETH_PROCEEDS = 402_410958904_110000000 @@ -31,6 +32,13 @@ ETH_PROCEEDS, LOCKED_RATIO, USER2_BUDGET ) COMMUNITY_FUND = int(Decimal("0.05") * ETH_PROCEEDS) +LEFTOVER_WITH_PPF_UNUSED_MR = ( + ETH_PROCEEDS + - OPERATIONAL_COST + - COMMUNITY_FUND + - int(0.5 * PPF) + - TOTAL_WITHDRAWALS +) USER1_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" USER2_ADDRESS = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" @@ -58,8 +66,11 @@ MULTISIG_ADDRESS = "0xa40FcB633d0A6c0d27aA9367047635Ff656229B0" MR_FUNDING_CAP_PERCENT = Decimal("0.2") -MAX_UQ_SCORE = Decimal("1") -LOW_UQ_SCORE = Decimal("0.2") +MAX_UQ_SCORE = Decimal("1.0") +LOW_UQ_SCORE = Decimal("0.01") +NULLIFIED_UQ_SCORE = Decimal("0.0") UQ_THRESHOLD_NOT_MAINNET = 5 -UQ_THRESHOLD_MAINNET = 20 +UQ_THRESHOLD_MAINNET = 15 + +TIMEOUT_LIST = set() diff --git a/backend/tests/helpers/finalized_snapshots.py b/backend/tests/helpers/finalized_snapshots.py new file mode 100644 index 0000000000..aee99bc3e3 --- /dev/null +++ b/backend/tests/helpers/finalized_snapshots.py @@ -0,0 +1,18 @@ +from app.extensions import db +from app.infrastructure import database +from tests.helpers.constants import ( + NO_PATRONS_REWARDS, + TOTAL_WITHDRAWALS, +) + + +def create_finalized_snapshot(epoch_nr: int, matched_rewards: int, leftover: int): + database.finalized_epoch_snapshot.save_snapshot( + epoch_nr, + matched_rewards, + NO_PATRONS_REWARDS, + leftover, + total_withdrawals=TOTAL_WITHDRAWALS, + ) + + db.session.commit() diff --git a/backend/tests/modules/modules_factory/test_modules_factory.py b/backend/tests/modules/modules_factory/test_modules_factory.py index faf3452858..d2439af905 100644 --- a/backend/tests/modules/modules_factory/test_modules_factory.py +++ b/backend/tests/modules/modules_factory/test_modules_factory.py @@ -52,7 +52,7 @@ from app.shared.blockchain_types import ChainTypes from app.modules.user.budgets.service.upcoming import UpcomingUserBudgets from app.modules.snapshots.pending.service.simulated import SimulatedPendingSnapshots -from tests.helpers.constants import UQ_THRESHOLD_MAINNET +from tests.helpers.constants import UQ_THRESHOLD_MAINNET, TIMEOUT_LIST def test_future_services_factory(): @@ -141,7 +141,7 @@ def test_pending_services_factory(): saved_user_budgets = SavedUserBudgets() user_nonce = SavedUserAllocationsNonce() uniqueness_quotients = PreliminaryUQ( - antisybil=GitcoinPassportAntisybil(), + antisybil=GitcoinPassportAntisybil(timeout_list=TIMEOUT_LIST), budgets=saved_user_budgets, uq_threshold=UQ_THRESHOLD_MAINNET, ) diff --git a/backend/tests/modules/octant_rewards/conftest.py b/backend/tests/modules/octant_rewards/conftest.py new file mode 100644 index 0000000000..8895ce9fe2 --- /dev/null +++ b/backend/tests/modules/octant_rewards/conftest.py @@ -0,0 +1,42 @@ +import pytest + +from tests.helpers.constants import ( + MOCKED_EPOCH_NO_WITH_CAPPED_MR, + COMMUNITY_FUND, + PPF, + MOCKED_FINALIZED_EPOCH_NO, + MATCHED_REWARDS_AFTER_OVERHAUL, + MATCHED_REWARDS, + LEFTOVER, + LEFTOVER_WITH_PPF_UNUSED_MR, +) +from tests.helpers.finalized_snapshots import create_finalized_snapshot +from tests.helpers.pending_snapshot import create_pending_snapshot + + +@pytest.fixture(scope="function") +def mock_pending_epoch_snapshot_with_uq_scores( + mock_users_db_with_scores, ppf=PPF, cf=COMMUNITY_FUND +): + user1_, user2, user3 = mock_users_db_with_scores + create_pending_snapshot( + epoch_nr=MOCKED_EPOCH_NO_WITH_CAPPED_MR, + optional_ppf=ppf, + optional_cf=cf, + mock_users_db=mock_users_db_with_scores, + ) + return user1_, user2, user3 + + +@pytest.fixture(scope="function") +def mock_finalized_epoch_snapshot_db(app, user_accounts): + create_finalized_snapshot(MOCKED_FINALIZED_EPOCH_NO, MATCHED_REWARDS, LEFTOVER) + + +@pytest.fixture(scope="function") +def mock_finalized_epoch_snapshot_db_for_e4(app, user_accounts): + create_finalized_snapshot( + MOCKED_EPOCH_NO_WITH_CAPPED_MR, + MATCHED_REWARDS_AFTER_OVERHAUL, + LEFTOVER_WITH_PPF_UNUSED_MR, + ) diff --git a/backend/tests/modules/octant_rewards/helpers/checker.py b/backend/tests/modules/octant_rewards/helpers/checker.py index 28450dea6b..d090fbec5e 100644 --- a/backend/tests/modules/octant_rewards/helpers/checker.py +++ b/backend/tests/modules/octant_rewards/helpers/checker.py @@ -16,6 +16,7 @@ def check_octant_rewards( matched_rewards: int = None, total_withdrawals: int = None, patrons_rewards: int = None, + donated_to_projects: int = None, ): assert rewards.staking_proceeds == ETH_PROCEEDS assert rewards.locked_ratio == LOCKED_RATIO @@ -29,3 +30,4 @@ def check_octant_rewards( assert rewards.leftover == leftover assert rewards.community_fund == community_fund assert rewards.ppf == ppf + assert rewards.donated_to_projects == donated_to_projects diff --git a/backend/tests/modules/octant_rewards/test_apr_core.py b/backend/tests/modules/octant_rewards/test_apr_core.py new file mode 100644 index 0000000000..4b27c5b0c1 --- /dev/null +++ b/backend/tests/modules/octant_rewards/test_apr_core.py @@ -0,0 +1,17 @@ +import pytest + +from app.modules.octant_rewards.core import get_rewards_rate +from app.modules.staking.proceeds.core import ESTIMATED_STAKING_REWARDS_RATE + + +@pytest.fixture(autouse=True) +def before(app): + pass + + +@pytest.mark.parametrize("epoch_num", [1, 2, 3, 4, 5]) +def test_get_epoch_rewards_rate_return_valid_value(epoch_num: int): + actual_result = get_rewards_rate(epoch_num) + expected_result = ESTIMATED_STAKING_REWARDS_RATE + + assert actual_result == expected_result diff --git a/backend/tests/modules/octant_rewards/test_calculated_octant_rewards.py b/backend/tests/modules/octant_rewards/test_calculated_octant_rewards.py index 0a174fbdcc..69200be707 100644 --- a/backend/tests/modules/octant_rewards/test_calculated_octant_rewards.py +++ b/backend/tests/modules/octant_rewards/test_calculated_octant_rewards.py @@ -41,6 +41,7 @@ def test_calculate_octant_rewards_before_overhaul( assert result.total_rewards == expected_tr assert result.vanilla_individual_rewards == expected_ir assert result.operational_cost == expected_operational_cost + assert result.donated_to_projects is None def test_calculate_octant_rewards_after_overhaul( @@ -69,3 +70,4 @@ def test_calculate_octant_rewards_after_overhaul( assert result.ppf == overhaul_formulas.ppf( result.staking_proceeds, result.vanilla_individual_rewards, LOCKED_RATIO ) + assert result.donated_to_projects is None diff --git a/backend/tests/modules/octant_rewards/test_finalized_octant_rewards.py b/backend/tests/modules/octant_rewards/test_finalized_octant_rewards.py index c1b48079f2..2b4a94690b 100644 --- a/backend/tests/modules/octant_rewards/test_finalized_octant_rewards.py +++ b/backend/tests/modules/octant_rewards/test_finalized_octant_rewards.py @@ -31,6 +31,7 @@ def test_finalized_octant_rewards_before_overhaul( matched_rewards=MATCHED_REWARDS, total_withdrawals=TOTAL_WITHDRAWALS, patrons_rewards=NO_PATRONS_REWARDS, + donated_to_projects=MATCHED_REWARDS, ) @@ -51,6 +52,7 @@ def test_finalized_octant_rewards_after_overhaul( matched_rewards=MATCHED_REWARDS_AFTER_OVERHAUL, total_withdrawals=TOTAL_WITHDRAWALS, patrons_rewards=NO_PATRONS_REWARDS, + donated_to_projects=MATCHED_REWARDS_AFTER_OVERHAUL, ) @@ -70,3 +72,13 @@ def test_finalized_get_leverage( result = service.get_leverage(context) assert result == 144160.63189897747 + + +def test_donated_to_projects_in_octant_rewards_for_capped_mr( + mock_pending_epoch_snapshot_with_uq_scores, mock_finalized_epoch_snapshot_db_for_e4 +): + context = get_context(epoch_num=4) + service = FinalizedOctantRewards() + result = service.get_octant_rewards(context) + + assert result.donated_to_projects == 140849434135859019815 diff --git a/backend/tests/modules/octant_rewards/test_pending_octant_rewards.py b/backend/tests/modules/octant_rewards/test_pending_octant_rewards.py index 2f2e2c5ee2..f0aec7ccce 100644 --- a/backend/tests/modules/octant_rewards/test_pending_octant_rewards.py +++ b/backend/tests/modules/octant_rewards/test_pending_octant_rewards.py @@ -4,6 +4,7 @@ from app.modules.octant_rewards.general.service.pending import PendingOctantRewards from app.modules.octant_rewards.matched.pending import PendingOctantMatchedRewards from app.modules.projects.rewards.service.finalizing import FinalizingProjectRewards +from tests.helpers import make_user_allocation from tests.helpers.constants import ( USER1_BUDGET, MOCKED_EPOCH_NO_AFTER_OVERHAUL, @@ -13,7 +14,6 @@ MATCHED_REWARDS, MATCHED_REWARDS_AFTER_OVERHAUL, ) -from tests.helpers import make_user_allocation from tests.helpers.context import get_context from tests.helpers.pending_snapshot import create_pending_snapshot from tests.modules.octant_rewards.helpers.checker import check_octant_rewards @@ -43,6 +43,7 @@ def test_pending_octant_rewards_before_overhaul( patrons_rewards=USER2_BUDGET, matched_rewards=MATCHED_REWARDS + USER2_BUDGET, leftover=321928766823288000000, + donated_to_projects=0, ) @@ -60,6 +61,7 @@ def test_pending_octant_rewards_after_overhaul( patrons_rewards=USER2_BUDGET, matched_rewards=MATCHED_REWARDS_AFTER_OVERHAUL, leftover=282293485473756640672, + donated_to_projects=0, ) @@ -117,3 +119,28 @@ def test_pending_get_leverage( result = service.get_leverage(context) assert result == 144164.29856550877 + + +def test_donated_to_projects_in_octant_rewards_for_capped_mr( + mock_pending_epoch_snapshot_with_uq_scores, service +): + user1, _, _ = mock_pending_epoch_snapshot_with_uq_scores + context = get_context(epoch_num=4) + make_user_allocation( + context, + user1, + allocation_items=[ + AllocationItem(context.projects_details.projects[0], USER1_BUDGET) + ], + ) + result = service.get_octant_rewards(context) + + check_octant_rewards( + result, + community_fund=COMMUNITY_FUND, + ppf=PPF, + patrons_rewards=USER2_BUDGET, + matched_rewards=MATCHED_REWARDS_AFTER_OVERHAUL, + leftover=366801619086282814574, + donated_to_projects=28171413696161041950, + ) diff --git a/backend/tests/modules/projects/conftest.py b/backend/tests/modules/projects/conftest.py new file mode 100644 index 0000000000..6ac250455e --- /dev/null +++ b/backend/tests/modules/projects/conftest.py @@ -0,0 +1,11 @@ +import pytest + +from tests.modules.projects.helpers import sample_projects_details + + +@pytest.fixture(scope="function") +def patch_projects_details(monkeypatch): + monkeypatch.setattr( + "app.modules.projects.details.service.projects_details.get_projects_details_for_epoch", + sample_projects_details, + ) diff --git a/backend/tests/modules/projects/details/test_filtering_core.py b/backend/tests/modules/projects/details/test_filtering_core.py new file mode 100644 index 0000000000..26fc1f7e6c --- /dev/null +++ b/backend/tests/modules/projects/details/test_filtering_core.py @@ -0,0 +1,71 @@ +from app.modules.projects.details.core import filter_projects_details +from tests.modules.projects.helpers import sample_projects_details + +SAMPLE_PROJECTS_DETAILS = sample_projects_details() + + +def test_filter_projects_partial_match_in_name(): + """Test that partial matches in the project name are correctly filtered.""" + search_phrase = "Octant" + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == 2 + assert filtered_projects[0].name == "OctantProject3" + assert filtered_projects[1].name == "OctantTestProject4" + + +def test_filter_projects_partial_match_in_address(): + """Test that partial matches in the project address are correctly filtered.""" + search_phrase = "0x111" + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == 1 + assert filtered_projects[0].address == "0x111" + assert filtered_projects[0].name == "TEST1" + + +def test_filter_projects_empty_search_phrase(): + """Test that all projects are returned when search phrase is empty.""" + search_phrase = "" + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == len(SAMPLE_PROJECTS_DETAILS) + assert all(project in filtered_projects for project in SAMPLE_PROJECTS_DETAILS) + + +def test_filter_projects_special_characters_in_search_phrase(): + """Test that search phrases with special characters work correctly.""" + search_phrase = "0x444" + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == 1 + assert filtered_projects[0].address == "0x444" + assert filtered_projects[0].name == "OctantTestProject4" + + +def test_filter_projects_search_phrase_not_in_name_or_address(): + """Test that no projects are returned when search phrase doesn't match anything.""" + search_phrase = "NonExistentProject" + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == 0 + + +def test_filter_projects_multiple_matches(): + """Test that multiple projects are returned when search phrase matches multiple names/addresses.""" + search_phrase = "Project" + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == 2 + assert filtered_projects[0].name == "OctantProject3" + assert filtered_projects[1].name == "OctantTestProject4" + + +def test_filter_projects_whitespace_in_search_phrase(): + """Test that leading/trailing whitespace in search phrase is handled correctly.""" + search_phrase = " TEST1 " + filtered_projects = filter_projects_details(SAMPLE_PROJECTS_DETAILS, search_phrase) + + assert len(filtered_projects) == 1 + assert filtered_projects[0].name == "TEST1" + assert filtered_projects[0].address == "0x111" diff --git a/backend/tests/modules/projects/details/test_projects_filtering.py b/backend/tests/modules/projects/details/test_projects_filtering.py new file mode 100644 index 0000000000..71e9f28e70 --- /dev/null +++ b/backend/tests/modules/projects/details/test_projects_filtering.py @@ -0,0 +1,37 @@ +import pytest + +from app.modules.projects.details.service.projects_details import ( + StaticProjectsDetailsService, + ProjectsDetailsDTO, +) +from tests.helpers.context import get_context + + +@pytest.fixture(autouse=True) +def before(app, patch_projects_details): + pass + + +@pytest.mark.parametrize("search_phrase", ["Octant", "TEST", "AnyName"]) +def test_get_projects_details_by_search_phrase(search_phrase): + epoch = 4 + context = get_context(epoch) + service = StaticProjectsDetailsService() + projects_details: ProjectsDetailsDTO = ( + service.get_projects_details_by_search_phrase(context, search_phrase) + ) + + for project in projects_details.projects_details: + assert search_phrase.lower() in project["name"].lower() + assert project["epoch"] == str(epoch) + assert "address" in project and project["address"] is not None + + +def test_get_projects_details_by_search_phrase_not_found(): + context = get_context(4) + service = StaticProjectsDetailsService() + projects_details: ProjectsDetailsDTO = ( + service.get_projects_details_by_search_phrase(context, "NOT_FOUND") + ) + + assert projects_details.projects_details == [] diff --git a/backend/tests/modules/projects/helpers.py b/backend/tests/modules/projects/helpers.py new file mode 100644 index 0000000000..8ed3b97201 --- /dev/null +++ b/backend/tests/modules/projects/helpers.py @@ -0,0 +1,12 @@ +from typing import List + +from app.infrastructure.database.models import ProjectsDetails + + +def sample_projects_details(*args, **kwargs) -> List[ProjectsDetails]: + return [ + ProjectsDetails(id=1, address="0x111", name="TEST1", epoch=4), + ProjectsDetails(id=2, address="0x222", name="AnyName2", epoch=4), + ProjectsDetails(id=3, address="0x333", name="OctantProject3", epoch=4), + ProjectsDetails(id=4, address="0x444", name="OctantTestProject4", epoch=4), + ] diff --git a/backend/tests/modules/projects/metadata/test_projects_retrieval.py b/backend/tests/modules/projects/metadata/test_projects_retrieval.py index 9f91b87041..dc585372f0 100644 --- a/backend/tests/modules/projects/metadata/test_projects_retrieval.py +++ b/backend/tests/modules/projects/metadata/test_projects_retrieval.py @@ -14,7 +14,6 @@ def before(app, patch_projects): MOCK_PROJECTS.get_project_cid.return_value = ( "QmXbFKrMGJUbXupmTQsQhoy9zkzXDBHZkPAzKC4yiaLt5n" ) - pass def test_get_projects_metadata_epoch_1(): diff --git a/backend/tests/modules/snapshots/finalized/test_finalizing_snapshots.py b/backend/tests/modules/snapshots/finalized/test_finalizing_snapshots.py index 9129dd996f..fbccc322d8 100644 --- a/backend/tests/modules/snapshots/finalized/test_finalizing_snapshots.py +++ b/backend/tests/modules/snapshots/finalized/test_finalizing_snapshots.py @@ -8,7 +8,11 @@ from app.modules.snapshots.finalized.service.finalizing import FinalizingSnapshots from tests.helpers import make_user_allocation from tests.helpers.allocations import make_user_allocation_with_uq_score -from tests.helpers.constants import MATCHED_REWARDS, USER2_BUDGET, LOW_UQ_SCORE +from tests.helpers.constants import ( + MATCHED_REWARDS, + USER2_BUDGET, + MR_FUNDING_CAP_PERCENT, +) from tests.helpers.context import get_context @@ -84,8 +88,8 @@ def test_create_finalized_snapshots_with_rewards_and_user_uq_score( assert rewards[0].amount == str(200_000000000) assert rewards[0].matched is None assert rewards[1].address == projects[0] - assert rewards[1].amount == str(int(LOW_UQ_SCORE * MATCHED_REWARDS + 100)) - assert rewards[1].matched == str(int(LOW_UQ_SCORE * MATCHED_REWARDS)) + assert rewards[1].amount == str(int(MR_FUNDING_CAP_PERCENT * MATCHED_REWARDS + 100)) + assert rewards[1].matched == str(int(MR_FUNDING_CAP_PERCENT * MATCHED_REWARDS)) assert rewards[2].address == alice.address assert rewards[2].amount == str(100_000000000) assert rewards[2].matched is None @@ -93,7 +97,7 @@ def test_create_finalized_snapshots_with_rewards_and_user_uq_score( snapshot = database.finalized_epoch_snapshot.get_by_epoch_num(result) assert snapshot.matched_rewards == str(MATCHED_REWARDS) assert snapshot.total_withdrawals == str( - int(LOW_UQ_SCORE * MATCHED_REWARDS) + 100 + 300_000000000 + int(MR_FUNDING_CAP_PERCENT * MATCHED_REWARDS) + 100 + 300_000000000 ) assert snapshot.patrons_rewards == str(USER2_BUDGET) assert snapshot.leftover == str(414362124463057389617) diff --git a/backend/tests/modules/uq/conftest.py b/backend/tests/modules/uq/conftest.py index 1a46984a88..668c85a7ee 100644 --- a/backend/tests/modules/uq/conftest.py +++ b/backend/tests/modules/uq/conftest.py @@ -4,13 +4,16 @@ import pytest from app.modules.uq.service.preliminary import PreliminaryUQ +from app.modules.user.antisybil.dto import AntisybilStatusDTO from tests.helpers.constants import UQ_THRESHOLD_MAINNET @pytest.fixture def mock_antisybil(): mock = Mock() - mock.get_antisybil_status.return_value = (10.0, datetime.now()) + mock.get_antisybil_status.return_value = AntisybilStatusDTO( + score=10.0, expires_at=datetime.now(), is_on_timeout_list=False + ) return mock diff --git a/backend/tests/modules/uq/test_preliminary_uq.py b/backend/tests/modules/uq/test_preliminary_uq.py index 69bc81c767..a628c68f47 100644 --- a/backend/tests/modules/uq/test_preliminary_uq.py +++ b/backend/tests/modules/uq/test_preliminary_uq.py @@ -7,8 +7,9 @@ from app.extensions import db from app.infrastructure import database from app.modules.uq import core +from app.modules.user.antisybil.dto import AntisybilStatusDTO from tests.helpers.allocations import mock_request -from tests.helpers.constants import USER1_ADDRESS, USER2_ADDRESS +from tests.helpers.constants import USER1_ADDRESS, USER2_ADDRESS, LOW_UQ_SCORE from tests.helpers.context import get_context @@ -18,14 +19,16 @@ def before(app): def test_calculate_uq_above_threshold(context, mock_antisybil, service): - mock_antisybil.get_antisybil_status.return_value = (20.0, datetime.now()) + mock_antisybil.get_antisybil_status.return_value = AntisybilStatusDTO( + score=20.0, expires_at=datetime.now(), is_on_timeout_list=False + ) result = service.calculate(context, USER1_ADDRESS) assert result == 1.0 def test_calculate_uq_below_threshold(context, service): result = service.calculate(context, USER1_ADDRESS) - assert result == Decimal("0.2") + assert result == LOW_UQ_SCORE def test_retrieve_uq_when_score_in_the_db(service, mock_users_db_with_scores): @@ -46,7 +49,7 @@ def test_retrieve_uq_when_score_calculated_dynamically(context, service, mock_us db.session.commit() result = service.retrieve(context, USER1_ADDRESS) - assert result == Decimal("0.2") + assert result == LOW_UQ_SCORE def test_get_all_user_uq_pairs(context, service, mock_users_db): @@ -60,4 +63,4 @@ def test_get_all_user_uq_pairs(context, service, mock_users_db): db.session.commit() result = core.get_all_uqs(1) - assert result == [(alice.address, Decimal("0.2")), (bob.address, Decimal("0.2"))] + assert result == [(alice.address, LOW_UQ_SCORE), (bob.address, LOW_UQ_SCORE)] diff --git a/backend/tests/modules/uq/test_uq_score.py b/backend/tests/modules/uq/test_uq_score.py index dcb0488841..2c653e1776 100644 --- a/backend/tests/modules/uq/test_uq_score.py +++ b/backend/tests/modules/uq/test_uq_score.py @@ -3,19 +3,35 @@ import pytest from app.modules.uq.core import calculate_uq -from tests.helpers.constants import UQ_THRESHOLD_MAINNET +from tests.helpers.constants import ( + UQ_THRESHOLD_MAINNET, + LOW_UQ_SCORE, + MAX_UQ_SCORE, + NULLIFIED_UQ_SCORE, +) @pytest.mark.parametrize( "gp_score, expected_output", [ - (1, 0.2), - (19, 0.2), - (20, 1.0), - (27, 1.0), + (1, LOW_UQ_SCORE), + (14, LOW_UQ_SCORE), + (15, MAX_UQ_SCORE), + (27, MAX_UQ_SCORE), ], ) def test_calculate_uq(gp_score, expected_output): assert calculate_uq(gp_score, uq_threshold=UQ_THRESHOLD_MAINNET) == Decimal( str(expected_output) ) + + +def test_calculate_uq_when_address_on_timeout_list(): + gp_score = 0 + + assert ( + calculate_uq( + gp_score, uq_threshold=UQ_THRESHOLD_MAINNET, is_on_timeout_list=True + ) + == NULLIFIED_UQ_SCORE + ) diff --git a/backend/tests/modules/user/allocations/pending/test_allocations.py b/backend/tests/modules/user/allocations/pending/test_allocations.py index 926fffcb57..e57caf5fc2 100644 --- a/backend/tests/modules/user/allocations/pending/test_allocations.py +++ b/backend/tests/modules/user/allocations/pending/test_allocations.py @@ -27,7 +27,7 @@ create_payload, deserialize_allocations, ) -from tests.helpers.constants import MATCHED_REWARDS +from tests.helpers.constants import MATCHED_REWARDS, LOW_UQ_SCORE from tests.helpers.context import get_context @@ -416,7 +416,7 @@ def test_uq_added_while_allocating(project_accounts, tos_users): ) uq_1 = get_uq_by_address(user_addr, context.epoch_details.epoch_num) - assert uq_1.score == "0.2" + assert uq_1.score == str(LOW_UQ_SCORE) # Allocate for the second time payload = create_payload(project_accounts[0:4], None, 1) diff --git a/backend/tests/modules/user/allocations/pending/test_allocations_epoch4.py b/backend/tests/modules/user/allocations/pending/test_allocations_epoch4.py index bcafe6adce..162ba7291f 100644 --- a/backend/tests/modules/user/allocations/pending/test_allocations_epoch4.py +++ b/backend/tests/modules/user/allocations/pending/test_allocations_epoch4.py @@ -11,7 +11,11 @@ PendingUserAllocationsVerifier, ) from tests.helpers.allocations import make_user_allocation_with_uq_score -from tests.helpers.constants import MATCHED_REWARDS, LOW_UQ_SCORE +from tests.helpers.constants import ( + MATCHED_REWARDS, + LOW_UQ_SCORE, + MR_FUNDING_CAP_PERCENT, +) from tests.helpers.context import get_context @@ -66,7 +70,9 @@ def test_simulate_allocation_with_user_uq_score(service, mock_users_db): ProjectRewardDTO(sorted_projects[0], 0, 0), ProjectRewardDTO(sorted_projects[1], 0, 0), ProjectRewardDTO( - sorted_projects[2], 200000000000, int(MATCHED_REWARDS * LOW_UQ_SCORE) + sorted_projects[2], + 200000000000, + int(MATCHED_REWARDS * MR_FUNDING_CAP_PERCENT), ), ProjectRewardDTO(sorted_projects[3], 0, 0), ProjectRewardDTO(sorted_projects[4], 0, 0), @@ -107,7 +113,9 @@ def test_simulate_allocation_user_uq_score_with_passed_param(service, mock_users ProjectRewardDTO(sorted_projects[0], 0, 0), ProjectRewardDTO(sorted_projects[1], 0, 0), ProjectRewardDTO( - sorted_projects[2], 200000000000, int(MATCHED_REWARDS * LOW_UQ_SCORE) + sorted_projects[2], + 200000000000, + int(MATCHED_REWARDS * MR_FUNDING_CAP_PERCENT), ), ProjectRewardDTO(sorted_projects[3], 0, 0), ProjectRewardDTO(sorted_projects[4], 0, 0), diff --git a/backend/tests/modules/user/antisybil/test_antisybil.py b/backend/tests/modules/user/antisybil/test_antisybil.py index 40a2f30838..cf371ae265 100644 --- a/backend/tests/modules/user/antisybil/test_antisybil.py +++ b/backend/tests/modules/user/antisybil/test_antisybil.py @@ -1,10 +1,13 @@ +from datetime import datetime + import pytest + from app import exceptions, db -from datetime import datetime from app.exceptions import UserNotFound from app.infrastructure import database from app.modules.common.delegation import get_hashed_addresses from app.modules.user.antisybil.service.initial import GitcoinPassportAntisybil +from tests.helpers.constants import TIMEOUT_LIST from tests.helpers.context import get_context @@ -21,7 +24,7 @@ def test_antisybil_service( ): context = get_context(4) - service = GitcoinPassportAntisybil() + service = GitcoinPassportAntisybil(timeout_list=TIMEOUT_LIST) unknown_address = "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720" try: @@ -39,10 +42,28 @@ def test_antisybil_service( service.update_antisybil_status(context, alice.address, score, expires_at, stamps) - score, _ = service.get_antisybil_status(context, alice.address) + result = service.get_antisybil_status(context, alice.address) + score = result.score assert score == 2.572 +def test_gtc_staking_stamp_nullification( + patch_gitcoin_passport_issue_address_for_scoring, + patch_gitcoin_passport_fetch_score, + patch_gitcoin_passport_fetch_stamps, + mock_users_db, +): + context = get_context(4) + service = GitcoinPassportAntisybil(timeout_list=TIMEOUT_LIST) + _, _, carol = mock_users_db + score, expires_at, stamps = service.fetch_antisybil_status(context, carol.address) + service.update_antisybil_status(context, carol.address, score, expires_at, stamps) + + result = service.get_antisybil_status(context, carol.address) + + assert result.score == 0.5 + + def test_guest_stamp_score_bump_for_both_gp_and_octant_side_application( patch_gitcoin_passport_issue_address_for_scoring, patch_gitcoin_passport_fetch_score, @@ -51,19 +72,21 @@ def test_guest_stamp_score_bump_for_both_gp_and_octant_side_application( ): context = get_context(4) - service = GitcoinPassportAntisybil() + service = GitcoinPassportAntisybil(timeout_list=TIMEOUT_LIST) alice, _, _ = mock_users_db score, expires_at, stamps = service.fetch_antisybil_status(context, alice.address) service.update_antisybil_status(context, alice.address, score, expires_at, stamps) - score, _ = service.get_antisybil_status(context, alice.address) + result = service.get_antisybil_status(context, alice.address) + score = result.score assert score == 2.572 # guest list score bonus not applied guest_address = "0x2f51E78ff8aeC6A941C4CEeeb26B4A1f03737c50" database.user.add_user(guest_address) score, expires_at, stamps = service.fetch_antisybil_status(context, guest_address) service.update_antisybil_status(context, guest_address, score, expires_at, stamps) - score, _ = service.get_antisybil_status(context, guest_address) + result = service.get_antisybil_status(context, guest_address) + score = result.score assert (not stamps) and ( score == 21.0 ) # is on guest list, no stamps, applying 21 score bonus manually @@ -72,7 +95,8 @@ def test_guest_stamp_score_bump_for_both_gp_and_octant_side_application( database.user.add_user(stamp_address) score, expires_at, stamps = service.fetch_antisybil_status(context, stamp_address) service.update_antisybil_status(context, stamp_address, score, expires_at, stamps) - score, _ = service.get_antisybil_status(context, stamp_address) + result = service.get_antisybil_status(context, stamp_address) + score = result.score assert (stamps) and ( score == 22.0 ) # is on guest list, HAS GUEST LIST STAMP, score is from fetch @@ -85,7 +109,7 @@ def test_antisybil_cant_be_update_when_address_is_delegated(alice, bob): database.score_delegation.save_delegation(primary, secondary, both) db.session.commit() - service = GitcoinPassportAntisybil() + service = GitcoinPassportAntisybil(timeout_list=TIMEOUT_LIST) with pytest.raises(exceptions.AddressAlreadyDelegated): service.update_antisybil_status( @@ -94,3 +118,25 @@ def test_antisybil_cant_be_update_when_address_is_delegated(alice, bob): with pytest.raises(exceptions.AddressAlreadyDelegated): service.update_antisybil_status(context, bob.address, score, datetime.now(), {}) + + +def test_antisybil_score_is_nullified_when_address_on_timeout_list( + patch_gitcoin_passport_issue_address_for_scoring, + patch_gitcoin_passport_fetch_score, + patch_gitcoin_passport_fetch_stamps, + mock_users_db, +): + context = get_context(4) + + service = GitcoinPassportAntisybil( + timeout_list={"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"} + ) + alice, _, _ = mock_users_db + timeout_address = alice.address + score, expires_at, stamps = service.fetch_antisybil_status(context, timeout_address) + service.update_antisybil_status(context, timeout_address, score, expires_at, stamps) + + result = service.get_antisybil_status(context, timeout_address) + + assert result.score == 0.0 + assert result.is_on_timeout_list is True diff --git a/ci/Dockerfile.contracts-v1 b/ci/Dockerfile.contracts-v1 index e9bca8497e..3da2b887b2 100644 --- a/ci/Dockerfile.contracts-v1 +++ b/ci/Dockerfile.contracts-v1 @@ -1,4 +1,4 @@ -FROM local-docker-registry.wildland.dev:80/library/node:16-alpine AS root +FROM local-docker-registry.wildland.dev:80/library/node:18-alpine AS root WORKDIR /app diff --git a/ci/Dockerfile.multideployer b/ci/Dockerfile.multideployer index cae93cd928..16ed6724fb 100644 --- a/ci/Dockerfile.multideployer +++ b/ci/Dockerfile.multideployer @@ -9,8 +9,10 @@ WORKDIR /app RUN mkdir /hardhat/ COPY --from=hardhat /app/ /hardhat/ -COPY --chmod=+x entrypoint.sh . -COPY --chmod=+x wait_for_subgraph.sh . +COPY entrypoint.sh . +RUN chmod +x ./entrypoint.sh +COPY wait_for_subgraph.sh . +RUN chmod +x ./wait_for_subgraph.sh COPY server.py /app/server.py ENTRYPOINT ["./entrypoint.sh"] diff --git a/ci/argocd/contracts/master.env b/ci/argocd/contracts/master.env index 6d3aa84242..8f1ebab5e0 100644 --- a/ci/argocd/contracts/master.env +++ b/ci/argocd/contracts/master.env @@ -1,8 +1,8 @@ -BLOCK_NUMBER=6153543 +BLOCK_NUMBER=6838320 GLM_CONTRACT_ADDRESS=0x71432DD1ae7DB41706ee6a22148446087BdD0906 -AUTH_CONTRACT_ADDRESS=0x738413d47E9670757D662497bEb38B69b26ddC4E -DEPOSITS_CONTRACT_ADDRESS=0x3Ba9caeAc79b784708DfdDF936F2aaAf9CF39884 -EPOCHS_CONTRACT_ADDRESS=0xb918ce1c1966208720C1F0F80767C534D227e164 -PROPOSALS_CONTRACT_ADDRESS=0x5454A1Fa39c16af307FDf0B2E9B3dbB97EcF98fD -WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0x0f9C752bdB7A4727dD21F44f1CE4dA6413517CcB -VAULT_CONTRACT_ADDRESS=0x7af367B58d851cE54DB86F907c380a0C98102685 +AUTH_CONTRACT_ADDRESS=0x685E5F403d23CDAA598008D7833D56F499043994 +DEPOSITS_CONTRACT_ADDRESS=0xF5F55c10B097471B6587DEb6744E78f726203722 +EPOCHS_CONTRACT_ADDRESS=0x5C29cC041cf93195D5619114fCde2b8734F6b84e +PROPOSALS_CONTRACT_ADDRESS=0xF6d7839CD9BF4d116B94006B0A2Fa15eF706bD14 +WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0x1045826d9a7768EE3c3415A080b60A679F435194 +VAULT_CONTRACT_ADDRESS=0x5F6D99694577A7cD9D0E27D81AdB062b2bFe3312 diff --git a/ci/argocd/contracts/uat.env b/ci/argocd/contracts/uat.env index 984dc43cd4..1c7452eaba 100644 --- a/ci/argocd/contracts/uat.env +++ b/ci/argocd/contracts/uat.env @@ -1,8 +1,8 @@ -BLOCK_NUMBER=6440834 +BLOCK_NUMBER=6793660 GLM_CONTRACT_ADDRESS=0x71432DD1ae7DB41706ee6a22148446087BdD0906 -AUTH_CONTRACT_ADDRESS=0xab594179cD009616fD183259F3b9d68270faA053 -DEPOSITS_CONTRACT_ADDRESS=0xe6E9332995F0469D33fa6296D0A432bD9fe8Db60 -EPOCHS_CONTRACT_ADDRESS=0x323F991FC3659507Ef5eA9385D72bF31cb468033 -PROPOSALS_CONTRACT_ADDRESS=0xead93af66E5C2228Cd047Cd70891405111fA3548 -WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0x077265B94EeF6c40B725c0426331bcc8DBFC7Ee0 -VAULT_CONTRACT_ADDRESS=0x0c6c38FB0e5222727389E6efe0E654bD5b9D4484 +AUTH_CONTRACT_ADDRESS=0xa49b0d8711BDe73C4c48371bE53B3a0Ba9efFDa0 +DEPOSITS_CONTRACT_ADDRESS=0x773B267Eb3BfdC0C00b6d5e6ED0AFC8dF164E69b +EPOCHS_CONTRACT_ADDRESS=0x227c5ad3cD9Ba8646e1584e822F88538037e85b7 +PROPOSALS_CONTRACT_ADDRESS=0x322FA3313CDDE6e021db71D6Ea8aaaF57fE219f2 +WITHDRAWALS_TARGET_CONTRACT_ADDRESS=0xc0720c9cC73497A142E7D0a4A05cFF2DAf82b3fc +VAULT_CONTRACT_ADDRESS=0xff21F0910F7A3b83D2D4d145C648A738c48d993F diff --git a/ci/argocd/templates/octant-application.yaml b/ci/argocd/templates/octant-application.yaml index a3e660d5ea..f5ffc85aa4 100644 --- a/ci/argocd/templates/octant-application.yaml +++ b/ci/argocd/templates/octant-application.yaml @@ -34,7 +34,7 @@ spec: - name: 'webClient.hideCurrentProjectsOutsideAW' value: 'false' - name: 'webClient.ipfsGateways' - value: 'https://ipfs.octant.wildland.dev/ipfs/' + value: '$IPFS_GATEWAYS' ## Graph Node - name: graphNode.graph.env.NETWORK value: '$NETWORK_NAME' diff --git a/client/cypress/e2e/_2makePendingSnapshot.cy.ts b/client/cypress/e2e/_2makePendingSnapshot.cy.ts deleted file mode 100644 index 9f23bc0da3..0000000000 --- a/client/cypress/e2e/_2makePendingSnapshot.cy.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import { mutateAsyncMakeSnapshot } from 'cypress/utils/moveTime'; -import { - HAS_ONBOARDING_BEEN_CLOSED, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -// In E2E snapshotter is disabled. Before the first test can be run, pending snapshot needs to be done. -describe('Make pending snapshot', () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.playground.absolute); - }); - - it('make pending snapshot', () => { - cy.window().then(async win => { - cy.wrap(null).then(() => { - return mutateAsyncMakeSnapshot(win, 'pending').then(str => { - expect(str).to.eq(true); - }); - }); - cy.get('[data-test=SyncView]', { timeout: 60000 }).should('not.exist'); - }); - }); -}); diff --git a/client/cypress/e2e/_3onboardingTOSNotAccepted.cy.ts b/client/cypress/e2e/_3onboardingTOSNotAccepted.cy.ts deleted file mode 100644 index 1bc289b944..0000000000 --- a/client/cypress/e2e/_3onboardingTOSNotAccepted.cy.ts +++ /dev/null @@ -1,103 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import chaiColors from 'chai-colors'; - -import { - beforeSetup, - checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px, - checkChangeStepsByClickingEdgeOfTheScreenUpTo25px, - checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px, - checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px, - checkChangeStepsWithArrowKeys, - checkCurrentElement, - checkProgressStepperSlimIsCurrentAndClickNext, - connectWalletOnboarding, -} from 'cypress/utils/onboarding'; -import viewports from 'cypress/utils/viewports'; -import { QUERY_KEYS } from 'src/api/queryKeys'; -import { - getStepsDecisionWindowClosed, - getStepsDecisionWindowOpen, -} from 'src/hooks/helpers/useOnboardingSteps/steps'; - -chai.use(chaiColors); - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }, index, arr) => { - describe(`onboarding (TOS not accepted): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - beforeSetup(); - }); - - beforeEach(() => { - connectWalletOnboarding(); - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('onboarding TOS step should be first and active', () => { - checkCurrentElement(0, true); - cy.get('[data-test=ModalOnboardingTOS]').should('be.visible'); - }); - - it('user is not able to click through entire onboarding flow', () => { - cy.window().then(win => { - const isDecisionWindowOpen = win.clientReactQuery.getQueryData( - QUERY_KEYS.isDecisionWindowOpen, - ); - - const onboardingSteps = isDecisionWindowOpen - ? getStepsDecisionWindowOpen('2', '16 Jan') - : getStepsDecisionWindowClosed('2', '16 Jan'); - - for (let i = 1; i < onboardingSteps.length; i++) { - checkProgressStepperSlimIsCurrentAndClickNext(i, i === 1); - } - }); - }); - - it('user is not able to close the modal by clicking button in the top-right', () => { - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - cy.get('[data-test=ModalOnboarding__Button]').click({ force: true }); - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - }); - - it('renders every time page is refreshed', () => { - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - cy.reload(); - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - }); - - it('user cannot change steps with arrow keys (left, right)', () => { - checkChangeStepsWithArrowKeys(false); - }); - - it('user can change steps by clicking the edge of the screen (up to 25px from each edge)', () => { - checkChangeStepsByClickingEdgeOfTheScreenUpTo25px(false); - }); - - it('user cannot change steps by clicking the edge of the screen (more than 25px from each edge)', () => { - checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px(false); - }); - - it('user cannot change steps by swiping on screen (difference more than or equal 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px(false); - }); - - it('user cannot change steps by swiping on screen (difference less than 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(false); - }); - - if (index === arr.length - 1) { - it('TOS acceptance changes onboarding step to next step and allows the user to close the modal by clicking button in the top-right', () => { - checkCurrentElement(0, true); - cy.get('[data-test=TOS_InputCheckbox]').check(); - cy.switchToMetamaskNotification(); - cy.confirmMetamaskSignatureRequest(); - checkCurrentElement(1, true); - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - }); - } - }); -}); diff --git a/client/cypress/e2e/_4onboardingTOSAccepted.cy.ts b/client/cypress/e2e/_4onboardingTOSAccepted.cy.ts deleted file mode 100644 index 080728357a..0000000000 --- a/client/cypress/e2e/_4onboardingTOSAccepted.cy.ts +++ /dev/null @@ -1,216 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import chaiColors from 'chai-colors'; - -import { navigateWithCheck } from 'cypress/utils/e2e'; -import { moveTime, setupAndMoveToPlayground } from 'cypress/utils/moveTime'; -import { - beforeSetup, - checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px, - checkChangeStepsByClickingEdgeOfTheScreenUpTo25px, - checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px, - checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px, - checkChangeStepsWithArrowKeys, - checkProgressStepperSlimIsCurrentAndClickNext, - connectWalletOnboarding, -} from 'cypress/utils/onboarding'; -import viewports from 'cypress/utils/viewports'; -import { QUERY_KEYS } from 'src/api/queryKeys'; -import { HAS_ONBOARDING_BEEN_CLOSED, IS_ONBOARDING_DONE } from 'src/constants/localStorageKeys'; -import { - getStepsDecisionWindowClosed, - getStepsDecisionWindowOpen, -} from 'src/hooks/helpers/useOnboardingSteps/steps'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -chai.use(chaiColors); - -describe('move time', () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - it('allocation window is open, when it is not, move time', () => { - setupAndMoveToPlayground(); - - cy.window().then(async win => { - moveTime(win, 'nextEpochDecisionWindowOpen').then(() => { - const isDecisionWindowOpenAfter = win.clientReactQuery.getQueryData( - QUERY_KEYS.isDecisionWindowOpen, - ); - expect(isDecisionWindowOpenAfter).to.be.true; - }); - }); - }); -}); - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`onboarding (TOS accepted): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - beforeSetup(); - }); - - beforeEach(() => { - cy.clearLocalStorage(); - connectWalletOnboarding(); - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('user is able to click through entire onboarding flow', () => { - cy.window().then(win => { - const isDecisionWindowOpen = win.clientReactQuery.getQueryData( - QUERY_KEYS.isDecisionWindowOpen, - ); - - const onboardingSteps = isDecisionWindowOpen - ? getStepsDecisionWindowOpen('2', '16 Jan') - : getStepsDecisionWindowClosed('2', '16 Jan'); - - for (let i = 1; i < onboardingSteps.length - 1; i++) { - checkProgressStepperSlimIsCurrentAndClickNext(i); - } - - cy.get('[data-test=ModalOnboarding__ProgressStepperSlim__element]') - .eq(onboardingSteps.length - 1) - .click(); - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - cy.get('[data-test=ProjectsView__ProjectsList]').should('be.visible'); - }); - }); - - it('user is able to close the modal by clicking button in the top-right', () => { - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - cy.get('[data-test=ProjectsView__ProjectsList]').should('be.visible'); - }); - - it('renders every time page is refreshed when "Always show Allocate onboarding" option is checked', () => { - cy.get('[data-test=ModalOnboarding__Button]').click(); - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').check().should('be.checked'); - cy.reload(); - // For the unknown reason reloads sometimes cause app to disconnect in E2E env. - cy.disconnectMetamaskWalletFromAllDapps(); - connectWalletOnboarding(); - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - }); - - it('renders only once when "Always show Allocate onboarding" option is not checked', () => { - cy.get('[data-test=ModalOnboarding__Button]').click(); - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('not.be.checked'); - cy.reload(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - }); - - it('user can change steps with arrow keys (left, right)', () => { - checkChangeStepsWithArrowKeys(true); - }); - - it('user can change steps by clicking the edge of the screen (up to 25px from each edge)', () => { - checkChangeStepsByClickingEdgeOfTheScreenUpTo25px(true); - }); - - it('user cannot change steps by clicking the edge of the screen (more than 25px from each edge)', () => { - checkChangeStepsByClickingEdgeOfTheScreenMoreThan25px(true); - }); - - it('user can change steps by swiping on screen (difference more than or equal 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceMoreThanOrEqual5px(true); - }); - - it('user cannot change steps by swiping on screen (difference less than 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(true); - }); - - it('user cannot change steps by swiping on screen (difference less than 5px)', () => { - checkChangeStepsBySwipingOnScreenDifferenceLessThanl5px(true); - }); - - it('user is able to close the onboarding, and after page reload, onboarding does not show up again', () => { - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - cy.reload(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - }); - - it('Onboarding stepper is visible after closing onboarding modal without going to the last step', () => { - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=OnboardingStepper]').should('be.visible'); - }); - - it('Onboarding stepper opens onboarding modal', () => { - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - cy.get('[data-test=OnboardingStepper]').click(); - cy.get('[data-test=ModalOnboarding]').should('be.visible'); - }); - - it(`Onboarding stepper is not visible if "${IS_ONBOARDING_DONE}" is set to "true"`, () => { - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - cy.reload(); - cy.get('[data-test=ModalOnboarding]').should('not.exist'); - cy.get('[data-test=OnboardingStepper]').should('not.exist'); - }); - - if (isDesktop) { - it(`Onboarding stepper has tooltip`, () => { - cy.get('[data-test=ModalOnboarding__Button]').click(); - cy.get('[data-test=OnboardingStepper]').trigger('mouseover'); - cy.get('[data-test=OnboardingStepper__Tooltip__content]').should('be.visible'); - cy.get('[data-test=OnboardingStepper__Tooltip__content]') - .invoke('text') - .should('eq', 'Reopen onboarding'); - }); - } - - it('Onboarding stepper has right amount of steps and highlights correct amount of passed steps', () => { - const onboardingSteps = getStepsDecisionWindowOpen('2', '16 Jan'); - - cy.get('[data-test=ModalOnboarding__Button]').click(); - - cy.get(`[data-test*=OnboardingStepper__circle]`).should( - 'have.length', - onboardingSteps.length, - ); - - for (let i = 0; i < onboardingSteps.length - 1; i++) { - cy.get(`[data-test=OnboardingStepper__circle--${i}]`) - .then($el => $el.css('stroke')) - .should('be.colored', i > 0 ? '#ffffff' : '#2d9b87'); - } - cy.get('[data-test=OnboardingStepper]').click(); - checkProgressStepperSlimIsCurrentAndClickNext(1); - cy.get('[data-test=ModalOnboarding__Button]').click(); - for (let i = 0; i < onboardingSteps.length - 1; i++) { - cy.get(`[data-test=OnboardingStepper__circle--${i}]`) - .then($el => $el.css('stroke')) - .should('be.colored', i > 1 ? '#ffffff' : '#2d9b87'); - } - cy.get('[data-test=OnboardingStepper]').click(); - checkProgressStepperSlimIsCurrentAndClickNext(2); - cy.get('[data-test=ModalOnboarding__Button]').click(); - for (let i = 0; i < onboardingSteps.length - 1; i++) { - cy.get(`[data-test=OnboardingStepper__circle--${i}]`) - .then($el => $el.css('stroke')) - .should('be.colored', i > 2 ? '#ffffff' : '#2d9b87'); - } - cy.get('[data-test=OnboardingStepper]').click(); - checkProgressStepperSlimIsCurrentAndClickNext(3); - cy.get('[data-test=ModalOnboarding__Button]').click(); - - cy.get('[data-test=OnboardingStepper]').should('not.exist'); - }); - }); -}); diff --git a/client/cypress/e2e/allocationItemWindowClosed.cy.ts b/client/cypress/e2e/allocationItemWindowClosed.cy.ts deleted file mode 100644 index 5188990442..0000000000 --- a/client/cypress/e2e/allocationItemWindowClosed.cy.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { - connectWallet, - visitWithLoader, - mockCoinPricesServer, - navigateWithCheck, - checkProjectsViewLoaded, -} from 'cypress/utils/e2e'; -import { moveTime, setupAndMoveToPlayground } from 'cypress/utils/moveTime'; -import viewports from 'cypress/utils/viewports'; -import { QUERY_KEYS } from 'src/api/queryKeys'; -import { - ALLOCATION_ITEMS_KEY, - HAS_ONBOARDING_BEEN_CLOSED, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -describe('allocation (allocation window closed)', () => { - describe('move time', () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - it('allocation window is closed, when it is not, move time', () => { - setupAndMoveToPlayground(); - - cy.window().then(async win => { - moveTime(win, 'nextEpochDecisionWindowClosed').then(() => { - cy.get('[data-test=PlaygroundView]').should('be.visible'); - const isDecisionWindowOpenAfter = win.clientReactQuery.getQueryData( - QUERY_KEYS.isDecisionWindowOpen, - ); - expect(isDecisionWindowOpenAfter).to.be.false; - }); - }); - }); - }); - - Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`test cases: ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - localStorage.setItem(ALLOCATION_ITEMS_KEY, '[]'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - - checkProjectsViewLoaded(); - cy.get('[data-test^=ProjectsView__ProjectsListItem]') - .eq(0) - .should('be.visible') - .find('[data-test=ProjectsListItem__name]') - .then($text => { - cy.wrap($text.text()).as('projectName'); - }); - - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(0) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .click(); - navigateWithCheck(ROOT_ROUTES.allocation.absolute); - }); - - it('AllocationItem shows all the elements', () => { - connectWallet({ isPatronModeEnabled: false }); - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__name]') - .then($allocationItemName => { - cy.get('@projectName').then(projectName => { - expect(projectName).to.eq($allocationItemName.text()); - }); - }); - - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__imageProfile]') - .should(isDesktop ? 'be.visible' : 'not.be.visible'); - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItemRewards__value]') - .contains('0 ETH'); - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItemRewardsDonors]') - .contains('0'); - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText]') - .should('be.disabled'); - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText__suffix]') - .contains('GWEI'); - }); - }); - }); -}); diff --git a/client/cypress/e2e/allocationItemWindowOpen.cy.ts b/client/cypress/e2e/allocationItemWindowOpen.cy.ts deleted file mode 100644 index d295448be9..0000000000 --- a/client/cypress/e2e/allocationItemWindowOpen.cy.ts +++ /dev/null @@ -1,207 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import chaiColors from 'chai-colors'; - -import { - visitWithLoader, - mockCoinPricesServer, - navigateWithCheck, - connectWallet, - checkProjectsViewLoaded, - ETH_USD, -} from 'cypress/utils/e2e'; -import { moveTime, setupAndMoveToPlayground } from 'cypress/utils/moveTime'; -import viewports from 'cypress/utils/viewports'; -import { QUERY_KEYS } from 'src/api/queryKeys'; -import { - ALLOCATION_ITEMS_KEY, - HAS_ONBOARDING_BEEN_CLOSED, - IS_CRYPTO_MAIN_VALUE_DISPLAY, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -chai.use(chaiColors); - -const budget = '10000000000000000'; // 0.01 ETH - -const changeMainValueToFiat = () => { - cy.get('[data-test=Navbar__Button--Settings]').click(); - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').uncheck(); - cy.get('[data-test=Navbar__Button--Allocate]').click(); -}; - -describe('allocation (allocation window open)', () => { - describe('move time', () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - it('allocation window is open, when it is not, move time', () => { - setupAndMoveToPlayground(); - - cy.window().then(async win => { - moveTime(win, 'nextEpochDecisionWindowOpen').then(() => { - const isDecisionWindowOpenAfter = win.clientReactQuery.getQueryData( - QUERY_KEYS.isDecisionWindowOpen, - ); - expect(isDecisionWindowOpenAfter).to.be.true; - }); - }); - }); - }); - - Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`test cases: ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - cy.intercept('GET', '/rewards/budget/*/epoch/*', { body: { budget } }); - cy.disconnectMetamaskWalletFromAllDapps(); - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - localStorage.setItem(ALLOCATION_ITEMS_KEY, '[]'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - connectWallet({ isPatronModeEnabled: false }); - - checkProjectsViewLoaded(); - cy.get('[data-test^=ProjectsView__ProjectsListItem]') - .eq(0) - .should('be.visible') - .find('[data-test=ProjectsListItem__name]') - .then($text => { - cy.wrap($text.text()).as('projectName'); - }); - - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(0) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .click(); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(1) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .click(); - navigateWithCheck(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationItemSkeleton]').should('not.exist'); - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('AllocationItem shows all the elements', () => { - cy.get('@projectName').then(projectName => { - cy.get('[data-test=AllocationItem__name]').contains(projectName).should('be.visible'); - }); - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__imageProfile]') - .should(isDesktop ? 'be.visible' : 'not.be.visible'); - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText]') - .should('be.enabled'); - }); - - it('AllocationItem__InputText correctly changes background color on focus', () => { - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText]') - .focus(); - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText]') - .should('have.focus'); - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText]') - .should('have.css', 'background-color') - .and('be.colored', '#f1faf8'); - }); - - it('AllocationItem__InputText correctly changes background color on error', () => { - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText__suffix]') - .contains('ETH'); - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText]') - .type('99'); - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText]') - .should('have.css', 'background-color') - .and('be.colored', '#f1faf8'); - }); - - it('AllocationItem__InputText has correct suffix', () => { - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText__suffix]') - .contains('ETH'); - - changeMainValueToFiat(); - - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText__suffix]') - .contains('USD'); - }); - - it(`User can change allocation item value manually (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: true)`, () => { - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText]') - .clear() - .type('0.005'); - cy.get('[data-test=AllocationItem]') - .eq(1) - .find('[data-test=AllocationItem__InputText]') - .clear() - .type('0.002'); - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', '0.007 ETH'); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', '0.003 ETH'); - }); - - it(`User can change allocation item value manually (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: false)`, () => { - changeMainValueToFiat(); - - cy.get('[data-test=AllocationItem]') - .eq(0) - .find('[data-test=AllocationItem__InputText]') - .clear() - .type(`${(0.005 * ETH_USD).toFixed(2)}`); - cy.get('[data-test=AllocationItem]') - .eq(1) - .find('[data-test=AllocationItem__InputText]') - .clear() - .type(`${(0.002 * ETH_USD).toFixed(2)}`); - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', `$${(0.007 * ETH_USD).toFixed(2)}`); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', `$${(0.003 * ETH_USD).toFixed(2)}`); - }); - }); - }); -}); diff --git a/client/cypress/e2e/allocationRewardsBox.cy.ts b/client/cypress/e2e/allocationRewardsBox.cy.ts deleted file mode 100644 index 5bddda1bcc..0000000000 --- a/client/cypress/e2e/allocationRewardsBox.cy.ts +++ /dev/null @@ -1,585 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import chaiColors from 'chai-colors'; - -import { - visitWithLoader, - mockCoinPricesServer, - connectWallet, - ETH_USD, - changeMainValueToFiat, -} from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { - HAS_ONBOARDING_BEEN_CLOSED, - IS_CRYPTO_MAIN_VALUE_DISPLAY, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -chai.use(chaiColors); - -const splitTheValueUsingSlider = (isCryptoAsAMainValue: boolean) => { - if (!isCryptoAsAMainValue) { - changeMainValueToFiat(ROOT_ROUTES.allocation.absolute); - } - - cy.get('[data-test=AllocationRewardsBox__title]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.01 ETH' : `$${(0.01 * ETH_USD).toFixed(2)}`); - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.01 ETH' : `$${(0.01 * ETH_USD).toFixed(2)}`); - - cy.get('[data-test=AllocationRewardsBox__Slider]').then($sliderEl => { - const { width: sliderElWidth } = $sliderEl[0].getBoundingClientRect(); - - cy.get('[data-test=AllocationRewardsBox__Slider__thumb]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - // track 0 is hidden under the thumb - cy.get('[data-test=AllocationRewardsBox__Slider__track--0]').should( - 'have.css', - 'width', - `${sliderButtonDimensions.width}px`, - ); - cy.get('[data-test=AllocationRewardsBox__Slider__track--1]').should( - 'have.css', - 'width', - `${sliderElWidth}px`, - ); - - const pageX0 = sliderButtonDimensions.left; - const pageX50 = - sliderButtonDimensions.left - sliderButtonDimensions.width / 2 + sliderElWidth / 2; - const pageX100 = - sliderButtonDimensions.left - sliderButtonDimensions.width / 2 + sliderElWidth; - - cy.get('[data-test=AllocationRewardsBox__Slider__thumb]') - .trigger('mousedown', { - pageX: pageX0, - }) - .trigger('mousemove', { - pageX: pageX50, - }) - .trigger('mouseup', { - pageX: pageX50, - }); - - cy.get('[data-test=AllocationRewardsBox__Slider__track--0]').should( - 'have.css', - 'width', - `${(sliderButtonDimensions.width + sliderElWidth) / 2}px`, - ); - cy.get('[data-test=AllocationRewardsBox__Slider__track--0]') - .then($el => $el.css('background-color')) - .should('be.colored', '#2d9b87'); - - cy.get('[data-test=AllocationRewardsBox__Slider__track--1]').should( - 'have.css', - 'width', - `${(sliderButtonDimensions.width + sliderElWidth) / 2}px`, - ); - cy.get('[data-test=AllocationRewardsBox__Slider__track--1]') - .then($el => $el.css('background-color')) - .should('be.colored', '#ff9601'); - - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.005 ETH' : `$${((0.01 * ETH_USD) / 2).toFixed(2)}`); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.005 ETH' : `$${((0.01 * ETH_USD) / 2).toFixed(2)}`); - - cy.get('[data-test=AllocationRewardsBox__Slider__thumb]') - .trigger('mousedown', { - pageX: pageX50, - }) - .trigger('mousemove', { - pageX: pageX100, - }) - .trigger('mouseup', { - pageX: pageX100, - }); - - cy.get('[data-test=AllocationRewardsBox__Slider__track--0]').should( - 'have.css', - 'width', - `${sliderElWidth}px`, - ); - cy.get('[data-test=AllocationRewardsBox__Slider__track--0]') - .then($el => $el.css('background-color')) - .should('be.colored', '#2d9b87'); - - // track 1 is hidden under the thumb - cy.get('[data-test=AllocationRewardsBox__Slider__track--1]').should( - 'have.css', - 'width', - `${sliderButtonDimensions.width}px`, - ); - cy.get('[data-test=AllocationRewardsBox__Slider__track--1]') - .then($el => $el.css('background-color')) - .should('be.colored', '#ff9601'); - - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.01 ETH' : `$${(0.01 * ETH_USD).toFixed(2)}`); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - }); - }); -}; - -const changeDonateManually = (isCryptoAsAMainValue: boolean) => { - if (!isCryptoAsAMainValue) { - changeMainValueToFiat(ROOT_ROUTES.allocation.absolute); - } - - cy.get('[data-test=AllocationRewardsBox__section--0]').click(); - cy.get('[data-test=ModalAllocationValuesEdit__header]').invoke('text').should('eq', 'Donate 0%'); - cy.get('[data-test=AllocationInputs__InputText--crypto]').should( - 'have.value', - isCryptoAsAMainValue ? '0' : '0.00', - ); - cy.get('[data-test=AllocationInputs__InputText--crypto]').should('be.focused'); - cy.get('[data-test=AllocationInputs__InputText--crypto]') - .then($el => $el.css('border-color')) - .should('be.colored', '#2d9b87'); - - cy.get('[data-test=AllocationInputs__InputText--percentage]').should('have.value', '0'); - cy.get('[data-test=AllocationInputs__InputText--percentage]').should('not.be.focused'); - - // 0.01 ETH - cy.get('[data-test=AllocationInputs__InputText--crypto]').type( - isCryptoAsAMainValue ? '0.01' : `${(0.01 * ETH_USD).toFixed(2)}`, - ); - cy.get('[data-test=AllocationInputs__InputText--crypto]') - .then($el => $el.css('border-color')) - .should('be.colored', '#2d9b87'); - - cy.get('[data-test=AllocationInputs__InputText--percentage]').should('have.value', '100'); - - cy.get('[data-test=AllocationInputs__Button]').should('not.be.disabled'); - cy.get('[data-test=AllocationInputs__Button]').click(); - - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.01 ETH' : `$${(0.01 * ETH_USD).toFixed(2)}`); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - - cy.get('[data-test=AllocationRewardsBox__section--0]').click(); - - cy.get('[data-test=ModalAllocationValuesEdit__header]') - .invoke('text') - .should('eq', 'Donate 100%'); - - // 0.1 ETH - cy.get('[data-test=AllocationInputs__InputText--percentage]').clear(); - cy.get('[data-test=AllocationInputs__InputText--crypto]') - .clear() - .type(isCryptoAsAMainValue ? '0.1' : `${(0.1 * ETH_USD).toFixed(2)}`); - cy.get('[data-test=AllocationInputs__InputText--crypto]') - .then($el => $el.css('border-color')) - .should('be.colored', '#FF6157'); - - cy.get('[data-test=AllocationInputs__InputText--percentage]').should('have.value', '100'); - - cy.get('[data-test=AllocationInputs__Button]').should('be.disabled'); - - cy.get('[data-test=AllocationInputs__InputText--crypto]').clear(); - cy.get('[data-test=AllocationInputs__InputText--percentage]').should('have.value', '0'); - - cy.get('[data-test=AllocationInputs__InputText--percentage]').clear(); - cy.get('[data-test=AllocationInputs__InputText--crypto]').should( - 'have.value', - isCryptoAsAMainValue ? '0' : '0.00', - ); - - // 50 % - cy.get('[data-test=AllocationInputs__InputText--percentage]').type('50'); - cy.get('[data-test=AllocationInputs__InputText--crypto]').should( - 'have.value', - isCryptoAsAMainValue ? '0.005' : `${((0.01 * ETH_USD) / 2).toFixed(2)}`, - ); - - cy.get('[data-test=AllocationInputs__Button]').should('not.be.disabled'); - cy.get('[data-test=AllocationInputs__Button]').click(); - - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.005 ETH' : `$${((0.01 * ETH_USD) / 2).toFixed(2)}`); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.005 ETH' : `$${((0.01 * ETH_USD) / 2).toFixed(2)}`); - - cy.get('[data-test=AllocationRewardsBox__section--0]').click(); - - cy.get('[data-test=ModalAllocationValuesEdit__header]').invoke('text').should('eq', 'Donate 50%'); - - // 100 % - cy.get('[data-test=AllocationInputs__InputText--percentage]').clear(); - cy.get('[data-test=AllocationInputs__InputText--percentage]').type('100'); - cy.get('[data-test=AllocationInputs__InputText--crypto]').should( - 'have.value', - isCryptoAsAMainValue ? '0.01' : `${(0.01 * ETH_USD).toFixed(2)}`, - ); - - cy.get('[data-test=AllocationInputs__Button]').should('not.be.disabled'); - cy.get('[data-test=AllocationInputs__Button]').click(); - - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.01 ETH' : `$${(0.01 * ETH_USD).toFixed(2)}`); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - - cy.get('[data-test=AllocationRewardsBox__section--0]').click(); - - cy.get('[data-test=ModalAllocationValuesEdit__header]') - .invoke('text') - .should('eq', 'Donate 100%'); - - // 1000 % - cy.get('[data-test=AllocationInputs__InputText--percentage]').clear(); - cy.get('[data-test=AllocationInputs__InputText--percentage]').type('1000'); - cy.get('[data-test=AllocationInputs__InputText--crypto]').should( - 'have.value', - isCryptoAsAMainValue ? '0.01' : `${(0.01 * ETH_USD).toFixed(2)}`, - ); - - cy.get('[data-test=AllocationInputs__Button]').should('not.be.disabled'); - cy.get('[data-test=AllocationInputs__Button]').click(); - - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.01 ETH' : `$${(0.01 * ETH_USD).toFixed(2)}`); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); -}; - -const changePersonalManually = (isCryptoAsAMainValue: boolean) => { - if (!isCryptoAsAMainValue) { - changeMainValueToFiat(ROOT_ROUTES.allocation.absolute); - } - - cy.get('[data-test=AllocationRewardsBox__section--1]').click(); - cy.get('[data-test=ModalAllocationValuesEdit__header]') - .invoke('text') - .should('eq', 'Personal 100%'); - cy.get('[data-test=AllocationInputs__InputText--crypto]').should( - 'have.value', - isCryptoAsAMainValue ? '0.01' : `${(0.01 * ETH_USD).toFixed(2)}`, - ); - cy.get('[data-test=AllocationInputs__InputText--crypto]').should('be.focused'); - cy.get('[data-test=AllocationInputs__InputText--crypto]') - .then($el => $el.css('border-color')) - .should('be.colored', '#2d9b87'); - - cy.get('[data-test=AllocationInputs__InputText--percentage]').should('have.value', '100'); - cy.get('[data-test=AllocationInputs__InputText--percentage]').should('not.be.focused'); - - // 0.01 ETH - cy.get('[data-test=AllocationInputs__InputText--crypto]').type( - isCryptoAsAMainValue ? '0.01' : `${(0.01 * ETH_USD).toFixed(2)}`, - ); - cy.get('[data-test=AllocationInputs__InputText--crypto]') - .then($el => $el.css('border-color')) - .should('be.colored', '#2d9b87'); - - cy.get('[data-test=AllocationInputs__InputText--percentage]').should('have.value', '100'); - - cy.get('[data-test=AllocationInputs__Button]').should('not.be.disabled'); - cy.get('[data-test=AllocationInputs__Button]').click(); - - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.01 ETH' : `$${(0.01 * ETH_USD).toFixed(2)}`); - - cy.get('[data-test=AllocationRewardsBox__section--1]').click(); - - cy.get('[data-test=ModalAllocationValuesEdit__header]') - .invoke('text') - .should('eq', 'Personal 100%'); - - // 0.1 ETH - cy.get('[data-test=AllocationInputs__InputText--crypto]').type( - isCryptoAsAMainValue ? '0.1' : `${(0.1 * ETH_USD).toFixed(2)}`, - ); - cy.get('[data-test=AllocationInputs__InputText--crypto]') - .then($el => $el.css('border-color')) - .should('be.colored', '#FF6157'); - cy.get('[data-test=AllocationInputs__InputText--percentage]').should('have.value', '100'); - - cy.get('[data-test=AllocationInputs__Button]').should('be.disabled'); - - cy.get('[data-test=AllocationInputs__InputText--crypto]').clear(); - cy.get('[data-test=AllocationInputs__InputText--percentage]').should('have.value', '0'); - - cy.get('[data-test=AllocationInputs__InputText--percentage]').clear(); - cy.get('[data-test=AllocationInputs__InputText--crypto]').should( - 'have.value', - isCryptoAsAMainValue ? '0' : '0.00', - ); - - // 50 % - cy.get('[data-test=AllocationInputs__InputText--percentage]').clear(); - cy.get('[data-test=AllocationInputs__InputText--percentage]').type('50'); - cy.get('[data-test=AllocationInputs__InputText--crypto]').should( - 'have.value', - isCryptoAsAMainValue ? '0.005' : `${((0.01 * ETH_USD) / 2).toFixed(2)}`, - ); - - cy.get('[data-test=AllocationInputs__Button]').should('not.be.disabled'); - cy.get('[data-test=AllocationInputs__Button]').click(); - - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.005 ETH' : `$${((0.01 * ETH_USD) / 2).toFixed(2)}`); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.005 ETH' : `$${((0.01 * ETH_USD) / 2).toFixed(2)}`); - - cy.get('[data-test=AllocationRewardsBox__section--1]').click(); - - cy.get('[data-test=ModalAllocationValuesEdit__header]') - .invoke('text') - .should('eq', 'Personal 50%'); - - // 100 % - cy.get('[data-test=AllocationInputs__InputText--percentage]').clear(); - cy.get('[data-test=AllocationInputs__InputText--percentage]').type('100'); - cy.get('[data-test=AllocationInputs__InputText--crypto]').should( - 'have.value', - isCryptoAsAMainValue ? '0.01' : `${(0.01 * ETH_USD).toFixed(2)}`, - ); - - cy.get('[data-test=AllocationInputs__Button]').should('not.be.disabled'); - cy.get('[data-test=AllocationInputs__Button]').click(); - - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.01 ETH' : `$${(0.01 * ETH_USD).toFixed(2)}`); - - cy.get('[data-test=AllocationRewardsBox__section--1]').click(); - - cy.get('[data-test=ModalAllocationValuesEdit__header]') - .invoke('text') - .should('eq', 'Personal 100%'); - - // 1000 % - cy.get('[data-test=AllocationInputs__InputText--percentage]').clear(); - cy.get('[data-test=AllocationInputs__InputText--percentage]').type('1000'); - cy.get('[data-test=AllocationInputs__InputText--crypto]').should( - 'have.value', - isCryptoAsAMainValue ? '0.01' : `${(0.01 * ETH_USD).toFixed(2)}`, - ); - - cy.get('[data-test=AllocationInputs__Button]').should('not.be.disabled'); - cy.get('[data-test=AllocationInputs__Button]').click(); - - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0.01 ETH' : `$${(0.01 * ETH_USD).toFixed(2)}`); -}; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe( - `allocation rewards box (disabled): ${device}`, - { viewportHeight, viewportWidth }, - () => { - beforeEach(() => { - mockCoinPricesServer(); - visitWithLoader(ROOT_ROUTES.allocation.absolute); - }); - - it('is visible', () => { - cy.get('[data-test=AllocationRewardsBox]').should('be.visible'); - }); - - it(`has each field with value 0 ETH (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: true)`, () => { - cy.get('[data-test=AllocationRewardsBox__title]').invoke('text').should('eq', '0 ETH'); - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', '0 ETH'); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', '0 ETH'); - }); - - it(`has each field with value $0.00 (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: false)`, () => { - changeMainValueToFiat(ROOT_ROUTES.allocation.absolute); - - cy.get('[data-test=AllocationRewardsBox__title]').invoke('text').should('eq', '$0.00'); - cy.get('[data-test=AllocationRewardsBox__section__value--0]') - .invoke('text') - .should('eq', '$0.00'); - cy.get('[data-test=AllocationRewardsBox__section__value--1]') - .invoke('text') - .should('eq', '$0.00'); - }); - - it('shows "No rewards yet" message below rewards value', () => { - cy.get('[data-test=AllocationRewardsBox__subtitle]') - .invoke('text') - .should('eq', 'No rewards yet'); - }); - - it('Clicking on `Donate` label or value doesn`t open modal to editing value', () => { - cy.get('[data-test=AllocationRewardsBox__section--0]').click(); - cy.get('[data-test=ModalAllocationValuesEdit]').should('not.exist'); - }); - - it('Clicking on `Personal` label or value doesn`t open modal to editing value', () => { - cy.get('[data-test=AllocationRewardsBox__section--1]').click(); - cy.get('[data-test=ModalAllocationValuesEdit]').should('not.exist'); - }); - - it('slider is visible', () => { - cy.get('[data-test=AllocationRewardsBox__Slider]').should('be.visible'); - }); - - it('slider thumb exists but isn`t visible', () => { - cy.get('[data-test=AllocationRewardsBox__Slider__thumb]').should('exist'); - cy.get('[data-test=AllocationRewardsBox__Slider__thumb]').should('not.be.visible'); - }); - - it('slider track should be filled in 50%', () => { - cy.get('[data-test=AllocationRewardsBox__Slider]').then($sliderEl => { - const { width } = $sliderEl[0].getBoundingClientRect(); - - cy.get('[data-test=AllocationRewardsBox__Slider__track--0]').should( - 'have.css', - 'width', - `${width / 2}px`, - ); - cy.get('[data-test=AllocationRewardsBox__Slider__track--0]') - .then($el => $el.css('background-color')) - .should('be.colored', '#9ea39e'); - - cy.get('[data-test=AllocationRewardsBox__Slider__track--1]').should( - 'have.css', - 'width', - `${width / 2}px`, - ); - cy.get('[data-test=AllocationRewardsBox__Slider__track--1]') - .then($el => $el.css('background-color')) - .should('be.colored', '#cdd1cd'); - }); - }); - - it('Clicking on `Personal` label or value doesn`t open modal to editing value', () => { - cy.get('[data-test=AllocationRewardsBox__section--1]').click(); - cy.get('[data-test=ModalAllocationValuesEdit]').should('not.exist'); - }); - - it('Clicking on `Personal` label or value doesn`t open modal to editing value', () => { - cy.get('[data-test=AllocationRewardsBox__section--1]').click(); - cy.get('[data-test=ModalAllocationValuesEdit]').should('not.exist'); - }); - }, - ); - - describe(`allocation rewards box (enabled): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.allocation.absolute); - cy.intercept('GET', '/rewards/budget/*/epoch/*', { body: { budget: '10000000000000000' } }); - connectWallet({ isPatronModeEnabled: false }); - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('is visible', () => { - cy.get('[data-test=AllocationRewardsBox]').should('be.visible'); - }); - - it('shows "Available now" message below rewards value', () => { - cy.get('[data-test=AllocationRewardsBox__subtitle]') - .invoke('text') - .should('eq', 'Available now'); - }); - - it(`user has 0.01 ETH rewards (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: true)`, () => { - cy.get('[data-test=AllocationRewardsBox__title]').invoke('text').should('eq', '0.01 ETH'); - }); - - it(`user has $20.42 (0.01 ETH) rewards (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: false)`, () => { - changeMainValueToFiat(ROOT_ROUTES.allocation.absolute); - - cy.get('[data-test=AllocationRewardsBox__title]') - .invoke('text') - .should('eq', `$${(0.01 * ETH_USD).toFixed(2)}`); - }); - - it('slider thumb exists and is visible', () => { - cy.get('[data-test=AllocationRewardsBox__Slider__thumb]').should('exist'); - cy.get('[data-test=AllocationRewardsBox__Slider__thumb]').should('be.visible'); - }); - - it('Clicking on `Donate` label or value opens modal to editing value', () => { - cy.get('[data-test=AllocationRewardsBox__section--0]').click(); - cy.get('[data-test=ModalAllocationValuesEdit]').should('exist').should('be.visible'); - }); - - it('Clicking on `Personal` label or value opens modal to editing value', () => { - cy.get('[data-test=AllocationRewardsBox__section--1]').click(); - cy.get('[data-test=ModalAllocationValuesEdit]').should('exist').should('be.visible'); - }); - - it(`user can split the value by using slider from 0/100 to 50/50 and then from 50/50 to 100/0 (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: true)`, () => { - splitTheValueUsingSlider(true); - }); - - it(`user can split the value by using slider from 0/100 to 50/50 and then from 50/50 to 100/0 (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: false)`, () => { - splitTheValueUsingSlider(false); - }); - - it(`user can change 'Donate' value manually in modal (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: true)`, () => { - changeDonateManually(true); - }); - - it(`user can change 'Donate' value manually in modal (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: false)`, () => { - changeDonateManually(false); - }); - - it(`user can change 'Personal' value manually in modal ${IS_CRYPTO_MAIN_VALUE_DISPLAY}: true)`, () => { - changePersonalManually(true); - }); - - it(`user can change 'Personal' value manually in modal ${IS_CRYPTO_MAIN_VALUE_DISPLAY}: false)`, () => { - changePersonalManually(false); - }); - }); -}); diff --git a/client/cypress/e2e/allocationSummary.cy.ts b/client/cypress/e2e/allocationSummary.cy.ts deleted file mode 100644 index 63a616e47b..0000000000 --- a/client/cypress/e2e/allocationSummary.cy.ts +++ /dev/null @@ -1,99 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import chaiColors from 'chai-colors'; - -import { - visitWithLoader, - mockCoinPricesServer, - connectWallet, - changeMainValueToFiat, - ETH_USD, -} from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { - HAS_ONBOARDING_BEEN_CLOSED, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -chai.use(chaiColors); - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`allocation summary: ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.allocation.absolute); - cy.intercept('GET', '/rewards/budget/*/epoch/*', { body: { budget: '10000000000000000' } }); - cy.intercept('GET', '/allocations/user/*/epoch/*', { - body: { - allocations: [ - { - address: '0x15c941a44a343B8c46a28F2BB9aFc7a54E255A4f', - amount: '5000000000000000', - }, - { - address: '0x1c01595f9534E33d411035AE99a4317faeC4f6Fe', - amount: '2000000000000000', - }, - ], - isManuallyEdited: true, - }, - }); - connectWallet({ isPatronModeEnabled: false }); - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('Allocation summary section shows correct values', () => { - cy.get('[data-test=AllocationSummary]').should('be.visible'); - cy.get('[data-test=AllocationSummary__personalRewardBox]').should('be.visible'); - cy.get('[data-test=AllocationSummaryProject]').should('have.length', 2); - cy.get('[data-test=AllocationSummaryProject]') - .eq(0) - .find('[data-test=AllocationSummaryProject__donation]') - .invoke('text') - .should('eq', '0.005'); - cy.get('[data-test=AllocationSummaryProject]') - .eq(1) - .find('[data-test=AllocationSummaryProject__donation]') - .invoke('text') - .should('eq', '0.002'); - - cy.get('[data-test=AllocationSummary__personalReward]') - .invoke('text') - .should('eq', '0.003 ETH'); - - changeMainValueToFiat(ROOT_ROUTES.allocation.absolute); - - cy.get('[data-test=AllocationSummaryProject]') - .eq(0) - .find('[data-test=AllocationSummaryProject__donation]') - .invoke('text') - .should('eq', `$${(0.005 * ETH_USD).toFixed(2)}`); - cy.get('[data-test=AllocationSummaryProject]') - .eq(1) - .find('[data-test=AllocationSummaryProject__donation]') - .invoke('text') - .should('eq', `$${(0.002 * ETH_USD).toFixed(2)}`); - - cy.get('[data-test=AllocationSummary__personalReward]') - .invoke('text') - .should('eq', `$${(0.003 * ETH_USD).toFixed(2)}`); - }); - }); -}); diff --git a/client/cypress/e2e/earn.cy.ts b/client/cypress/e2e/earn.cy.ts deleted file mode 100644 index e080d151af..0000000000 --- a/client/cypress/e2e/earn.cy.ts +++ /dev/null @@ -1,364 +0,0 @@ -import { - visitWithLoader, - mockCoinPricesServer, - connectWallet, - GLM_USD, - changeMainValueToFiat, -} from 'cypress/utils/e2e'; -import { moveTime } from 'cypress/utils/moveTime'; -import { ConnectWalletParameters } from 'cypress/utils/types'; -import viewports from 'cypress/utils/viewports'; -import { - HAS_ONBOARDING_BEEN_CLOSED, - IS_CRYPTO_MAIN_VALUE_DISPLAY, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -const checkValues = (isCryptoAsAMainValue: boolean) => { - if (!isCryptoAsAMainValue) { - changeMainValueToFiat(ROOT_ROUTES.earn.absolute); - } - - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 GLM' : '$0.00'); - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__secondary]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '$0.00' : '0 GLM'); - - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 GLM' : '$0.00'); - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '$0.00' : '0 GLM'); - - cy.get('[data-test=BoxPersonalAllocation__Section--pending__DoubleValue__primary]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - cy.get('[data-test=BoxPersonalAllocation__Section--pending__DoubleValue__secondary]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '$0.00' : '0 ETH'); - - cy.get('[data-test=BoxPersonalAllocation__Section--availableNow__DoubleValue__primary]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - cy.get('[data-test=BoxPersonalAllocation__Section--availableNow__DoubleValue__secondary]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '$0.00' : '0 ETH'); -}; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }, idx) => { - describe(`earn: ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.earn.absolute); - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('renders "Locked balance" box', () => { - cy.get('[data-test=BoxGlmLock__BoxRounded]').should('be.visible'); - }); - - it('renders "Personal allocation" box', () => { - cy.get('[data-test=BoxPersonalAllocation]').should('be.visible'); - }); - - it('renders "History"', () => { - cy.get('[data-test=History]').should('be.visible'); - }); - - it('"Lock GLM" button is visible', () => { - cy.get('[data-test=BoxGlmLock__Button]').should('be.visible'); - }); - - it('"Lock GLM" button is disabled', () => { - cy.get('[data-test=BoxGlmLock__Button]').should('be.disabled'); - }); - - it('"Withdraw to wallet" button is visible', () => { - cy.get('[data-test=BoxPersonalAllocation__Button]').should('be.visible'); - }); - - it('"Withdraw to wallet" button is disabled', () => { - cy.get('[data-test=BoxPersonalAllocation__Button]').should('be.disabled'); - }); - - it('"Effective" section has tooltip', () => { - cy.get('[data-test=BoxGlmLock__Section--effective]').should('be.visible'); - }); - - if (!isDesktop) { - it('"Effective" section tooltip svg icon opens "TooltipEffectiveLockedBalance"', () => { - cy.get('[data-test=BoxGlmLock__Section--effective__Svg]').click(); - cy.get('[data-test=TooltipEffectiveLockedBalance]').should('be.visible'); - }); - } - - it('Wallet connected: "Lock GLM" / "Edit Locked GLM" button is active', () => { - connectWallet({ isPatronModeEnabled: false }); - cy.get('[data-test=BoxGlmLock__Button]').should('not.be.disabled'); - }); - - it('Wallet connected: "Lock GLM" / "Edit Locked GLM" button opens "ModalGlmLock"', () => { - connectWallet({ isPatronModeEnabled: false }); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=ModalGlmLock]').should('be.visible'); - }); - - it('Wallet connected: "ModalGlmLock" has overflow', () => { - connectWallet({ isPatronModeEnabled: false }); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=ModalGlmLock__overflow]').should('exist'); - }); - - it('Wallet connected: inputs allow to type multiple characters without focus problems', () => { - /** - * In EarnGlmLock there are multiple autofocus rules set. - * This test checks if user is still able to type without any autofocus disruption. - */ - connectWallet({ isPatronModeEnabled: false }); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=ModalGlmLock]').should('be.visible'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type('100'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.value', '100'); - cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type('100'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.value', '100'); - }); - - it('Wallet connected: "ModalGlmLock" - changing tabs keep focus on first input', () => { - connectWallet({ isPatronModeEnabled: false }); - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=ModalGlmLock]').should('be.visible'); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); - cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); - cy.get('[data-test=EarnGlmLockTabs__tab--0]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').should('have.focus'); - }); - - it('Wallet connected: Lock 1 GLM', () => { - connectWallet({ isPatronModeEnabled: false }); - - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') - .invoke('text') - .then(text => { - const amountToLock = 1; - const lockedGlms = parseInt(text, 10); - - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type(`${amountToLock}`); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Lock'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Waiting for confirmation'); - cy.confirmMetamaskPermissionToSpend({ - spendLimit: '99999999999999999999', - }); - // Workaround for two notifications during first transaction. - // 1. Allow the third party to spend TKN from your current balance. - // 2. Confirm permission to spend - if (Cypress.env('CI') === 'true' && idx === 0) { - cy.wait(1000); - cy.confirmMetamaskPermissionToSpend({ - spendLimit: '99999999999999999999', - }); - } - cy.get('[data-test=GlmLockTabs__Button]', { timeout: 180000 }).should( - 'have.text', - 'Close', - ); - cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get( - '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', - // Small timeout ensures skeleton shows up quickly after the transaction. - { timeout: 1000 }, - ).should('be.visible'); - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { - timeout: 60000, - }) - .invoke('text') - .then(nextText => { - const lockedGlmsAfterLock = parseInt(nextText, 10); - expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); - }); - cy.get('[data-test=HistoryItem__title]').first().should('have.text', 'Locked GLM'); - cy.get('[data-test=HistoryItem__DoubleValue__primary]') - .first() - .should('have.text', '1 GLM'); - }); - }); - - it('Wallet connected: Unlock 1 GLM', () => { - connectWallet({ isPatronModeEnabled: false }); - - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') - .invoke('text') - .then(text => { - const amountToUnlock = 1; - const lockedGlms = parseInt(text, 10); - - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=EarnGlmLockTabs__tab--1]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]') - .clear() - .type(`${amountToUnlock}`); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Unlock'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Waiting for confirmation'); - cy.confirmMetamaskPermissionToSpend({ - shouldWaitForPopupClosure: true, - spendLimit: '99999999999999999999', - }); - cy.get('[data-test=GlmLockTabs__Button]', { timeout: 60000 }).should( - 'have.text', - 'Close', - ); - cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get( - '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', - // Small timeout ensures skeleton shows up quickly after the transaction. - { timeout: 1000 }, - ).should('be.visible'); - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { - timeout: 60000, - }) - .invoke('text') - .then(nextText => { - const lockedGlmsAfterUnlock = parseInt(nextText, 10); - expect(lockedGlms - amountToUnlock).to.be.eq(lockedGlmsAfterUnlock); - }); - cy.get('[data-test=HistoryItem__title]').first().should('have.text', 'Unlocked GLM'); - cy.get('[data-test=HistoryItem__DoubleValue__primary]') - .first() - .should('have.text', '1 GLM'); - cy.get('[data-test=HistoryItem__DoubleValue__secondary]') - .first() - .should('have.text', `$${(1 * GLM_USD).toFixed(2)}`); - - cy.get('[data-test=HistoryItem]').first().click(); - cy.get('[data-test=EarnHistoryItemDetailsModal]').should('be.visible'); - - cy.get('[data-test=EarnHistoryItemDetailsRest__amount__DoubleValue__primary]') - .invoke('text') - .should('eq', '1 GLM'); - cy.get('[data-test=EarnHistoryItemDetailsRest__amount__DoubleValue__secondary]') - .invoke('text') - .should('eq', `$${(1 * GLM_USD).toFixed(2)}`); - - cy.get('[data-test=EarnHistoryItemDetailsModal__Button]').click(); - cy.get('[data-test=EarnHistoryItemDetailsModal]').should('not.be.visible'); - - changeMainValueToFiat(ROOT_ROUTES.earn.absolute); - - cy.get('[data-test=HistoryItem__DoubleValue__primary]') - .first() - .should('have.text', `$${(1 * GLM_USD).toFixed(2)}`); - cy.get('[data-test=HistoryItem__DoubleValue__secondary]') - .first() - .should('have.text', '1 GLM'); - - cy.get('[data-test=HistoryItem]').first().click(); - cy.get('[data-test=EarnHistoryItemDetailsModal]').should('be.visible'); - - cy.get('[data-test=EarnHistoryItemDetailsRest__amount__DoubleValue__primary]') - .invoke('text') - .should('eq', `$${(1 * GLM_USD).toFixed(2)}`); - cy.get('[data-test=EarnHistoryItemDetailsRest__amount__DoubleValue__secondary]') - .invoke('text') - .should('eq', `1 GLM`); - }); - }); - - it('Wallet connected: Effective deposit after locking 1000 GLM and moving epoch is equal to current deposit', () => { - const connectWalletParameters: ConnectWalletParameters = { - isPatronModeEnabled: false, - }; - connectWallet(connectWalletParameters); - - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]') - .invoke('text') - .then(text => { - const amountToLock = 1000; - const lockedGlms = parseInt(text.replace(/\u200a/g, ''), 10); - - cy.get('[data-test=BoxGlmLock__Button]').click(); - cy.get('[data-test=InputsCryptoFiat__InputText--crypto]').clear().type(`${amountToLock}`); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Lock'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get('[data-test=GlmLockTabs__Button]').should('have.text', 'Waiting for confirmation'); - cy.confirmMetamaskPermissionToSpend({ - spendLimit: '99999999999999999999', - }); - cy.get('[data-test=GlmLockTabs__Button]', { timeout: 180000 }).should( - 'have.text', - 'Close', - ); - cy.get('[data-test=GlmLockNotification--success]').should('be.visible'); - cy.get('[data-test=GlmLockTabs__Button]').click(); - cy.get( - '[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', - // Small timeout ensures skeleton shows up quickly after the transaction. - { timeout: 1000 }, - ).should('be.visible'); - // Waiting for skeletons to disappear ensures Graph indexed lock/unlock. - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__DoubleValueSkeleton]', { - timeout: 60000, - }).should('not.exist'); - cy.window().then(async win => { - cy.wrap(null).then(() => { - return moveTime(win, 'nextEpochDecisionWindowClosed', connectWalletParameters).then( - () => { - cy.get('[data-test=BoxGlmLock__Section--current__DoubleValue__primary]', { - timeout: 60000, - }) - .invoke('text') - .then(nextText => { - const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); - expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); - }); - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]', { - timeout: 60000, - }) - .invoke('text') - .then(nextText => { - const lockedGlmsAfterLock = parseInt(nextText.replace(/\u200a/g, ''), 10); - expect(lockedGlms + amountToLock).to.be.eq(lockedGlmsAfterLock); - }); - }, - ); - }); - }); - }); - }); - - it(`check boxes values ${IS_CRYPTO_MAIN_VALUE_DISPLAY}: true`, () => { - checkValues(true); - }); - - it(`check boxes values ${IS_CRYPTO_MAIN_VALUE_DISPLAY}: false`, () => { - checkValues(false); - }); - }); -}); diff --git a/client/cypress/e2e/layout.cy.ts b/client/cypress/e2e/layout.cy.ts deleted file mode 100644 index 20c22785e3..0000000000 --- a/client/cypress/e2e/layout.cy.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { - navigateWithCheck, - mockCoinPricesServer, - connectWallet, - visitWithLoader, -} from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { - HAS_ONBOARDING_BEEN_CLOSED, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { navigationTabs } from 'src/constants/navigationTabs/navigationTabs'; -import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`layout: ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - cy.clearLocalStorage(); - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - cy.disconnectMetamaskWalletFromAllDapps(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT.absolute, ROOT_ROUTES.projects.absolute); - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('renders top bar', () => { - cy.get('[data-test=MainLayout__Header]').should('be.visible'); - }); - - it('Clicking on Octant logo scrolls view to the top on logo click (projects view)', () => { - cy.scrollTo(0, 500); - cy.get('[data-test=MainLayout__Logo]').click(); - // waiting for scrolling to finish - cy.wait(2000); - cy.window().then(cyWindow => { - expect(cyWindow.scrollY).to.be.eq(0); - }); - }); - - it('Clicking on Octant logo redirects to projects view (outside projects view)', () => { - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsView]').should('be.visible'); - cy.get('[data-test=MainLayout__Logo]').click(); - cy.get('[data-test=ProjectsView]').should('be.visible'); - }); - - it('Clicking on Octant logo redirects to projects view (outside projects view) with memorized scrollY', () => { - cy.scrollTo(0, 500); - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsView]').should('be.visible'); - cy.get('[data-test=MainLayout__Logo]').click(); - cy.get('[data-test=ProjectsView]').should('be.visible'); - cy.window().then(cyWindow => { - expect(cyWindow.scrollY).to.be.eq(500); - }); - }); - - it('renders bottom navbar', () => { - cy.get('[data-test=Navbar]').should('be.visible'); - }); - - it('bottom navbar allows to change views', () => { - navigationTabs.forEach(({ to }) => { - navigateWithCheck(to); - }); - }); - - it('"Connect" button is visible when wallet is disconnected', () => { - cy.get('[data-test=MainLayout__Button--connect]').should('be.visible'); - cy.get('[data-test=MainLayout__Button--connect]').click(); - }); - - it('"Connect" button opens "ModalConnectWallet"', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet]').should('be.visible'); - }); - - it('"ModalConnectWallet" always shows "WalletConnect" option', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet]').within(() => { - cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').should('be.visible'); - }); - }); - - it('"ModalConnectWallet" shows "Browser wallet" and "WalletConnect" options (MetaMask wallet detected)', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet]').within(() => { - cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').should('be.visible'); - cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').should('be.visible'); - }); - }); - - it('"ModalConnectWallet" has overflow enabled', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet__overflow]').should('exist'); - }); - - it('Clicking background when "ModalConnectWallet" is open, closes Modal', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet__overflow]').click({ force: true }); - cy.get('[data-test=ModalConnectWallet]').should('not.exist'); - }); - - it('"ModalConnectWallet" has "cross" icon button in header', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet__Button]').should('be.visible'); - }); - - it('Clicking on "X" mark in "ModalConnectWallet", closes Modal', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ModalConnectWallet__Button]').click(); - cy.get('[data-test=ModalConnectWallet]').should('not.exist'); - }); - - // eslint-disable-next-line no-only-tests/no-only-tests - it('Clicking on "WalletConnect" option, opens WalletConnect modal', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - // Wait for RainbowKit to load WalletConnect. - cy.wait(2000); - cy.get('[data-test=ConnectWallet__BoxRounded--walletConnect]').click(); - cy.get('div:contains("Need the WalletConnect modal?")').should('be.visible'); - }); - - it('Clicking on "Browser wallet" option connects with MetaMask wallet', () => { - cy.get('[data-test=MainLayout__Button--connect]').click(); - cy.get('[data-test=ConnectWallet__BoxRounded--browserWallet]').click(); - cy.switchToMetamaskNotification(); - cy.acceptMetamaskAccess(); - cy.get('[data-test=MainLayout__Button--connect]').should('not.exist'); - cy.get('[data-test=ProfileInfo]').should('exist'); - }); - - it('Wallet address is clickable and has href attribute', () => { - connectWallet({ isPatronModeEnabled: false }); - cy.get('[data-test=ProfileInfo]').click(); - cy.get('[data-test=LayoutWallet__Button--address]').should('be.visible'); - cy.get('[data-test=LayoutWallet__Button--address]').should('have.attr', 'href'); - cy.get('[data-test=LayoutWallet__Button--address]').click(); - }); - }); -}); diff --git a/client/cypress/e2e/metrics.cy.ts b/client/cypress/e2e/metrics.cy.ts deleted file mode 100644 index b7bbd0c3c2..0000000000 --- a/client/cypress/e2e/metrics.cy.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { - changeMainValueToFiat, - connectWallet, - mockCoinPricesServer, - visitWithLoader, -} from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { - HAS_ONBOARDING_BEEN_CLOSED, - IS_CRYPTO_MAIN_VALUE_DISPLAY, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -const rendersTilesWithCorrectValues = (isCryptoAsAMainValue: boolean) => { - connectWallet({ isPatronModeEnabled: false }); - if (!isCryptoAsAMainValue) { - changeMainValueToFiat(ROOT_ROUTES.metrics.absolute); - } - - cy.get('[data-test=MetricsEpochGridTopProjects__list__item__value]') - .eq(0) - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0' : '$0.00'); - - cy.get('[data-test=MetricsEpochGridTotalDonationsAndPersonal__totalDonations__value]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - cy.get('[data-test=MetricsEpochGridTotalDonationsAndPersonal__totalDonations__subvalue]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '$0.00' : '0 ETH'); - cy.get('[data-test=MetricsEpochGridTotalDonationsAndPersonal__totalPersonal__value]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - cy.get('[data-test=MetricsEpochGridTotalDonationsAndPersonal__totalPersonal__subvalue]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '$0.00' : '0 ETH'); - - cy.get('[data-test=MetricsEpochGridRewardsUnusedAndUnallocatedValue__unallocatedValue__value]') - .invoke('text') - .should(isCryptoAsAMainValue ? 'not.include' : 'include', '$'); - cy.get('[data-test=MetricsEpochGridRewardsUnusedAndUnallocatedValue__unallocatedValue__subvalue]') - .invoke('text') - .should(isCryptoAsAMainValue ? 'include' : 'not.include', '$'); - - cy.get('[data-test=MetricsEpochGridFundsUsage__total]') - .invoke('text') - .should(isCryptoAsAMainValue ? 'not.include' : 'include', '$'); - - cy.get('[data-test=MetricsPersonalGridTotalRewardsWithdrawals__totalRewards__value]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - cy.get('[data-test=MetricsPersonalGridTotalRewardsWithdrawals__totalRewards__subvalue]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '$0.00' : '0 ETH'); - cy.get('[data-test=MetricsPersonalGridTotalRewardsWithdrawals__totalWithdrawals__value]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '0 ETH' : '$0.00'); - cy.get('[data-test=MetricsPersonalGridTotalRewardsWithdrawals__totalWithdrawals__subvalue]') - .invoke('text') - .should('eq', isCryptoAsAMainValue ? '$0.00' : '0 ETH'); -}; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`metrics: ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.metrics.absolute); - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('renders total projects tile', () => { - cy.get('[data-test=MetricsGeneralGridTotalProjects]').should('be.visible'); - }); - - it('renders total eth staked tile', () => { - cy.get('[data-test=MetricsGeneralGridTotalEthStaked]').should('be.visible'); - }); - - it('renders tile with total glm locked and % of 1B total supply groups', () => { - cy.get('[data-test=MetricsGeneralGridTotalGlmLockedAndTotalSupply]').should('be.visible'); - cy.get('[data-test=MetricsGeneralGridTotalGlmLockedAndTotalSupply]') - .children() - .should('have.length', 2); - }); - - it('renders wallet with glm locked graph tile', () => { - cy.get('[data-test=MetricsGeneralGridWalletsWithGlmLocked]').should('be.visible'); - }); - - it('renders cumulative glm locked graph tile', () => { - cy.get('[data-test=MetricsGeneralGridCumulativeGlmLocked]').should('be.visible'); - }); - - it('renders tiles in correct order', () => { - const metricsEpochGridTilesDataTest = [ - 'MetricsEpochGridTopProjects', - 'MetricsEpochGridTotalDonationsAndPersonal', - 'MetricsEpochGridDonationsVsPersonalAllocations', - 'MetricsEpochGridFundsUsage', - 'MetricsEpochGridTotalUsers', - 'MetricsEpochGridPatrons', - 'MetricsEpochGridCurrentDonors', - 'MetricsEpochGridAverageLeverage', - 'MetricsEpochGridRewardsUnusedAndUnallocatedValue', - ]; - - const metricsGeneralGridTilesDataTest = [ - 'MetricsGeneralGridTotalGlmLockedAndTotalSupply', - 'MetricsGeneralGridTotalProjects', - 'MetricsGeneralGridTotalEthStaked', - 'MetricsGeneralGridCumulativeGlmLocked', - 'MetricsGeneralGridWalletsWithGlmLocked', - ]; - - cy.get('[data-test=MetricsEpoch__MetricsGrid]') - .children() - .should('have.length', metricsEpochGridTilesDataTest.length); - - for (let i = 0; i < metricsEpochGridTilesDataTest.length; i++) { - cy.get('[data-test=MetricsEpoch__MetricsGrid]') - .children() - .eq(i) - .invoke('data', 'test') - .should('eq', metricsEpochGridTilesDataTest[i]); - } - - cy.get('[data-test=MetricsGeneral__MetricsGrid]') - .children() - .should('have.length', metricsGeneralGridTilesDataTest.length); - - for (let i = 0; i < metricsGeneralGridTilesDataTest.length; i++) { - cy.get('[data-test=MetricsGeneral__MetricsGrid]') - .children() - .eq(i) - .invoke('data', 'test') - .should('eq', metricsGeneralGridTilesDataTest[i]); - } - }); - - it('renders grid with 4 columns on desktop or with 2 columns on other devices', () => { - cy.get('[data-test=MetricsEpoch__MetricsGrid]').then(el => { - const width = parseInt(el.css('width'), 10); - const rowGap = parseInt(el.css('rowGap'), 10); - - const columnWidth = isDesktop ? (width - 3 * rowGap) / 4 : (width - rowGap) / 2; - - cy.get('[data-test=MetricsEpoch__MetricsGrid]').should( - 'have.css', - 'grid-template-columns', - isDesktop - ? `${columnWidth}px ${columnWidth}px ${columnWidth}px ${columnWidth}px` - : `${columnWidth}px ${columnWidth}px`, - ); - }); - - cy.get('[data-test=MetricsGeneral__MetricsGrid]').then(el => { - const width = parseInt(el.css('width'), 10); - const rowGap = parseInt(el.css('rowGap'), 10); - - const columnWidth = isDesktop ? (width - 3 * rowGap) / 4 : (width - rowGap) / 2; - - cy.get('[data-test=MetricsGeneral__MetricsGrid]').should( - 'have.css', - 'grid-template-columns', - isDesktop - ? `${columnWidth}px ${columnWidth}px ${columnWidth}px ${columnWidth}px` - : `${columnWidth}px ${columnWidth}px`, - ); - }); - }); - - it(`renders tiles values in correct order ${IS_CRYPTO_MAIN_VALUE_DISPLAY}: true`, () => { - rendersTilesWithCorrectValues(true); - }); - - it(`renders tiles values in correct order ${IS_CRYPTO_MAIN_VALUE_DISPLAY}: false`, () => { - rendersTilesWithCorrectValues(false); - }); - }); -}); diff --git a/client/cypress/e2e/patronMode.cy.ts b/client/cypress/e2e/patronMode.cy.ts deleted file mode 100644 index 7122413999..0000000000 --- a/client/cypress/e2e/patronMode.cy.ts +++ /dev/null @@ -1,745 +0,0 @@ -import { - connectWallet, - mockCoinPricesServer, - navigateWithCheck, - visitWithLoader, -} from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { - HAS_ONBOARDING_BEEN_CLOSED, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`patron mode (disabled): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.settings.absolute); - connectWallet({ isPatronModeEnabled: false }); - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('patron badge should not exist ', () => { - cy.get('[data-test=ProfileInfo__badge]').should('not.exist'); - }); - - it('Patron mode toggle is not checked', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').should('not.be.checked'); - }); - - if (isDesktop) { - it('Patron mode tooltip is visible on hover and has correct text', () => { - cy.get('[data-test=SettingsPatronModeBox__Tooltip]').trigger('mouseover'); - cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]').should('be.visible'); - cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]') - .invoke('text') - .should( - 'eq', - 'Patron mode is for token holders who want to support Octant. It disables allocation to yourself or projects. All rewards go directly to the matching fund with no action required by the patron.', - ); - }); - } - - it('Checking patron mode opens patron mode modal', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=ModalPatronMode]').should('be.visible'); - }); - - it('Patron mode modal last paragraph has correct text', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=SettingsPatronMode__fourthParagraph]') - .invoke('text') - .should('eq', 'Slide the switch below all the way to the right to enable patron mode.'); - }); - - it('Slider is visible', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=PatronModeSlider]').should('be.visible'); - }); - - it('Slider has correct label', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=PatronModeSlider__label]') - .invoke('text') - .should('eq', 'Slide right to confirm'); - }); - - it('Slider button is visible ', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=PatronModeSlider__button]').should('be.visible'); - }); - - it('Slider button has right arrow inside', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=PatronModeSlider__button__arrow]') - .should('be.visible') - .should('have.css', 'transform', 'none'); - }); - - it('Slider button is on the left side', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderLeftDistance = sliderEl[0].getBoundingClientRect().left; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonLeftDistance = sliderButtonEl[0].getBoundingClientRect().left; - - expect(sliderButtonLeftDistance).to.be.eq(sliderLeftDistance + sliderLeftPadding); - }); - }); - }); - - it('Slider button returns to the starting point if user drops it below or equal 50% of slider width', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const pointerDownPageX = sliderButtonDimensions.x; - const pointerMovePageX = sliderButtonDimensions.x + sliderTrackWidth / 2; - - cy.get('[data-test=PatronModeSlider__button]') - .trigger('pointerdown', { - pageX: pointerDownPageX, - }) - .trigger('pointermove', { - pageX: pointerMovePageX, - }) - .wait(1000) - .then(sliderButtonElAfterPointerMove => { - const sliderButtonDimensionsAfterPointerMove = - sliderButtonElAfterPointerMove[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerMove.x).eq(pointerMovePageX); - }) - .trigger('pointerup') - .wait(1000) - .then(sliderButtonElAfterPointerUp => { - const sliderButtonDimensionsAfterPointerUp = - sliderButtonElAfterPointerUp[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerDownPageX); - }); - }); - }); - }); - - it('Slider elements change color while moving slider button', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const slider0PercentagePageX = sliderButtonDimensions.x; - const slider25PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth / 4; - const slider50PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth / 2; - const slider75PercentagePageX = sliderButtonDimensions.x + 3 * (sliderTrackWidth / 4); - const slider100PercentagePageX = sliderButtonDimensions.x + sliderTrackWidth; - - // 0% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointerdown', { - pageX: slider0PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgb(243, 243, 243)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(255, 255, 255)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(23, 23, 23)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '1'); - - // 25% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider25PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(212, 224, 221, 0.95)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(222, 234, 231)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(129, 129, 129)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.75'); - - // 50% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider50PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(175, 204, 197, 0.9)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(183, 211, 204)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(181, 181, 181)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.5'); - - // 75% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider75PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(128, 181, 169, 0.85)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(133, 185, 173)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(221, 221, 221)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.25'); - - // 100% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider100PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(45, 155, 135, 0.8)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(45, 155, 135)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(255, 255, 255)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0'); - }); - }); - }); - - it('Slider button goes to the end of track if user drops it above 50% of slider width + animation after signature', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').check(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const pointerDownPageX = sliderButtonDimensions.x; - const pointerMovePageX = sliderButtonDimensions.x + (sliderTrackWidth / 2 + 1); - const pointerUpPageX = sliderDimensions.right - sliderRightPadding - sliderButtonWidth; - - cy.get('[data-test=PatronModeSlider__button]') - .trigger('pointerdown', { - pageX: pointerDownPageX, - }) - .trigger('pointermove', { - pageX: pointerMovePageX, - }) - .wait(1000) - .then(sliderButtonElAfterPointerMove => { - const sliderButtonDimensionsAfterPointerMove = - sliderButtonElAfterPointerMove[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerMove.x).eq(pointerMovePageX); - }) - .trigger('pointerup') - .wait(1000) - .then(sliderButtonElAfterPointerUp => { - const sliderButtonDimensionsAfterPointerUp = - sliderButtonElAfterPointerUp[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerUpPageX); - }); - - cy.confirmMetamaskSignatureRequest(); - cy.switchToCypressWindow(); - cy.get('[data-test=PatronModeSlider__button]').should('not.exist'); - cy.get('[data-test=PatronModeSlider__label]').should('not.exist'); - cy.get('[data-test=PatronModeSlider__status-label]') - .invoke('text') - .should('eq', 'Patron mode enabled'); - cy.wait(500); - cy.get('[data-test=ModalPatronMode]').should('not.exist'); - }); - }); - }); - }); - - describe(`patron mode (enabled): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.settings.absolute); - connectWallet({ isPatronModeEnabled: true }); - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('patron badge is visible and has correct label, background and text-transform prop', () => { - cy.get('[data-test=ProfileInfo__badge]').should('be.visible'); - cy.get('[data-test=ProfileInfo__badge]').invoke('text').should('eq', 'Patron'); - cy.get('[data-test=ProfileInfo__badge]') - .should('have.css', 'background-color', 'rgb(104, 91, 138)') - .should('have.css', 'text-transform', 'uppercase'); - }); - - it('Navbar has 4 items - projects, earn, metrics, settings', () => { - const navbarChildrenDataTest = [ - 'Navbar__Button--Projects', - 'Navbar__Button--Earn', - 'Navbar__Button--Metrics', - 'Navbar__Button--Settings', - ]; - - cy.get('[data-test=Navbar__buttons]') - .children() - .should('have.length', navbarChildrenDataTest.length); - - for (let i = 0; i < navbarChildrenDataTest.length; i++) { - cy.get('[data-test=Navbar__buttons]') - .children() - .eq(i) - .invoke('data', 'test') - .should('eq', navbarChildrenDataTest[i]); - } - }); - - it('route /allocate redirects to /projects', () => { - visitWithLoader(ROOT_ROUTES.allocation.absolute, ROOT_ROUTES.projects.absolute); - cy.get('[data-test=ProjectsView]').should('be.visible'); - }); - - it('BoxPersonalAllocation has correct title and sections labels', () => { - navigateWithCheck(ROOT_ROUTES.earn.absolute); - cy.get('[data-test=BoxPersonalAllocation__title]') - .invoke('text') - .should('eq', 'Patron earnings'); - cy.get('[data-test=BoxPersonalAllocation__Section__label]') - .eq(0) - .invoke('text') - .should('eq', 'Current epoch'); - cy.get('[data-test=BoxPersonalAllocation__Section__label]') - .eq(1) - .invoke('text') - .should('eq', 'All time'); - }); - - it('Patron mode toggle is checked', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').should('be.checked'); - }); - - if (isDesktop) { - it('Patron mode tooltip is visible on hover and has correct text', () => { - cy.get('[data-test=SettingsPatronModeBox__Tooltip]').trigger('mouseover'); - cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]').should('be.visible'); - cy.get('[data-test=SettingsPatronModeBox__Tooltip__content]') - .invoke('text') - .should( - 'eq', - 'Patron mode is for token holders who want to support Octant. It disables allocation to yourself or projects. All rewards go directly to the matching fund with no action required by the patron.', - ); - }); - } - - it('Unchecking patron mode opens patron mode modal', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=ModalPatronMode]').should('be.visible'); - }); - - it('Patron mode modal last paragraph has correct text', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=SettingsPatronMode__fourthParagraph]') - .invoke('text') - .should('eq', 'Slide the switch below all the way to the left to disable patron mode.'); - }); - - it('Slider is visible', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=PatronModeSlider]').should('be.visible'); - }); - - it('Slider has correct label', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=PatronModeSlider__label]') - .invoke('text') - .should('eq', 'Slide left to confirm'); - }); - - it('Slider button is visible', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=PatronModeSlider__button]').should('be.visible'); - }); - - it('Slider button has left arrow inside', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=PatronModeSlider__button__arrow]') - .should('be.visible') - .should('have.css', 'transform', 'matrix(-1, 0, 0, -1, 0, 0)'); - }); - - it('Slider button is on the right side', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderRightDistance = sliderEl[0].getBoundingClientRect().right; - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonRightDistance = sliderButtonEl[0].getBoundingClientRect().right; - - expect(sliderButtonRightDistance).to.be.eq(sliderRightDistance - sliderRightPadding); - }); - }); - }); - - it('Slider button returns to the starting point if user drops it below or equal 50% of slider width', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const pointerDownPageX = sliderButtonDimensions.right; - const pointerMovePageX = sliderButtonDimensions.right - sliderTrackWidth / 2; - - cy.get('[data-test=PatronModeSlider__button]') - .trigger('pointerdown', { - pageX: pointerDownPageX, - }) - .trigger('pointermove', { - pageX: pointerMovePageX, - }) - .wait(1000) - .then(sliderButtonElAfterPointerMove => { - const sliderButtonDimensionsAfterPointerMove = - sliderButtonElAfterPointerMove[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerMove.right).eq(pointerMovePageX); - }) - .trigger('pointerup') - .wait(1000) - .then(sliderButtonElAfterPointerUp => { - const sliderButtonDimensionsAfterPointerUp = - sliderButtonElAfterPointerUp[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerUp.right).eq(pointerDownPageX); - }); - }); - }); - }); - - it('Slider elements change color while moving slider button', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const slider0PercentagePageX = sliderButtonDimensions.right; - const slider25PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth / 4; - const slider50PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth / 2; - const slider75PercentagePageX = sliderButtonDimensions.right - 3 * (sliderTrackWidth / 4); - const slider100PercentagePageX = sliderButtonDimensions.right - sliderTrackWidth; - - // 0% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointerdown', { - pageX: slider0PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgb(243, 243, 243)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(255, 255, 255)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(23, 23, 23)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '1'); - - // 25% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider25PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(212, 224, 221, 0.95)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(222, 234, 231)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(129, 129, 129)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.75'); - - // 50% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider50PercentagePageX, - waitForAnimations: true, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(175, 204, 197, 0.9)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(183, 211, 204)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(181, 181, 181)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.5'); - - // 75% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider75PercentagePageX, - waitForAnimations: true, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(128, 181, 169, 0.85)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(133, 185, 173)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(221, 221, 221)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0.25'); - - // 100% - cy.get('[data-test=PatronModeSlider__button]').trigger('pointermove', { - pageX: slider100PercentagePageX, - }); - cy.get('[data-test=PatronModeSlider]').should( - 'have.css', - 'background-color', - 'rgba(45, 155, 135, 0.8)', - ); - cy.get('[data-test=PatronModeSlider__button]').should( - 'have.css', - 'background-color', - 'rgb(45, 155, 135)', - ); - cy.get('[data-test=PatronModeSlider__button__arrow__path]').should( - 'have.css', - 'fill', - 'rgb(255, 255, 255)', - ); - cy.get('[data-test=PatronModeSlider__label]') - .should('have.css', 'color', 'rgb(158, 163, 158)') - .should('have.css', 'opacity', '0'); - }); - }); - }); - - it('Slider button goes to the end of track if user drops it above 50% of slider width + animation after signature', () => { - cy.get('[data-test=SettingsPatronModeBox__InputToggle]').uncheck(); - - cy.get('[data-test=PatronModeSlider]').then(sliderEl => { - const sliderDimensions = sliderEl[0].getBoundingClientRect(); - const sliderWidth = sliderDimensions.width; - const sliderLeftPadding = parseInt(sliderEl.css('paddingLeft'), 10); - const sliderRightPadding = parseInt(sliderEl.css('paddingRight'), 10); - - cy.get('[data-test=PatronModeSlider__button]').then(sliderButtonEl => { - const sliderButtonDimensions = sliderButtonEl[0].getBoundingClientRect(); - - const sliderButtonWidth = sliderButtonDimensions.width; - - const sliderTrackWidth = - sliderWidth - sliderLeftPadding - sliderRightPadding - sliderButtonWidth; - - const pointerDownPageX = sliderButtonDimensions.right; - const pointerMovePageX = sliderButtonDimensions.right - (sliderTrackWidth / 2 + 1); - const pointerUpPageX = sliderDimensions.left + sliderLeftPadding; - - cy.get('[data-test=PatronModeSlider__button]') - .trigger('pointerdown', { - pageX: pointerDownPageX, - }) - .trigger('pointermove', { - pageX: pointerMovePageX, - }) - .wait(1000) - .then(sliderButtonElAfterPointerMove => { - const sliderButtonDimensionsAfterPointerMove = - sliderButtonElAfterPointerMove[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerMove.right).eq(pointerMovePageX); - }) - .trigger('pointerup') - .wait(1000) - .then(sliderButtonElAfterPointerUp => { - const sliderButtonDimensionsAfterPointerUp = - sliderButtonElAfterPointerUp[0].getBoundingClientRect(); - expect(sliderButtonDimensionsAfterPointerUp.x).eq(pointerUpPageX); - }); - - cy.confirmMetamaskSignatureRequest(); - cy.switchToCypressWindow(); - cy.get('[data-test=PatronModeSlider__button]').should('not.exist'); - cy.get('[data-test=PatronModeSlider__label]').should('not.exist'); - cy.get('[data-test=PatronModeSlider__status-label]') - .invoke('text') - .should('eq', 'Patron mode disabled'); - cy.wait(500); - cy.get('[data-test=ModalPatronMode]').should('not.exist'); - }); - }); - }); - - it('when entering project view, button icon changes to chevronLeft', () => { - navigateWithCheck(ROOT_ROUTES.projects.absolute); - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - cy.get('[data-test=Navbar__Button--Projects]') - .find('svg') - // HTML tag can't be self-closing in CY. - .should( - 'have.html', - '', - ); - }); - }); -}); diff --git a/client/cypress/e2e/project.cy.ts b/client/cypress/e2e/project.cy.ts deleted file mode 100644 index 32f4e42119..0000000000 --- a/client/cypress/e2e/project.cy.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { - changeMainValueToFiat, - checkProjectsViewLoaded, - connectWallet, - mockCoinPricesServer, - visitWithLoader, -} from 'cypress/utils/e2e'; -import { getNamesOfProjects } from 'cypress/utils/projects'; -import viewports from 'cypress/utils/viewports'; -import { - HAS_ONBOARDING_BEEN_CLOSED, - IS_CRYPTO_MAIN_VALUE_DISPLAY, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -import Chainable = Cypress.Chainable; - -const getButtonAddToAllocate = (): Chainable => { - const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); - - return projectListItemFirst.find('[data-test=ProjectListItemHeader__ButtonAddToAllocate]'); -}; - -const checkProjectItemElements = (): Chainable => { - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); - projectListItemFirst.get('[data-test=ProjectListItemHeader__Img]').should('be.visible'); - projectListItemFirst.get('[data-test=ProjectListItemHeader__name]').should('be.visible'); - getButtonAddToAllocate().should('be.visible'); - projectListItemFirst.get('[data-test=ProjectListItemHeader__Button]').should('be.visible'); - projectListItemFirst.get('[data-test=ProjectListItem__Description]').should('be.visible'); - - cy.get('[data-test=ProjectListItem__Donors]') - .first() - .scrollIntoView({ offset: { left: 0, top: 100 } }); - - cy.get('[data-test=ProjectListItem__Donors]').first().should('be.visible'); - cy.get('[data-test=ProjectListItem__Donors__DonorsHeader__count]') - .first() - .should('be.visible') - .should('have.text', '0'); - return cy.get('[data-test=ProjectListItem__Donors__noDonationsYet]').first().should('be.visible'); -}; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`project: ${device}`, { viewportHeight, viewportWidth }, () => { - let projectNames: string[] = []; - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - checkProjectsViewLoaded(); - - /** - * This could be done in before hook, but CY wipes the state after each test - * (could be disabled, but creates other problems) - */ - if (projectNames.length === 0) { - projectNames = getNamesOfProjects(); - } - }); - - it('entering project view directly renders content', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - cy.reload(); - const projectListItemFirst = cy.get('[data-test=ProjectListItem').first(); - projectListItemFirst.get('[data-test=ProjectListItemHeader__Img]').should('be.visible'); - projectListItemFirst.get('[data-test=ProjectListItemHeader__name]').should('be.visible'); - }); - - it('entering project view renders all its elements', () => { - checkProjectItemElements(); - }); - - it('entering project view renders all its elements with fallback IPFS provider', () => { - cy.intercept('GET', '**/ipfs/**', req => { - if (req.url.includes('infura')) { - req.destroy(); - } - }); - - checkProjectItemElements(); - }); - - it('entering project view allows to add it to allocation and remove, triggering change of the icon, change of the number in navbar', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - - getButtonAddToAllocate().click(); - - // cy.get('@buttonAddToAllocate').click(); - cy.get('[data-test=Navbar__numberOfAllocations]').contains(1); - getButtonAddToAllocate().click(); - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - }); - - it('Entering project view allows scroll only to the last project', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); - - for (let i = 0; i < projectNames.length; i++) { - cy.get('[data-test=ProjectListItem]').should( - 'have.length.greaterThan', - i === projectNames.length - 1 ? projectNames.length - 1 : i, - ); - cy.get('[data-test=ProjectListItemHeader__name]') - .eq(i) - .scrollIntoView({ offset: { left: 0, top: -150 } }) - .should('be.visible'); - cy.get('[data-test=ProjectListItem__Donors]') - .eq(i) - .scrollIntoView({ offset: { left: 0, top: -150 } }) - .should('be.visible'); - } - }); - - it('"Back to top" button is displayed if the user has scrolled past the start of the final project description', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); - - for (let i = 0; i < projectNames.length - 1; i++) { - cy.get('[data-test=ProjectListItem__Donors]') - .eq(i) - .scrollIntoView({ offset: { left: 0, top: 100 } }); - - if (i === projectNames.length - 1) { - cy.get('[data-test=ProjectBackToTopButton__Button]').should('be.visible'); - } - } - }); - - it('Clicking on "Back to top" button scrolls to the top of view (first project is visible)', () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').first().click(); - - for (let i = 0; i < projectNames.length - 1; i++) { - cy.get('[data-test=ProjectListItem__Donors]') - .eq(i) - .scrollIntoView({ offset: { left: 0, top: 100 } }); - - if (i === projectNames.length - 1) { - cy.get('[data-test=ProjectBackToTopButton__Button]').click(); - cy.get('[data-test=ProjectListItem]').eq(0).should('be.visible'); - } - } - }); - - it(`shows current total (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: true)`, () => { - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - cy.get('[data-test=ProjectRewards__currentTotal__number]') - .first() - .invoke('text') - .should('eq', '0 ETH'); - }); - it(`shows current total (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: false)`, () => { - changeMainValueToFiat(ROOT_ROUTES.projects.absolute); - cy.get('[data-test^=ProjectsView__ProjectsListItem').first().click(); - cy.get('[data-test=ProjectRewards__currentTotal__number]') - .first() - .invoke('text') - .should('eq', '$0.00'); - }); - }); - - describe(`projects (IPFS failure): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - cy.intercept('GET', '**/ipfs/**', req => { - req.destroy(); - }); - - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - }); - - it('entering project view shows Toast with info about IPFS failure when all providers fail', () => { - cy.get('[data-test=Toast--ipfsMessage').should('be.visible'); - }); - }); - - describe(`project (patron mode): ${device}`, { viewportHeight, viewportWidth }, () => { - let projectNames: string[] = []; - - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - connectWallet({ isPatronModeEnabled: true }); - checkProjectsViewLoaded(); - - /** - * This could be done in before hook, but CY wipes the state after each test - * (could be disabled, but creates other problems) - */ - if (projectNames.length === 0) { - projectNames = getNamesOfProjects(); - } - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('button "add to allocate" is disabled', () => { - for (let i = 0; i < projectNames.length; i++) { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).click(); - getButtonAddToAllocate().should('be.visible').should('be.disabled'); - cy.go('back'); - } - }); - }); -}); diff --git a/client/cypress/e2e/projects.cy.ts b/client/cypress/e2e/projects.cy.ts deleted file mode 100644 index 7d8c010057..0000000000 --- a/client/cypress/e2e/projects.cy.ts +++ /dev/null @@ -1,310 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import chaiColors from 'chai-colors'; - -import { - connectWallet, - mockCoinPricesServer, - visitWithLoader, - navigateWithCheck, - checkProjectsViewLoaded, - changeMainValueToFiat, -} from 'cypress/utils/e2e'; -import { moveTime, setupAndMoveToPlayground } from 'cypress/utils/moveTime'; -import { getNamesOfProjects } from 'cypress/utils/projects'; -import viewports from 'cypress/utils/viewports'; -import { QUERY_KEYS } from 'src/api/queryKeys'; -import { - HAS_ONBOARDING_BEEN_CLOSED, - IS_CRYPTO_MAIN_VALUE_DISPLAY, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import getMilestones from 'src/constants/milestones'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -import Chainable = Cypress.Chainable; - -chai.use(chaiColors); - -describe('move time', () => { - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - it('allocation window is open, when it is not, move time', () => { - setupAndMoveToPlayground(); - - cy.window().then(async win => { - moveTime(win, 'nextEpochDecisionWindowOpen').then(() => { - const isDecisionWindowOpenAfter = win.clientReactQuery.getQueryData( - QUERY_KEYS.isDecisionWindowOpen, - ); - expect(isDecisionWindowOpenAfter).to.be.true; - }); - }); - }); -}); - -function checkProjectItemElements(index, name, isPatronMode = false): Chainable { - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__imageProfile]') - .should('be.visible'); - cy.get('[data-test^=ProjectsView__ProjectsListItem]') - .eq(index) - .should('be.visible') - .find('[data-test=ProjectsListItem__name]') - .should('be.visible') - .contains(name); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__IntroDescription]') - .should('be.visible'); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .should('be.visible'); - - if (isPatronMode) { - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .should('be.disabled'); - } - - return cy - .get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectRewards]') - .should('be.visible'); - // TODO OCT-663 Make CY check if rewards are available (Epoch 2, decision window open). - // return cy - // .get('[data-test^=ProjectsView__ProjectsListItem') - // .eq(index) - // .find('[data-test=ProjectRewards__currentTotal__label]') - // .should('be.visible'); -} - -function addProjectToAllocate(index, numberOfAddedProjects): Chainable { - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__imageProfile]') - .should('be.visible'); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__IntroDescription]') - .should('be.visible'); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .scrollIntoView(); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .click(); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .find('svg') - .find('path') - .then($el => $el.css('fill')) - .should('be.colored', '#FF6157'); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .find('svg') - .find('path') - .then($el => $el.css('stroke')) - .should('be.colored', '#FF6157'); - cy.get('[data-test=Navbar__numberOfAllocations]').contains(numberOfAddedProjects + 1); - navigateWithCheck(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationItem]').should('have.length', numberOfAddedProjects + 1); - return cy.go('back'); -} - -function removeProjectFromAllocate(numberOfProjects, numberOfAddedProjects, index): Chainable { - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .scrollIntoView(); - cy.get('[data-test^=ProjectsView__ProjectsListItem') - .eq(index) - .find('[data-test=ProjectsListItem__ButtonAddToAllocate]') - .click(); - navigateWithCheck(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationItem]').should('have.length', numberOfAddedProjects - 1); - if (index < numberOfProjects - 1) { - cy.get('[data-test=Navbar__numberOfAllocations]').contains(numberOfAddedProjects - 1); - } else { - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - } - return cy.go('back'); -} - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`projects: ${device}`, { viewportHeight, viewportWidth }, () => { - let projectNames: string[] = []; - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - checkProjectsViewLoaded(); - - /** - * This could be done in before hook, but CY wipes the state after each test - * (could be disabled, but creates other problems) - */ - if (projectNames.length === 0) { - projectNames = getNamesOfProjects(); - } - }); - - it('user is able to see all the projects in the view', () => { - for (let i = 0; i < projectNames.length; i++) { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).scrollIntoView(); - checkProjectItemElements(i, projectNames[i]); - } - }); - - it('user is able to add & remove the first and the last project to/from allocation, triggering change of the icon, change of the number in navbar', () => { - // This test checks the first and the last elements only to save time. - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - - addProjectToAllocate(0, 0); - addProjectToAllocate(projectNames.length - 1, 1); - removeProjectFromAllocate(projectNames.length, 2, 0); - removeProjectFromAllocate(projectNames.length, 1, projectNames.length - 1); - }); - - it('user is able to add project to allocation in ProjectsView and remove it from allocation in AllocationView', () => { - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - addProjectToAllocate(0, 0); - navigateWithCheck(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationItemSkeleton]').should('not.exist'); - cy.get('[data-test=AllocationItem]').then(el => { - const { x } = el[0].getBoundingClientRect(); - cy.get('[data-test=AllocationItem]') - .trigger('pointerdown') - .trigger('pointermove', { pageX: x - 20 }) - .trigger('pointerup', { pageX: x - 40 }); - cy.wait(500); - cy.get('[data-test=AllocationItem__removeButton]').should('be.visible'); - cy.get('[data-test=AllocationItem__removeButton]').click(); - cy.get('[data-test=AllocationItem__removeButton]').should('not.exist'); - cy.get('[data-test=AllocationItem]').should('not.exist'); - cy.get('[data-test=Navbar__numberOfAllocations]').should('not.exist'); - }); - }); - - it('ProjectsTimelineWidgetItem with href opens link when clicked without mouse movement', () => { - const milestones = getMilestones(); - cy.get('[data-test=ProjectsTimelineWidget]').should('be.visible'); - cy.get('[data-test=ProjectsTimelineWidgetItem]').should('have.length', milestones.length); - for (let i = 0; i < milestones.length; i++) { - if (milestones[i].href) { - cy.get('[data-test=ProjectsTimelineWidgetItem]') - .eq(i) - .within(() => { - cy.get('[data-test=ProjectsTimelineWidgetItem__Svg--arrowTopRight]').should( - 'be.visible', - ); - }); - - cy.get('[data-test=ProjectsTimelineWidgetItem]') - .eq(i) - .then(el => { - const { x } = el[0].getBoundingClientRect(); - cy.get('[data-test=ProjectsTimelineWidgetItem]') - .eq(i) - .trigger('mousedown') - .trigger('mouseup', { clientX: x + 10 }); - cy.location('pathname').should('eq', ROOT_ROUTES.projects.absolute); - - cy.get('[data-test=ProjectsTimelineWidgetItem]') - .eq(i) - .trigger('mousedown') - .trigger('mouseup'); - cy.location('pathname').should('not.eq', ROOT_ROUTES.projects.absolute); - }); - } - } - }); - - it(`shows current total (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: true)`, () => { - cy.get('[data-test=ProjectRewards__currentTotal__number]') - .first() - .invoke('text') - .should('eq', '0 ETH'); - }); - - it(`shows current total (${IS_CRYPTO_MAIN_VALUE_DISPLAY}: false)`, () => { - changeMainValueToFiat(ROOT_ROUTES.projects.absolute); - - cy.get('[data-test=ProjectRewards__currentTotal__number]') - .first() - .invoke('text') - .should('eq', '$0.00'); - }); - - it('search field -- results should show project', () => { - cy.get('[data-test=ProjectsList__InputText]').clear().type(projectNames[0]); - cy.get('[data-test=ProjectsView__ProjectsList]') - .find('[data-test^=ProjectsView__ProjectsListItem]') - .should('have.length', 1); - }); - - it('search field -- no results should show no results image & text', () => { - cy.get('[data-test=ProjectsList__InputText]') - .clear() - .type('there-is-no-way-there-will-ever-be-a-project-with-such-a-name'); - cy.get('[data-test=ProjectsList__noSearchResults]').should('be.visible'); - cy.get('[data-test=ProjectsList__noSearchResults__Img]').should('be.visible'); - }); - }); - - describe(`projects (patron mode): ${device}`, { viewportHeight, viewportWidth }, () => { - let projectNames: string[] = []; - - before(() => { - /** - * Global Metamask setup done by Synpress is not always done. - * Since Synpress needs to have valid provider to fetch the data from contracts, - * setupMetamask is required in each test suite. - */ - cy.setupMetamask(); - }); - - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - connectWallet({ isPatronModeEnabled: true }); - checkProjectsViewLoaded(); - /** - * This could be done in before hook, but CY wipes the state after each test - * (could be disabled, but creates other problems) - */ - if (projectNames.length === 0) { - projectNames = getNamesOfProjects(); - } - }); - - after(() => { - cy.disconnectMetamaskWalletFromAllDapps(); - }); - - it('button "add to allocate" is disabled', () => { - for (let i = 0; i < projectNames.length; i++) { - cy.get('[data-test^=ProjectsView__ProjectsListItem]').eq(i).scrollIntoView(); - checkProjectItemElements(i, projectNames[i], true); - } - }); - }); -}); diff --git a/client/cypress/e2e/projectsArchive.cy.ts b/client/cypress/e2e/projectsArchive.cy.ts deleted file mode 100644 index be0fbb7611..0000000000 --- a/client/cypress/e2e/projectsArchive.cy.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { - checkLocationWithLoader, - visitWithLoader, - checkProjectsViewLoaded, -} from 'cypress/utils/e2e'; -import { moveTime } from 'cypress/utils/moveTime'; -import viewports from 'cypress/utils/viewports'; -import { QUERY_KEYS } from 'src/api/queryKeys'; -import { - HAS_ONBOARDING_BEEN_CLOSED, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -let wasEpochMoved = false; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`projects archive: ${device}`, { viewportHeight, viewportWidth }, () => { - beforeEach(() => { - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.projects.absolute); - }); - - it('moves to the next epoch', () => { - // Move time only once, for the first device. - if (!wasEpochMoved) { - cy.window().then(async win => { - const currentEpochBefore = Number( - win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), - ); - - cy.wrap(null).then(() => { - return moveTime(win, 'nextEpochDecisionWindowClosed').then(() => { - const currentEpochAfter = Number( - win.clientReactQuery.getQueryData(QUERY_KEYS.currentEpoch), - ); - wasEpochMoved = true; - expect(currentEpochBefore + 1).to.eq(currentEpochAfter); - }); - }); - }); - } else { - expect(true).to.be.true; - } - }); - - it('renders archive elements + clicking on epoch archive ProjectsListItem opens ProjectView for particular epoch and project', () => { - cy.get('[data-test=MainLayout__body]').then(el => { - const mainLayoutPaddingTop = parseInt(el.css('paddingTop'), 10); - - checkProjectsViewLoaded(); - cy.get('[data-test=ProjectsView__ProjectsList]') - .should('be.visible') - .children() - .then(children => { - children[children.length - 1].scrollIntoView(); - cy.window().then(window => window.scrollTo(0, window.scrollY - mainLayoutPaddingTop)); - cy.wait(1000); - // header test - cy.get('[data-test=ProjectsView__ProjectsList__header--archive]').should('be.visible'); - - // list test - cy.get('[data-test=ProjectsView__ProjectsList--archive]').first().should('be.visible'); - checkProjectsViewLoaded(); - cy.get('[data-test=ProjectsView__ProjectsList--archive]') - .first() - .children() - .then(childrenArchive => { - const numberOfArchivedProjects = childrenArchive.length - 2; // archived projects tiles - (header + divider)[2] - for (let i = 0; i < numberOfArchivedProjects; i++) { - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .scrollIntoView(); - cy.window().then(window => - window.scrollTo(0, window.scrollY - mainLayoutPaddingTop), - ); - // list item test - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .should('be.visible') - .within(() => { - // rewards test - cy.get('[data-test=ProjectRewards]').should('be.visible'); - }); - - if (numberOfArchivedProjects - 1) { - cy.get('[data-test=ProjectsView__ProjectsList--archive]') - .first() - .should('have.length', 1); - } - - checkProjectsViewLoaded(); - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .invoke('data', 'address') - .then(address => { - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .invoke('data', 'epoch') - .then(epoch => { - cy.get(`[data-test=ProjectsView__ProjectsListItem--archive--${i}]`) - .first() - .click(); - checkLocationWithLoader( - `${ROOT_ROUTES.project.absolute}/${epoch}/${address}`, - ); - cy.go('back'); - checkLocationWithLoader(ROOT_ROUTES.projects.absolute); - }); - }); - } - }); - }); - }); - }); - }); -}); diff --git a/client/cypress/e2e/rewardsCalculator.cy.ts b/client/cypress/e2e/rewardsCalculator.cy.ts deleted file mode 100644 index 931b7f48ba..0000000000 --- a/client/cypress/e2e/rewardsCalculator.cy.ts +++ /dev/null @@ -1,392 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import chaiColors from 'chai-colors'; - -import { - ETH_USD, - GLM_USD, - changeMainValueToFiat, - mockCoinPricesServer, - visitWithLoader, - changeMainValueToCrypto, -} from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { - HAS_ONBOARDING_BEEN_CLOSED, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; -import getValueCryptoToDisplay from 'src/utils/getValueCryptoToDisplay'; -import getValueFiatToDisplay from 'src/utils/getValueFiatToDisplay'; -import { parseUnitsBigInt } from 'src/utils/parseUnitsBigInt'; - -import Chainable = Cypress.Chainable; - -chai.use(chaiColors); - -const rendersWithCorrectValues = ({ - isCryptoAsAMainValue, - onAfterInterceptCallback, - onAfterOpenCallback, - postAlias, -}: { - isCryptoAsAMainValue: boolean; - onAfterInterceptCallback?: () => Chainable; - onAfterOpenCallback?: () => Chainable; - postAlias: string; -}) => { - cy.intercept('POST', '/rewards/estimated_budget').as(postAlias); - - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - - if (onAfterOpenCallback) { - onAfterOpenCallback(); - } - - cy.get('[data-test=EarnRewardsCalculatorEstimates__rewardsValue--skeleton]').should('be.visible'); - cy.get('[data-test=EarnRewardsCalculatorEstimates__matchFundingValue--skeleton]').should( - 'be.visible', - ); - - cy.wait(`@${postAlias}`); - - cy.get(`@${postAlias}`).then( - ({ - response: { - body: { budget, matchedFunding }, - }, - }) => { - const rewardsCrypto = getValueCryptoToDisplay({ - cryptoCurrency: 'ethereum', - valueCrypto: parseUnitsBigInt(budget, 'wei'), - }).fullString; - const rewardsFiat = getValueFiatToDisplay({ - cryptoCurrency: 'ethereum', - cryptoValues: { ethereum: { usd: ETH_USD }, golem: { usd: GLM_USD } }, - displayCurrency: 'usd', - valueCrypto: parseUnitsBigInt(budget, 'wei'), - }); - - const matchFundingCrypto = getValueCryptoToDisplay({ - cryptoCurrency: 'ethereum', - valueCrypto: parseUnitsBigInt(matchedFunding, 'wei'), - }).fullString; - const matchFundingFiat = getValueFiatToDisplay({ - cryptoCurrency: 'ethereum', - cryptoValues: { ethereum: { usd: ETH_USD }, golem: { usd: GLM_USD } }, - displayCurrency: 'usd', - valueCrypto: parseUnitsBigInt(matchedFunding, 'wei'), - }); - const rewards = isCryptoAsAMainValue ? rewardsCrypto : rewardsFiat; - const matchFunding = isCryptoAsAMainValue ? matchFundingCrypto : matchFundingFiat; - - cy.get('[data-test=EarnRewardsCalculatorEstimates__rewardsValue--skeleton]').should( - 'not.exist', - ); - cy.get('[data-test=EarnRewardsCalculatorEstimates__matchFundingValue--skeleton]').should( - 'not.exist', - ); - - cy.get('[data-test=EarnRewardsCalculatorEstimates__rewardsValue') - .invoke('text') - .should('eq', rewards); - cy.get('[data-test=EarnRewardsCalculatorEstimates__matchFundingValue]') - .invoke('text') - .should('eq', matchFunding); - - if (onAfterInterceptCallback) { - onAfterInterceptCallback(); - } - }, - ); - - cy.get('[data-test=ModalRewardsCalculator__Button]').click(); -}; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`rewards calculator: ${device}`, { viewportHeight, viewportWidth }, () => { - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.earn.absolute); - }); - - it('renders calculator icon inside box', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').should('be.visible'); - }); - - if (isDesktop) { - it('tooltip is visible on calculator icon hover and has correct text', () => { - cy.get('[data-test=Tooltip__rewardsCalculator').trigger('mouseover'); - cy.get('[data-test=Tooltip__rewardsCalculator__content') - .should('be.visible') - .invoke('text') - .should('eq', 'Calculate rewards'); - }); - } - - it('clicking on rewards calculator icon opens rewards calculator modal', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=ModalRewardsCalculator]').should('be.visible'); - }); - - it('GLM amount input is visible, has "5000" as a default value and a "GLM" suffix', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=EarnRewardsCalculator__InputText--glm]').should('be.visible'); - cy.get('[data-test=EarnRewardsCalculator__InputText--glm]') - .invoke('val') - .should('eq', '5000'); - cy.get('[data-test=EarnRewardsCalculator__InputText--glm__suffix]').should('be.visible'); - cy.get('[data-test=EarnRewardsCalculator__InputText--glm__suffix]') - .invoke('text') - .should('eq', 'GLM'); - }); - - it('Days selector is visible, has 3 options (90, 180, 270), "90" as a default value and "DAYS" suffix', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=EarnRewardsCalculatorEpochDaysSelector]').should('be.visible'); - cy.get('[data-test*=EarnRewardsCalculatorEpochDaysSelector__label]') - .invoke('text') - .should('eq', 'Lock for 1 epoch'); - cy.get('[data-test*=EarnRewardsCalculatorEpochDaysSelector__option--]').then(options => { - for (let i = 1; i <= options.length; i++) { - cy.get(`[data-test=EarnRewardsCalculatorEpochDaysSelector__option--${i}]`) - .then($el => $el.css('color')) - .should('be.colored', i === 1 ? '#171717' : '#cdd1cd'); - cy.get( - `[data-test=EarnRewardsCalculatorEpochDaysSelector__optionBackground--${i}]`, - ).should(i === 1 ? 'exist' : 'not.exist'); - - if (i === 1) { - cy.get(`[data-test=EarnRewardsCalculatorEpochDaysSelector__optionBackground--${i}]`) - .then($el => $el.css('background-color')) - .should('be.colored', '#ebebeb'); - } - cy.get(`[data-test=EarnRewardsCalculatorEpochDaysSelector__optionLabel--${i}]`) - .invoke('text') - .should('eq', `${i * 90}`); - } - }); - cy.get('[data-test*=EarnRewardsCalculatorEpochDaysSelector__suffix]') - .invoke('text') - .should('eq', 'Days'); - }); - - it('UQ selector is visible, has 2 options (Yes, No), "Yes" as a default value', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=EarnRewardsCalculatorUqSelector]').should('be.visible'); - cy.get('[data-test*=EarnRewardsCalculatorUqSelector__option--]').then(options => { - for (let i = 0; i < options.length; i++) { - cy.get(`[data-test=EarnRewardsCalculatorUqSelector__option--${i}]`) - .then($el => $el.css('color')) - .should('be.colored', i === 0 ? '#171717' : '#cdd1cd'); - cy.get(`[data-test=EarnRewardsCalculatorUqSelector__optionBackground--${i}]`).should( - i === 0 ? 'exist' : 'not.exist', - ); - - if (i === 0) { - cy.get(`[data-test=EarnRewardsCalculatorUqSelector__optionBackground--${i}]`) - .then($el => $el.css('background-color')) - .should('be.colored', '#ebebeb'); - } - cy.get(`[data-test=EarnRewardsCalculatorUqSelector__optionLabel--${i}]`) - .invoke('text') - .should('eq', i === 0 ? 'Yes' : 'No'); - } - }); - }); - - it('Estimates box is visible and has "Rewards" and "Match funding" fields', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=EarnRewardsCalculatorEstimates]').should('be.visible'); - cy.get('[data-test=EarnRewardsCalculatorEstimates__label]') - .invoke('text') - .should('eq', 'Estimates'); - - cy.get('[data-test=EarnRewardsCalculatorEstimates__rewards]').should('be.visible'); - cy.get('[data-test=EarnRewardsCalculatorEstimates__rewardsLabel]') - .invoke('text') - .should('eq', 'Rewards '); - - cy.get('[data-test=EarnRewardsCalculatorEstimates__matchFunding]').should('be.visible'); - cy.get('[data-test=EarnRewardsCalculatorEstimates__matchFundingLabel]') - .invoke('text') - .should('eq', 'Match funding'); - }); - - it('User can change days selector value', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=EarnRewardsCalculatorEpochDaysSelector__option--2]').click(); - cy.wait(500); - cy.get(`[data-test=EarnRewardsCalculatorEpochDaysSelector__option--2]`) - .then($el => $el.css('color')) - .should('be.colored', '#171717'); - cy.get(`[data-test=EarnRewardsCalculatorEpochDaysSelector__optionBackground--2]`).should( - 'exist', - ); - cy.get(`[data-test=EarnRewardsCalculatorEpochDaysSelector__optionBackground--2]`) - .then($el => $el.css('background-color')) - .should('be.colored', '#ebebeb'); - - cy.get('[data-test=EarnRewardsCalculatorEpochDaysSelector__option--3]').click(); - cy.wait(500); - - cy.get(`[data-test=EarnRewardsCalculatorEpochDaysSelector__option--2]`) - .then($el => $el.css('color')) - .should('be.colored', '#cdd1cd'); - cy.get(`[data-test=EarnRewardsCalculatorEpochDaysSelector__optionBackground--2]`).should( - 'not.exist', - ); - - cy.get(`[data-test=EarnRewardsCalculatorEpochDaysSelector__option--3]`) - .then($el => $el.css('color')) - .should('be.colored', '#171717'); - cy.get(`[data-test=EarnRewardsCalculatorEpochDaysSelector__optionBackground--3]`).should( - 'exist', - ); - cy.get(`[data-test=EarnRewardsCalculatorEpochDaysSelector__optionBackground--3]`) - .then($el => $el.css('background-color')) - .should('be.colored', '#ebebeb'); - }); - - it('Calculator shows "Rewards" and "Match funding" in USD based on GLM input value and days selector option', () => { - changeMainValueToCrypto(ROOT_ROUTES.earn.absolute); - - rendersWithCorrectValues({ - isCryptoAsAMainValue: true, - postAlias: 'postEstimatedRewards-true', - }); - rendersWithCorrectValues({ - isCryptoAsAMainValue: true, - onAfterOpenCallback: () => { - return cy.get('[data-test=EarnRewardsCalculator__InputText--glm]').type('500000'); - }, - postAlias: 'postEstimatedRewardsGlmValueChange-true', - }); - rendersWithCorrectValues({ - isCryptoAsAMainValue: true, - onAfterOpenCallback: () => { - return cy.get('[data-test=EarnRewardsCalculatorEpochDaysSelector__option--2]').click(); - }, - postAlias: 'postEstimatedRewardsDaysValueChange-true', - }); - - changeMainValueToFiat(ROOT_ROUTES.earn.absolute); - - rendersWithCorrectValues({ - isCryptoAsAMainValue: false, - postAlias: 'postEstimatedRewards-false', - }); - rendersWithCorrectValues({ - isCryptoAsAMainValue: false, - onAfterOpenCallback: () => { - return cy.get('[data-test=EarnRewardsCalculator__InputText--glm]').type('500000'); - }, - postAlias: 'postEstimatedRewardsGlmValueChange-false', - }); - rendersWithCorrectValues({ - isCryptoAsAMainValue: false, - onAfterOpenCallback: () => { - return cy.get('[data-test=EarnRewardsCalculatorEpochDaysSelector__option--2]').click(); - }, - postAlias: 'postEstimatedRewardsDaysValueChange-false', - }); - }); - - it('If GLM input is empty estimates section fields are empty too', () => { - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.get('[data-test=EarnRewardsCalculator__InputText--glm]').clear(); - cy.get('[data-test=EarnRewardsCalculatorEstimates__rewardsValue--skeleton]').should( - 'not.exist', - ); - cy.get('[data-test=EarnRewardsCalculatorEstimates__matchFundingValue--skeleton]').should( - 'not.exist', - ); - // Debouce prevents value from being immediately loaded. Let's give it a chance to load. - cy.wait(5000); - cy.get('[data-test=EarnRewardsCalculatorEstimates__matchFundingValue]') - .invoke('text') - .should('eq', ''); - }); - - it('Max GLM amount is 1000000000', () => { - changeMainValueToCrypto(ROOT_ROUTES.earn.absolute); - - rendersWithCorrectValues({ - isCryptoAsAMainValue: true, - onAfterInterceptCallback: () => { - cy.get('[data-test=EarnRewardsCalculator__InputText--glm]') - .clear() - .type('1000000001') - .should('have.css', 'border-color', 'rgb(255, 97, 87)'); - cy.get('[data-test=EarnRewardsCalculator__InputText--glm__error]') - .should('be.visible') - .invoke('text') - .should('eq', 'That isn’t a valid amount'); - }, - onAfterOpenCallback: () => { - return cy.get('[data-test=EarnRewardsCalculator__InputText--glm]').type('1000000000'); - }, - postAlias: 'postEstimatedRewards-true', - }); - - changeMainValueToFiat(ROOT_ROUTES.earn.absolute); - - rendersWithCorrectValues({ - isCryptoAsAMainValue: false, - onAfterInterceptCallback: () => { - cy.get('[data-test=EarnRewardsCalculator__InputText--glm]') - .clear() - .type('1000000001') - .should('have.css', 'border-color', 'rgb(255, 97, 87)'); - cy.get('[data-test=EarnRewardsCalculator__InputText--glm__error]') - .should('be.visible') - .invoke('text') - .should('eq', 'That isn’t a valid amount'); - }, - onAfterOpenCallback: () => { - return cy.get('[data-test=EarnRewardsCalculator__InputText--glm]').type('1000000000'); - }, - postAlias: 'postEstimatedRewards-false', - }); - }); - - it('Closing the modal successfully cancels the request /estimated_budget', () => { - cy.window().then(win => { - cy.spy(win.console, 'error').as('consoleErrSpy'); - }); - - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - - cy.get('[data-test=ModalRewardsCalculator__Button]').click(); - cy.get('[data-test=ModalRewardsCalculator').should('not.be.visible'); - - cy.on('uncaught:exception', error => { - expect(error.code).to.equal('ERR_CANCELED'); - }); - }); - - it('Estimates section shows correct fiat values', () => { - changeMainValueToFiat(ROOT_ROUTES.earn.absolute); - - cy.intercept('POST', '/rewards/estimated_budget', { - body: { budget: '18829579190901', matchedFunding: '18829579190901' }, - delay: 500, - }).as('postEstimatedRewards'); - - cy.get('[data-test=Tooltip__rewardsCalculator__body]').click(); - cy.wait('@postEstimatedRewards'); - - cy.get('@postEstimatedRewards').then(() => { - cy.get('[data-test=EarnRewardsCalculatorEstimates__rewardsValue') - .invoke('text') - .should('eq', '$0.04'); - cy.get('[data-test=EarnRewardsCalculatorEstimates__matchFundingValue]') - .invoke('text') - .should('eq', '$0.04'); - }); - }); - }); -}); diff --git a/client/cypress/e2e/routes.cy.ts b/client/cypress/e2e/routes.cy.ts deleted file mode 100644 index 1d2dc113b8..0000000000 --- a/client/cypress/e2e/routes.cy.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { mockCoinPricesServer, visitWithLoader } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { ROOT, ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight }) => { - describe(`routes (wallet not connected): ${device}`, { viewportHeight, viewportWidth }, () => { - before(() => { - mockCoinPricesServer(); - cy.clearLocalStorage(); - }); - - it('empty route redirects to projects view', () => { - visitWithLoader(ROOT.absolute, ROOT_ROUTES.projects.absolute); - cy.get('[data-test=ProjectsView]').should('be.visible'); - }); - - it('allocation route redirects to allocation view', () => { - visitWithLoader(ROOT_ROUTES.allocation.absolute); - cy.get('[data-test=AllocationView]').should('be.visible'); - }); - - it('earn route redirects to earn view', () => { - visitWithLoader(ROOT_ROUTES.earn.absolute); - cy.get('[data-test=EarnView]').should('be.visible'); - }); - - it('metrics route redirects to metrics view', () => { - visitWithLoader(ROOT_ROUTES.metrics.absolute); - cy.get('[data-test=MetricsView]').should('be.visible'); - }); - - it('projects route redirects to projects view', () => { - visitWithLoader(ROOT_ROUTES.projects.absolute); - cy.get('[data-test=ProjectsView]').should('be.visible'); - }); - - it('settings route redirects to settings view', () => { - visitWithLoader(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsView]').should('be.visible'); - }); - }); -}); diff --git a/client/cypress/e2e/settings.cy.ts b/client/cypress/e2e/settings.cy.ts deleted file mode 100644 index 36e4b2ca43..0000000000 --- a/client/cypress/e2e/settings.cy.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { visitWithLoader, navigateWithCheck, mockCoinPricesServer } from 'cypress/utils/e2e'; -import viewports from 'cypress/utils/viewports'; -import { FIAT_CURRENCIES_SYMBOLS, DISPLAY_CURRENCIES } from 'src/constants/currencies'; -import { - ARE_OCTANT_TIPS_ALWAYS_VISIBLE, - DISPLAY_CURRENCY, - HAS_ONBOARDING_BEEN_CLOSED, - IS_CRYPTO_MAIN_VALUE_DISPLAY, - IS_ONBOARDING_ALWAYS_VISIBLE, - IS_ONBOARDING_DONE, -} from 'src/constants/localStorageKeys'; -import { OCTANT_BUILD_LINK, OCTANT_DOCS, DISCORD_LINK, TERMS_OF_USE } from 'src/constants/urls'; -import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; -import getValueCryptoToDisplay from 'src/utils/getValueCryptoToDisplay'; - -Object.values(viewports).forEach(({ device, viewportWidth, viewportHeight, isDesktop }) => { - describe(`settings: ${device}`, { viewportHeight, viewportWidth }, () => { - beforeEach(() => { - mockCoinPricesServer(); - localStorage.setItem(IS_ONBOARDING_ALWAYS_VISIBLE, 'false'); - localStorage.setItem(IS_ONBOARDING_DONE, 'true'); - localStorage.setItem(HAS_ONBOARDING_BEEN_CLOSED, 'true'); - visitWithLoader(ROOT_ROUTES.settings.absolute); - }); - - it('"Always show Allocate onboarding" option toggle works', () => { - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').check(); - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('true'); - }); - - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').click(); - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('not.be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('false'); - }); - - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').click(); - cy.get('[data-test=SettingsShowOnboardingBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_ONBOARDING_ALWAYS_VISIBLE)).eq('true'); - }); - }); - - it(`${IS_CRYPTO_MAIN_VALUE_DISPLAY} option is checked by default`, () => { - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); - }); - }); - - it(`${IS_CRYPTO_MAIN_VALUE_DISPLAY} option toggle works`, () => { - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').check(); - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); - }); - - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').click(); - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('not.be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('false'); - }); - - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').click(); - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(IS_CRYPTO_MAIN_VALUE_DISPLAY)).eq('true'); - }); - }); - - it(`${IS_CRYPTO_MAIN_VALUE_DISPLAY} option by default displays crypto value as primary in DoubleValue component`, () => { - navigateWithCheck(ROOT_ROUTES.earn.absolute); - - const cryptoValue = getValueCryptoToDisplay({ - cryptoCurrency: 'golem', - valueCrypto: BigInt(0), - }).fullString; - - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]') - .invoke('text') - .should('eq', cryptoValue); - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]') - .invoke('text') - .should('not.eq', cryptoValue); - }); - - it(`${IS_CRYPTO_MAIN_VALUE_DISPLAY} option changes DoubleValue sections order`, () => { - cy.get('[data-test=SettingsCryptoMainValueBox__InputToggle]').uncheck(); - navigateWithCheck(ROOT_ROUTES.earn.absolute); - - const cryptoValue = getValueCryptoToDisplay({ - cryptoCurrency: 'golem', - valueCrypto: BigInt(0), - }).fullString; - - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__primary]') - .invoke('text') - .should('not.eq', cryptoValue); - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]') - .invoke('text') - .should('eq', cryptoValue); - }); - - it('"Choose a display currency" option works', () => { - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(DISPLAY_CURRENCY)).eq('"usd"'); - }); - - for (let i = 0; i < DISPLAY_CURRENCIES.length - 1; i++) { - const displayCurrency = DISPLAY_CURRENCIES[i]; - const displayCurrencyToUppercase = displayCurrency.toUpperCase(); - const nextDisplayCurrencyToUppercase = - i < DISPLAY_CURRENCIES.length - 1 ? DISPLAY_CURRENCIES[i + 1].toUpperCase() : undefined; - - cy.get('[data-test=SettingsCurrencyBox__InputSelect--currency__SingleValue]').contains( - displayCurrencyToUppercase, - ); - navigateWithCheck(ROOT_ROUTES.earn.absolute); - - if (FIAT_CURRENCIES_SYMBOLS[displayCurrency]) { - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]').contains( - FIAT_CURRENCIES_SYMBOLS[displayCurrency], - ); - } else { - cy.get('[data-test=BoxGlmLock__Section--effective__DoubleValue__secondary]').contains( - displayCurrencyToUppercase, - ); - } - - navigateWithCheck(ROOT_ROUTES.settings.absolute); - cy.get('[data-test=SettingsCurrencyBox__InputSelect--currency]').click(); - cy.get( - `[data-test=SettingsCurrencyBox__InputSelect--currency__Option--${nextDisplayCurrencyToUppercase}]`, - ).click(); - } - }); - - it('"Always show Octant tips" option toggle works', () => { - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').check(); - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('true'); - }); - - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').click(); - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').should('not.be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('false'); - }); - - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').click(); - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').should('be.checked'); - cy.getAllLocalStorage().then(() => { - expect(localStorage.getItem(ARE_OCTANT_TIPS_ALWAYS_VISIBLE)).eq('true'); - }); - }); - - it('"Always show Octant tips" works (checked)', () => { - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').check(); - - navigateWithCheck(ROOT_ROUTES.earn.absolute); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); - - cy.get('[data-test=EarnView__TipTile--connectWallet__Button]').click(); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); - - cy.reload(); - - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); - }); - - it('"Always show Octant tips" works (unchecked)', () => { - cy.get('[data-test=SettingsShowTipsBox__InputToggle]').uncheck(); - - navigateWithCheck(ROOT_ROUTES.earn.absolute); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('exist'); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('be.visible'); - - cy.get('[data-test=EarnView__TipTile--connectWallet__Button]').click(); - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); - - cy.reload(); - - cy.get('[data-test=EarnView__TipTile--connectWallet]').should('not.exist'); - }); - - it('should show correct setting links', () => { - cy.get('[data-test=SettingsLinkBoxes__Button]').each(($button, index) => { - const expectedOrderAndContentLinksMobile = [ - { href: OCTANT_BUILD_LINK, text: isDesktop ? 'Visit the website' : 'Website' }, - { href: OCTANT_DOCS, text: isDesktop ? 'Check out the docs' : 'Docs' }, - { href: DISCORD_LINK, text: isDesktop ? 'Join our Discord' : 'Discord' }, - ]; - - cy.wrap($button) - .should('have.text', expectedOrderAndContentLinksMobile[index].text) - .should('have.attr', 'href', expectedOrderAndContentLinksMobile[index].href); - - cy.get('[data-test=SettingsMainInfoBox__Button]') - .should('have.text', 'Terms & Conditions') - .and('have.attr', 'href', TERMS_OF_USE); - }); - }); - }); -}); diff --git a/client/cypress/utils/e2e.ts b/client/cypress/utils/e2e.ts index fdd1e57b06..8797be5874 100644 --- a/client/cypress/utils/e2e.ts +++ b/client/cypress/utils/e2e.ts @@ -1,4 +1,6 @@ -import { navigationTabs } from 'src/constants/navigationTabs/navigationTabs'; +// TODO: https://linear.app/golemfoundation/issue/OCT-1891/e2e-layout +// import { navigationTabs } from 'src/constants/navigationTabs/navigationTabs'; + import { ROOT_ROUTES } from 'src/routes/RootRoutes/routes'; import { ConnectWalletParameters } from './types'; @@ -29,7 +31,9 @@ export const visitWithLoader = ( }; export const navigateWithCheck = (urlEnter: string): Chainable => { - const { label } = navigationTabs.find(({ to }) => to === urlEnter)!; + // TODO: https://linear.app/golemfoundation/issue/OCT-1891/e2e-layout + // const { label } = navigationTabs.find(({ to }) => to === urlEnter)!; + const label = 'Home'; cy.get(`[data-test=Navbar__Button--${label}]`).click(); return checkLocationWithLoader(urlEnter); }; diff --git a/client/index.html b/client/index.html index 200b3618fb..724acef6f5 100644 --- a/client/index.html +++ b/client/index.html @@ -1,7 +1,6 @@ - + + + Octant App diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-100.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-100.woff2 new file mode 100644 index 0000000000..bfaf238b85 Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-100.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-100italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-100italic.woff2 new file mode 100644 index 0000000000..744464311e Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-100italic.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-200.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-200.woff2 new file mode 100644 index 0000000000..3731913899 Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-200.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-200italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-200italic.woff2 new file mode 100644 index 0000000000..ec40e352ae Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-200italic.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-300.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-300.woff2 new file mode 100644 index 0000000000..c47c5189a3 Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-300.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-300italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-300italic.woff2 new file mode 100644 index 0000000000..7b59c6f3e2 Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-300italic.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-500.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-500.woff2 new file mode 100644 index 0000000000..c5aba164fa Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-500.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-500italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-500italic.woff2 new file mode 100644 index 0000000000..75992c428c Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-500italic.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-600.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-600.woff2 new file mode 100644 index 0000000000..86a4abfc87 Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-600.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-600italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-600italic.woff2 new file mode 100644 index 0000000000..2a7a02557f Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-600italic.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-700.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-700.woff2 new file mode 100644 index 0000000000..ba0b293881 Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-700.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-700italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-700italic.woff2 new file mode 100644 index 0000000000..8ea3d1ed27 Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-700italic.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-800.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-800.woff2 new file mode 100644 index 0000000000..b9b41ea178 Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-800.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-800italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-800italic.woff2 new file mode 100644 index 0000000000..2e403d1933 Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-800italic.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-900.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-900.woff2 new file mode 100644 index 0000000000..6edf0f1952 Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-900.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-900italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-900italic.woff2 new file mode 100644 index 0000000000..8d7cccab51 Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-900italic.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-italic.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-italic.woff2 new file mode 100644 index 0000000000..bee95b75ce Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-italic.woff2 differ diff --git a/client/public/fonts/inter-tight/inter-tight-v7-latin-regular.woff2 b/client/public/fonts/inter-tight/inter-tight-v7-latin-regular.woff2 new file mode 100644 index 0000000000..308f3c02df Binary files /dev/null and b/client/public/fonts/inter-tight/inter-tight-v7-latin-regular.woff2 differ diff --git a/client/public/images/funds_swept.webp b/client/public/images/funds_swept.webp deleted file mode 100644 index f7b3fc1c11..0000000000 Binary files a/client/public/images/funds_swept.webp and /dev/null differ diff --git a/client/public/images/lock-glm-desktop.webp b/client/public/images/lock-glm-desktop.webp deleted file mode 100644 index 3aeb02a0b6..0000000000 Binary files a/client/public/images/lock-glm-desktop.webp and /dev/null differ diff --git a/client/public/images/lock-glm-mobile.webp b/client/public/images/lock-glm-mobile.webp deleted file mode 100644 index da185519a1..0000000000 Binary files a/client/public/images/lock-glm-mobile.webp and /dev/null differ diff --git a/client/public/images/modalEffectiveLockedBalance.webp b/client/public/images/modalEffectiveLockedBalance.webp deleted file mode 100644 index 746d0205c3..0000000000 Binary files a/client/public/images/modalEffectiveLockedBalance.webp and /dev/null differ diff --git a/client/public/images/swept.webp b/client/public/images/swept.webp new file mode 100644 index 0000000000..3384063874 Binary files /dev/null and b/client/public/images/swept.webp differ diff --git a/client/public/images/sybil.webp b/client/public/images/sybil.webp new file mode 100644 index 0000000000..4ce1d528aa Binary files /dev/null and b/client/public/images/sybil.webp differ diff --git a/client/public/images/tip-connect-wallet.webp b/client/public/images/tip-connect-wallet.webp deleted file mode 100644 index 963b70f33d..0000000000 Binary files a/client/public/images/tip-connect-wallet.webp and /dev/null differ diff --git a/client/public/images/tip-withdraw.webp b/client/public/images/tip-withdraw.webp deleted file mode 100644 index e0ce55c116..0000000000 Binary files a/client/public/images/tip-withdraw.webp and /dev/null differ diff --git a/client/public/images/window-with-dog.webp b/client/public/images/window-with-dog.webp new file mode 100644 index 0000000000..3ef76a701c Binary files /dev/null and b/client/public/images/window-with-dog.webp differ diff --git a/client/src/App.tsx b/client/src/App.tsx index 0d88e6d9c1..2fd5bbb4de 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -3,7 +3,9 @@ import React, { ReactElement, useState, Fragment } from 'react'; import { useAccount } from 'wagmi'; import AppLoader from 'components/shared/AppLoader'; +import Layout from 'components/shared/Layout'; import ModalOnboarding from 'components/shared/ModalOnboarding/ModalOnboarding'; +import ModalTimeoutListPresence from 'components/shared/ModalTimeoutListPresence'; import OnboardingStepper from 'components/shared/OnboardingStepper'; import useAppConnectManager from 'hooks/helpers/useAppConnectManager'; import useAppIsLoading from 'hooks/helpers/useAppIsLoading'; @@ -44,8 +46,11 @@ const App = (): ReactElement => { return ( - + + + {!isSyncingInProgress && !isProjectAdminMode && } + {isConnected && !isOnboardingDone && !isOnboardingModalOpen && } diff --git a/client/src/api/calls/antisybilStatus.ts b/client/src/api/calls/antisybilStatus.ts index 75a8d5b209..50e9af5a7b 100644 --- a/client/src/api/calls/antisybilStatus.ts +++ b/client/src/api/calls/antisybilStatus.ts @@ -3,6 +3,7 @@ import apiService from 'services/apiService'; export type Response = { expires_at: string; + isOnTimeOutList: boolean; score: string; status: string; }; diff --git a/client/src/api/calls/epochInfo.ts b/client/src/api/calls/epochInfo.ts index ee377d6533..84ae716cec 100644 --- a/client/src/api/calls/epochInfo.ts +++ b/client/src/api/calls/epochInfo.ts @@ -5,6 +5,7 @@ import apiService from 'services/apiService'; export type Response = { communityFund: string | null; + donatedToProjects: string | null; leftover: string | null; matchedRewards: string | null; operationalCost: string; diff --git a/client/src/api/calls/projects.ts b/client/src/api/calls/projects.ts index 509973bb43..b487f0a873 100644 --- a/client/src/api/calls/projects.ts +++ b/client/src/api/calls/projects.ts @@ -30,3 +30,20 @@ export type Projects = { export async function apiGetProjects(epoch: number): Promise { return apiService.get(`${env.serverEndpoint}projects/epoch/${epoch}`).then(({ data }) => data); } + +export type ProjectsSearchResults = { + projectsDetails: { + address: string; + epoch: string; + name: string; + }[]; +}; + +export async function apiGetProjectsSearch( + epochs: string, + searchPhrases: string, +): Promise { + return apiService + .get(`${env.serverEndpoint}projects/details?epochs=${epochs}&searchPhrases=${searchPhrases}`) + .then(({ data }) => data); +} diff --git a/client/src/api/calls/rewardsRate.ts b/client/src/api/calls/rewardsRate.ts new file mode 100644 index 0000000000..faad479cb5 --- /dev/null +++ b/client/src/api/calls/rewardsRate.ts @@ -0,0 +1,17 @@ +import { GenericAbortSignal } from 'axios'; + +import env from 'env'; +import apiService from 'services/apiService'; + +export type Response = { + rewardsRate: number; +}; + +export async function apiGetRewardsRate( + epoch: number, + signal?: GenericAbortSignal, +): Promise { + return apiService + .get(`${env.serverEndpoint}epochs/rewards-rate/${epoch}`, { signal }) + .then(({ data }) => data); +} diff --git a/client/src/api/calls/userAllocations.ts b/client/src/api/calls/userAllocations.ts index bc33fc6912..7f463588fb 100644 --- a/client/src/api/calls/userAllocations.ts +++ b/client/src/api/calls/userAllocations.ts @@ -1,7 +1,7 @@ import env from 'env'; import apiService from 'services/apiService'; -export type Response = { +export type GetUserAllocationsResponse = { allocations: { address: string; // Funds allocated by user for the project in WEI @@ -10,8 +10,25 @@ export type Response = { isManuallyEdited: boolean | null; }; -export async function apiGetUserAllocations(address: string, epoch: number): Promise { +export async function apiGetUserAllocations( + address: string, + epoch: number, +): Promise { return apiService .get(`${env.serverEndpoint}allocations/user/${address}/epoch/${epoch}`) .then(({ data }) => data); } + +export type AllocationsPerProjectResponse = { + address: string; + amount: string; +}[]; + +export async function apiGetAllocationsPerProject( + projectAddress: string, + epoch: number, +): Promise { + return apiService + .get(`${env.serverEndpoint}allocations/project/${projectAddress}/epoch/${epoch}`) + .then(({ data }) => data); +} diff --git a/client/src/api/queryKeys/index.ts b/client/src/api/queryKeys/index.ts index 1d3a11f67b..b43595ea00 100644 --- a/client/src/api/queryKeys/index.ts +++ b/client/src/api/queryKeys/index.ts @@ -21,6 +21,8 @@ export const ROOTS: Root = { projectsDonors: 'projectsDonors', projectsEpoch: 'projectsEpoch', projectsIpfsResults: 'projectsIpfsResults', + rewardsRate: 'rewardsRate', + searchResultsDetails: 'searchResultsDetails', upcomingBudget: 'upcomingBudget', uqScore: 'uqScore', userAllocationNonce: 'userAllocationNonce', @@ -66,6 +68,9 @@ export const QUERY_KEYS: QueryKeys = { ], projectsMetadataAccumulateds: ['projectsMetadataAccumulateds'], projectsMetadataPerEpoches: ['projectsMetadataPerEpoches'], + rewardsRate: epochNumber => [ROOTS.rewardsRate, epochNumber.toString()], + searchResults: ['searchResults'], + searchResultsDetails: (address, epoch) => [ROOTS.searchResultsDetails, address, epoch.toString()], syncStatus: ['syncStatus'], totalAddresses: ['totalAddresses'], totalWithdrawals: ['totalWithdrawals'], diff --git a/client/src/api/queryKeys/types.ts b/client/src/api/queryKeys/types.ts index bf4f1e0851..f5b0ed8d42 100644 --- a/client/src/api/queryKeys/types.ts +++ b/client/src/api/queryKeys/types.ts @@ -21,6 +21,8 @@ export type Root = { projectsDonors: 'projectsDonors'; projectsEpoch: 'projectsEpoch'; projectsIpfsResults: 'projectsIpfsResults'; + rewardsRate: 'rewardsRate'; + searchResultsDetails: 'searchResultsDetails'; upcomingBudget: 'upcomingBudget'; uqScore: 'uqScore'; userAllocationNonce: 'userAllocationNonce'; @@ -67,6 +69,12 @@ export type QueryKeys = { ) => [Root['projectsIpfsResults'], string, string]; projectsMetadataAccumulateds: ['projectsMetadataAccumulateds']; projectsMetadataPerEpoches: ['projectsMetadataPerEpoches']; + rewardsRate: (epochNumber: number) => [Root['rewardsRate'], string]; + searchResults: ['searchResults']; + searchResultsDetails: ( + address: string, + epoch: number, + ) => [Root['searchResultsDetails'], string, string]; syncStatus: ['syncStatus']; totalAddresses: ['totalAddresses']; totalWithdrawals: ['totalWithdrawals']; diff --git a/client/src/components/Allocation/Allocation.module.scss b/client/src/components/Allocation/Allocation.module.scss new file mode 100644 index 0000000000..1165f2829c --- /dev/null +++ b/client/src/components/Allocation/Allocation.module.scss @@ -0,0 +1,94 @@ +.root { + width: 100%; + display: flex; + flex-direction: column; + flex: 1; + + @media #{$tablet-up} { + } + + @media #{$desktop-up} { + padding-bottom: 0; + width: 68rem; + min-height: 0; + } + + .title { + @include fontBig($font-size-24); + padding: 0; + display: flex; + align-items: center; + min-height: 8.8rem; + width: 100%; + color: $color-octant-dark; + line-height: 1.4rem; + + @media #{$tablet-up} { + @include fontBig($font-size-32); + min-height: 14.4rem; + } + + @media #{$desktop-up} { + padding: 0 4rem; + } + } + + .box { + &:not(:last-child) { + margin: 0 auto 1.6rem; + } + } + + .boxesWrapper { + width: 100%; + flex: 1; + display: flex; + flex-direction: column; + + &.withMarginBottom { + margin-bottom: 12.8rem; + } + + &.withPaddingBottom { + padding-bottom: 4rem; + } + + @media #{$desktop-up} { + padding: 0 4rem; + overflow: auto; + } + } + + .emptyState { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + flex: 1; + text-align: center; + + .emptyStateImage { + width: 12rem; + + @media #{$tablet-up} { + width: 15rem; + } + } + + .emptyStateText { + display: flex; + align-items: center; + height: 7.2rem; + margin-top: 1.8rem; + color: $color-octant-grey5; + font-size: $font-size-14; + font-weight: $font-weight-semibold; + line-height: 2.2rem; + + @media #{$tablet-up} { + font-size: $font-size-16; + line-height: 2.4rem; + } + } + } +} diff --git a/client/src/components/Allocation/Allocation.tsx b/client/src/components/Allocation/Allocation.tsx new file mode 100644 index 0000000000..0611d2af00 --- /dev/null +++ b/client/src/components/Allocation/Allocation.tsx @@ -0,0 +1,718 @@ +import cx from 'classnames'; +import { AnimatePresence } from 'framer-motion'; +import debounce from 'lodash/debounce'; +import isEmpty from 'lodash/isEmpty'; +import React, { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { Trans, useTranslation } from 'react-i18next'; +import { useAccount } from 'wagmi'; + +import { SignatureOpType, apiGetPendingMultisigSignatures } from 'api/calls/multisigSignatures'; +import AllocationItem from 'components/Allocation/AllocationItem'; +import AllocationItemSkeleton from 'components/Allocation/AllocationItemSkeleton'; +import AllocationNavigation from 'components/Allocation/AllocationNavigation'; +import AllocationRewardsBox from 'components/Allocation/AllocationRewardsBox'; +import AllocationSummary from 'components/Allocation/AllocationSummary'; +import ModalAllocationLowUqScore from 'components/Allocation/ModalAllocationLowUqScore'; +import Button from 'components/ui/Button'; +import Img from 'components/ui/Img'; +import { DRAWER_TRANSITION_TIME } from 'constants/animations'; +import { LAYOUT_NAVBAR_ID } from 'constants/domElementsIds'; +import { UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1_BIG_INT } from 'constants/uq'; +import useAllocate from 'hooks/events/useAllocate'; +import useAllocationViewSetRewardsForProjects from 'hooks/helpers/useAllocationViewSetRewardsForProjects'; +import useIdsInAllocation from 'hooks/helpers/useIdsInAllocation'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; +import useAllocateSimulate from 'hooks/mutations/useAllocateSimulate'; +import useRefreshAntisybilStatus from 'hooks/mutations/useRefreshAntisybilStatus'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useEpochAllocations from 'hooks/queries/useEpochAllocations'; +import useHistory from 'hooks/queries/useHistory'; +import useIndividualReward from 'hooks/queries/useIndividualReward'; +import useIsContract from 'hooks/queries/useIsContract'; +import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import useMatchedProjectRewards from 'hooks/queries/useMatchedProjectRewards'; +import useProjectsEpoch from 'hooks/queries/useProjectsEpoch'; +import useProjectsIpfsWithRewards from 'hooks/queries/useProjectsIpfsWithRewards'; +import useUpcomingBudget from 'hooks/queries/useUpcomingBudget'; +import useUqScore from 'hooks/queries/useUqScore'; +import useUserAllocationNonce from 'hooks/queries/useUserAllocationNonce'; +import useUserAllocations from 'hooks/queries/useUserAllocations'; +import useWithdrawals from 'hooks/queries/useWithdrawals'; +import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; +import toastService from 'services/toastService'; +import useAllocationsStore from 'store/allocations/store'; +import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; +import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; + +import styles from './Allocation.module.scss'; +import AllocationSliderBox from './AllocationSliderBox'; +import { AllocationValue, AllocationValues, PercentageProportions } from './types'; +import { + getAllocationValuesInitialState, + getAllocationsWithRewards, + getAllocationValuesAfterManualChange, +} from './utils'; + +const Allocation = (): ReactElement => { + const { isConnected } = useAccount(); + const keyPrefix = 'components.allocation'; + const { t } = useTranslation('translation', { keyPrefix }); + const [allocationValues, setAllocationValues] = useState([]); + const [isManualMode, setIsManualMode] = useState(false); + const [addressesWithError, setAddressesWithError] = useState([]); + const [percentageProportions, setPercentageProportions] = useState({}); + const { data: projectsEpoch } = useProjectsEpoch(); + const { data: projectsIpfsWithRewards } = useProjectsIpfsWithRewards(); + const { isRewardsForProjectsSet } = useAllocationViewSetRewardsForProjects(); + const [isWaitingForFirstMultisigSignature, setIsWaitingForFirstMultisigSignature] = + useState(false); + const { + data: allocationSimulated, + mutateAsync: mutateAsyncAllocateSimulate, + isPending: isLoadingAllocateSimulate, + reset: resetAllocateSimulate, + } = useAllocateSimulate(); + const [isWaitingForAllMultisigSignatures, setIsWaitingForAllMultisigSignatures] = useState(false); + const { isFetching: isFetchingUpcomingBudget, isRefetching: isRefetchingUpcomingBudget } = + useUpcomingBudget(); + const { data: isContract } = useIsContract(); + const { address: walletAddress } = useAccount(); + const [showAllocationNav, setShowAllocationNav] = useState(false); + const [isEmptyStateImageVisible, setIsEmptyStateImageVisible] = useState(true); + + const navRef = useRef(document.getElementById(LAYOUT_NAVBAR_ID)); + const boxesWrapperRef = useRef(null); + const allocationEmptyStateRef = useRef(null); + const { data: currentEpoch } = useCurrentEpoch(); + const { refetch: refetchHistory } = useHistory(); + const { + data: userAllocationsOriginal, + isFetching: isFetchingUserAllocation, + refetch: refetchUserAllocations, + } = useUserAllocations(undefined, { refetchOnMount: true }); + + const userAllocations = userAllocationsOriginal && { + ...userAllocationsOriginal, + elements: userAllocationsOriginal.elements.map(element => ({ + ...element, + value: formatUnitsBigInt(element.value), + })), + }; + + const { data: individualReward } = useIndividualReward(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const { refetch: refetchWithdrawals } = useWithdrawals(); + const { + data: userNonce, + isFetching: isFetchingUserNonce, + refetch: refetchUserAllocationNonce, + } = useUserAllocationNonce(); + const { + mutateAsync: refreshAntisybilStatus, + isPending: isPendingRefreshAntisybilStatus, + isSuccess: isSuccessRefreshAntisybilStatus, + error: refreshAntisybilStatusError, + } = useRefreshAntisybilStatus(); + const { data: uqScore, isFetching: isFetchingUqScore } = useUqScore(currentEpoch!, { + enabled: + isSuccessRefreshAntisybilStatus || + (refreshAntisybilStatusError as null | { message: string })?.message === + 'Address is already used for delegation', + }); + const { refetch: refetchMatchedProjectRewards } = useMatchedProjectRewards(); + const [showLowUQScoreModal, setShowLowUQScoreModal] = useState(false); + const { refetch: refetchEpochAllocations } = useEpochAllocations( + isDecisionWindowOpen ? currentEpoch! - 1 : currentEpoch!, + { + enabled: isDecisionWindowOpen === true, + }, + ); + const { isMobile, isTablet, isDesktop } = useMediaQuery(); + + const { + currentView, + setCurrentView, + allocations, + rewardsForProjects, + setAllocations, + addAllocations, + removeAllocations, + setRewardsForProjects, + } = useAllocationsStore(state => ({ + addAllocations: state.addAllocations, + allocations: state.data.allocations, + currentView: state.data.currentView, + removeAllocations: state.removeAllocations, + rewardsForProjects: state.data.rewardsForProjects, + setAllocations: state.setAllocations, + setCurrentView: state.setCurrentView, + setRewardsForProjects: state.setRewardsForProjects, + })); + const { onAddRemoveFromAllocate } = useIdsInAllocation({ + addAllocations, + allocations, + isDecisionWindowOpen, + removeAllocations, + userAllocationsElements: userAllocationsOriginal?.elements, + }); + + const onAllocateSuccess = () => { + refetchMatchedProjectRewards(); + refetchUserAllocations(); + refetchUserAllocationNonce(); + refetchHistory(); + refetchWithdrawals(); + refetchEpochAllocations(); + toastService.hideToast('confirmChanges'); + setAllocations([ + ...allocations.filter(allocation => { + const allocationValue = allocationValues.find(({ address }) => address === allocation); + return allocationValue && allocationValue.value !== '0'; + }), + ]); + setCurrentView('summary'); + }; + + const allocateEvent = useAllocate({ + nonce: userNonce!, + onMultisigMessageSign: () => { + toastService.hideToast('allocationMultisigInitialSignature'); + setIsWaitingForFirstMultisigSignature(false); + setIsWaitingForAllMultisigSignatures(true); + }, + onSuccess: onAllocateSuccess, + }); + + // eslint-disable-next-line react-hooks/exhaustive-deps + const mutateAsyncAllocateSimulateDebounced = useCallback( + debounce( + _allocationValues => { + resetAllocateSimulate(); + mutateAsyncAllocateSimulate(_allocationValues); + }, + 250, + { trailing: true }, + ), + [], + ); + + const setPercentageProportionsWrapper = ( + allocationValuesNew: AllocationValues, + rewardsForProjectsNew: bigint, + ) => { + if (!individualReward) { + return; + } + const percentageProportionsNew = allocationValuesNew.reduce((acc, curr) => { + const valueAsPercentageOfRewardsForProjects = ['0', ''].includes(curr.value) // 0 from the user, empty when removed entirely. + ? '0' + : ( + (parseFloat(curr.value.toString()) * 100) / + parseFloat(formatUnitsBigInt(rewardsForProjectsNew)) + ).toFixed(); + return { + ...acc, + [curr.address]: valueAsPercentageOfRewardsForProjects, + }; + }, {}); + setPercentageProportions(percentageProportionsNew); + }; + + const onResetAllocationValues = ({ + allocationValuesNew = allocationValues, + rewardsForProjectsNew = rewardsForProjects, + shouldReset = false, + } = {}) => { + if ( + isFetchingUserAllocation || + !isRewardsForProjectsSet || + currentEpoch === undefined || + (isConnected && !userAllocations && isDecisionWindowOpen && currentEpoch > 1) + ) { + return; + } + + const userAllocationsAddresses = userAllocations?.elements.map(({ address }) => address); + if (shouldReset && userAllocationsAddresses) { + const userAllocationsAddressesToAdd = userAllocationsAddresses?.filter( + element => !allocations.includes(element), + ); + + userAllocationsAddressesToAdd?.forEach((element, index, array) => { + onAddRemoveFromAllocate(element, [...allocations, ...array.slice(0, index)]); + }); + } + + const allocationValuesNewSum = allocationValuesNew.reduce( + (acc, curr) => acc + parseUnitsBigInt(curr.value), + BigInt(0), + ); + const shouldIsManulModeBeChangedToFalse = allocationValuesNewSum === 0n; + + /** + * Manual needs to be changed to false when values are 0. + * Percentages cant be calculated from 0, equal split cant be maintained, causing the app to crash. + * Mode needs to change to "auto". + */ + if (shouldIsManulModeBeChangedToFalse) { + setIsManualMode(false); + } + + const allocationValuesReset = getAllocationValuesInitialState({ + allocationValues: allocationValuesNew, + allocations: + shouldReset && userAllocationsAddresses + ? [...new Set([...allocations, ...userAllocationsAddresses])] + : allocations, + isManualMode: shouldIsManulModeBeChangedToFalse ? false : isManualMode, + percentageProportions, + rewardsForProjects: rewardsForProjectsNew, + shouldReset, + userAllocationsElements: isDecisionWindowOpen ? userAllocations?.elements || [] : [], + }); + + if (shouldReset) { + const allocationValuesResetSum = allocationValuesReset.reduce( + (acc, curr) => acc + parseUnitsBigInt(curr.value), + BigInt(0), + ); + + setRewardsForProjects(allocationValuesResetSum); + setPercentageProportionsWrapper(allocationValuesReset, allocationValuesResetSum); + + const shouldIsManualModeBeChangedToFalseNew = allocationValuesResetSum === 0n; + if (!shouldIsManualModeBeChangedToFalseNew) { + setIsManualMode(userAllocations!.isManuallyEdited); + } else { + setIsManualMode(false); + } + } + + setAllocationValues(allocationValuesReset); + }; + + const onAllocate = (isProceedingToAllocateWithLowUQScore?: boolean) => { + if (userNonce === undefined || projectsEpoch === undefined) { + return; + } + /** + * Whenever user wants to send an empty allocation (no projects, or all of them value 0) + * Push one element with value 0. It should be fixed on BE by creating "personal all" endpoint, + * but there is no ticket for it yet. + */ + const allocationValuesNew = [...allocationValues]; + if (allocationValuesNew.length === 0) { + allocationValuesNew.push({ + address: projectsEpoch.projectsAddresses[0], + value: '0', + }); + } + + if ( + !userAllocations?.hasUserAlreadyDoneAllocation && + uqScore === UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1_BIG_INT && + !isProceedingToAllocateWithLowUQScore + ) { + setShowLowUQScoreModal(true); + return; + } + + if (isProceedingToAllocateWithLowUQScore) { + setShowLowUQScoreModal(false); + } + + // this condition must always be last due to ModalAllocationLowUqScore + // if uqScore == 20n, the signature request is triggered in ModalAllocationLowUqScore + if (isContract) { + setIsWaitingForFirstMultisigSignature(true); + toastService.showToast({ + message: t('multisigSignatureToast.message'), + name: 'allocationMultisigInitialSignature', + title: t('multisigSignatureToast.title'), + type: 'warning', + }); + } + allocateEvent.emit(allocationValuesNew, isManualMode); + }; + + useEffect(() => { + if (!walletAddress) { + return; + } + /** + * The initial value of UQ for every user is 0.2 / 0.01 (after https://linear.app/golemfoundation/issue/OCT-1928/change-the-uq-leverage-to-001-and-1) + * It does not update automatically after delegation nor after change in Gitcoin Passport itself. + * + * We need to refreshAntisybilStatus to force BE to refetch current values from Gitcoin Passport + * and return true value. + */ + refreshAntisybilStatus(walletAddress!); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (!userAllocations || isManualMode) { + return; + } + if (userAllocationsOriginal?.isManuallyEdited) { + setIsManualMode(true); + return; + } + setIsManualMode(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [userAllocations?.isManuallyEdited]); + + useEffect(() => { + if (!isRewardsForProjectsSet || isFetchingUserAllocation) { + return; + } + + if (userAllocations && userAllocations.elements.length > 0) { + setAllocationValues(userAllocations.elements); + setPercentageProportionsWrapper(userAllocations.elements, rewardsForProjects); + onResetAllocationValues({ allocationValuesNew: userAllocations.elements }); + return; + } + onResetAllocationValues(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + currentEpoch, + allocations, + isRewardsForProjectsSet, + isFetchingUserAllocation, + userAllocations?.elements.length, + userNonce, + isRewardsForProjectsSet, + ]); + + useEffect(() => { + if ( + !currentEpoch || + !isDecisionWindowOpen || + !userAllocations || + currentEpoch < 2 || + !userAllocations.hasUserAlreadyDoneAllocation + ) { + return; + } + const userAllocationsAddresses = userAllocations.elements.map(({ address }) => address); + /** + * Whenever user did an allocation and removed/unhearted project they previously allocated to, + * land on edit. + * + * Otherwise, land on summary. + */ + if (allocations.length < userAllocationsAddresses.length) { + setCurrentView('edit'); + return; + } + + return () => { + if (!isDecisionWindowOpen || allocations.length < userAllocationsAddresses.length) { + return; + } + setCurrentView('summary'); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentEpoch, isDecisionWindowOpen, userAllocations?.elements.length]); + + useEffect(() => { + const areAllValuesZero = !allocationValues.some(element => element.value !== '0.0'); + if ( + allocationValues.length === 0 || + areAllValuesZero || + addressesWithError.length > 0 || + !isDecisionWindowOpen || + !isConnected + ) { + return; + } + mutateAsyncAllocateSimulateDebounced( + currentView === 'edit' ? allocationValues : userAllocations?.elements, + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + currentView, + mutateAsyncAllocateSimulateDebounced, + addressesWithError, + allocationValues, + isDecisionWindowOpen, + userAllocations?.elements?.length, + userNonce, + ]); + + const onChangeAllocationItemValue = ( + newAllocationValue: AllocationValue, + isManualModeEnforced = false, + ) => { + const { allocationValuesArrayNew, rewardsForProjectsNew } = + getAllocationValuesAfterManualChange({ + allocationValues, + individualReward, + // When deleting by button isManualMode does not trigger manual mode. When typing, it does. + isManualMode: isManualModeEnforced ? true : isManualMode, + newAllocationValue: newAllocationValue || '0', + rewardsForProjects, + setAddressesWithError, + }); + + setAllocationValues(allocationValuesArrayNew); + setRewardsForProjects(rewardsForProjectsNew); + + if (isManualModeEnforced) { + setPercentageProportionsWrapper(allocationValuesArrayNew, rewardsForProjectsNew); + } + + if (isManualModeEnforced) { + setIsManualMode(true); + } + }; + + const onRemoveAllocationElement = (address: string) => { + onAddRemoveFromAllocate(address); + onChangeAllocationItemValue({ address, value: '0' }); + }; + + const isLoading = + allocationValues === undefined || + (isConnected && isFetchingUserNonce) || + (isConnected && isFetchingUserAllocation) || + (isConnected && isPendingRefreshAntisybilStatus) || + (isConnected && isFetchingUqScore) || + (isFetchingUpcomingBudget && !isRefetchingUpcomingBudget); + + const areAllocationsAvailableOrAlreadyDone = + (allocationValues !== undefined && !isEmpty(allocations)) || + (!!userAllocations?.hasUserAlreadyDoneAllocation && userAllocations.elements.length > 0); + const hasUserIndividualReward = !!individualReward && individualReward !== 0n; + + const emptyStateI18nKey = useMemo(() => { + if (hasUserIndividualReward && isDecisionWindowOpen) { + if (isMobile) { + return `${keyPrefix}.emptyStateMobileAWOpen`; + } + return `${keyPrefix}.emptyStateAWOpen`; + } + + if (isMobile) { + return `${keyPrefix}.emptyStateMobile`; + } + + return `${keyPrefix}.emptyState`; + }, [hasUserIndividualReward, isDecisionWindowOpen, isMobile]); + + const allocationsWithRewards = getAllocationsWithRewards({ + allocationValues, + areAllocationsAvailableOrAlreadyDone, + projectsIpfsWithRewards, + userAllocationsElements: userAllocations?.elements, + }); + + const isEpoch1 = currentEpoch === 1; + + const areButtonsDisabled = + isEpoch1 || + isLoading || + !isConnected || + !isDecisionWindowOpen || + (!areAllocationsAvailableOrAlreadyDone && rewardsForProjects !== 0n) || + !hasUserIndividualReward || + isWaitingForFirstMultisigSignature; + + useEffect(() => { + if (!walletAddress || !isContract || isWaitingForFirstMultisigSignature) { + return; + } + const getPendingMultisigSignatures = () => { + apiGetPendingMultisigSignatures(walletAddress!, SignatureOpType.ALLOCATION).then(data => { + if (isWaitingForAllMultisigSignatures && !data.hash) { + onAllocateSuccess(); + } + setIsWaitingForAllMultisigSignatures(!!data.hash); + }); + }; + + if (!isWaitingForAllMultisigSignatures) { + getPendingMultisigSignatures(); + return; + } + + const intervalId = setInterval(getPendingMultisigSignatures, 2500); + + return () => { + clearInterval(intervalId); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + walletAddress, + isWaitingForAllMultisigSignatures, + isContract, + isWaitingForFirstMultisigSignature, + ]); + + useEffect(() => { + if (isDesktop) { + setTimeout(() => { + setShowAllocationNav(true); + }, DRAWER_TRANSITION_TIME * 1000); + } else { + setShowAllocationNav(true); + } + + return () => { + setShowAllocationNav(false); + }; + }, [isDesktop]); + + useEffect(() => { + navRef.current = document.getElementById(LAYOUT_NAVBAR_ID); + }, []); + + useEffect(() => { + if (!allocationEmptyStateRef.current || allocations.length) { + return; + } + const { height } = allocationEmptyStateRef.current.getBoundingClientRect(); + + // 200 px (mobile) / 226px (tablet, desktop, large desktop) -> min height of emptyState box to show image + text + if (height < (isMobile ? 200 : 226)) { + setIsEmptyStateImageVisible(false); + return; + } + + setIsEmptyStateImageVisible(true); + }, [allocations.length, isMobile, isTablet]); + + return ( +
+
{t('allocateRewards')}
+
+ {!isEpoch1 && ( + + )} + {hasUserIndividualReward && isDecisionWindowOpen && !isEpoch1 && ( + 0} + isLocked={currentView === 'summary'} + setRewardsForProjectsCallback={onResetAllocationValues} + /> + )} + {!allocations.length && ( +
+ {isEmptyStateImageVisible && ( + + )} +
+ , + ]} + i18nKey={emptyStateI18nKey} + /> +
+
+ )} + + {currentView === 'edit' ? ( + areAllocationsAvailableOrAlreadyDone && ( + + {allocationsWithRewards.length > 0 + ? allocationsWithRewards!.map( + ({ + address, + isAllocatedTo, + isLoadingError, + value, + profileImageSmall, + name, + }) => ( + 0} + name={name} + onChange={onChangeAllocationItemValue} + onRemoveAllocationElement={() => onRemoveAllocationElement(address)} + profileImageSmall={profileImageSmall} + rewardsProps={{ + isLoadingAllocateSimulate, + simulatedMatched: allocationSimulated?.matched.find( + element => element.address === address, + )?.value, + }} + setAddressesWithError={setAddressesWithError} + value={value} + /> + ), + ) + : allocations.map(allocation => ( + + ))} + + ) + ) : ( + + )} + setShowLowUQScoreModal(false), + }} + onAllocate={() => onAllocate(true)} + /> + {showAllocationNav && + (isDesktop ? boxesWrapperRef.current : navRef.current) && + createPortal( + onResetAllocationValues({ shouldReset: true })} + />, + isDesktop ? boxesWrapperRef.current! : navRef.current!, + )} +
+
+ ); +}; + +export default Allocation; diff --git a/client/src/components/Allocation/AllocationItem/AllocationItem.module.scss b/client/src/components/Allocation/AllocationItem/AllocationItem.module.scss index fc7786db98..d056f310cb 100644 --- a/client/src/components/Allocation/AllocationItem/AllocationItem.module.scss +++ b/client/src/components/Allocation/AllocationItem/AllocationItem.module.scss @@ -1,5 +1,5 @@ $removeButtonHeightWidthMobile: 8rem; -$removeButtonHeightWidthDesktop: 10.4rem; +$removeButtonHeightWidth: 10.4rem; .root { width: 100%; @@ -28,19 +28,19 @@ $removeButtonHeightWidthDesktop: 10.4rem; background-color: $color-octant-grey1; } - @media #{$desktop-up} { - height: $removeButtonHeightWidthDesktop; - width: $removeButtonHeightWidthDesktop; + @media #{$tablet-up} { + height: $removeButtonHeightWidth; + width: $removeButtonHeightWidth; } } .box { position: relative; z-index: $z-index-2; - padding: 2.4rem; + padding: 1.6rem 1.6rem 1.6rem 2.4rem; - @media #{$tablet-down} { - padding: 1.6rem 1.6rem 1.6rem 2.4rem; + @media #{$tablet-up } { + padding: 2.4rem; } } @@ -63,11 +63,12 @@ $removeButtonHeightWidthDesktop: 10.4rem; .image { margin-right: 2.4rem; border-radius: 50%; - height: 4.8rem; - width: 4.8rem; + height: 5.6rem; + width: 5.6rem; + display: none; - @media #{$tablet-down} { - display: none; + @media #{$tablet-up} { + display: initial; } } @@ -77,24 +78,25 @@ $removeButtonHeightWidthDesktop: 10.4rem; .name { min-width: 0; - font-size: $font-size-18; - line-height: 2.1rem; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - margin: 0 0 0.1rem 0; - - @media #{$tablet-down} { - font-size: $font-size-14; - margin: 0; - line-height: 1.6rem; + font-size: $font-size-14; + margin: 0; + line-height: 1.6rem; + + @media #{$tablet-up} { + font-size: $font-size-18; + line-height: 2.1rem; + margin: 0 0 0.1rem 0; } } .inputWrapper { flex: 1; - max-width: 26rem; + max-width: 14.2rem; + min-width: 14.2rem; &.isEpoch1 { opacity: 0.5; @@ -104,9 +106,8 @@ $removeButtonHeightWidthDesktop: 10.4rem; animation: horizontal-shaking 0.25s; } - @media #{$tablet-down} { - max-width: 14.2rem; - min-width: 14.2rem; + @media #{$tablet-up} { + max-width: 26rem; } } diff --git a/client/src/components/Allocation/AllocationItem/AllocationItem.tsx b/client/src/components/Allocation/AllocationItem/AllocationItem.tsx index 0bbdff742b..b2f94e6e75 100644 --- a/client/src/components/Allocation/AllocationItem/AllocationItem.tsx +++ b/client/src/components/Allocation/AllocationItem/AllocationItem.tsx @@ -72,7 +72,7 @@ const AllocationItem: FC = ({ const { data: currentEpoch } = useCurrentEpoch(); const { isFetching: isFetchingRewardsThreshold } = useProjectRewardsThreshold(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const { isDesktop } = useMediaQuery(); + const { isMobile } = useMediaQuery(); const [ref, animate] = useAnimate(); const removeButtonRef = useRef(null); const inputRef = useRef(null); @@ -202,7 +202,7 @@ const AllocationItem: FC = ({ const itemHeight = ref.current.getBoundingClientRect().height; setConstraints([(itemHeight + removeButtonLeftPadding) * -1, 0]); - }, [ref, removeButtonRef, isDesktop, isLoading]); + }, [ref, removeButtonRef, isMobile, isLoading]); useEffect(() => { if (isError) { @@ -248,7 +248,7 @@ const AllocationItem: FC = ({ onClick={onRemoveAllocationElement} style={{ scale: removeButtonScaleTransform }} > - + )} { className?: string; isError: boolean; diff --git a/client/src/components/Allocation/AllocationItemRewards/AllocationItemRewards.tsx b/client/src/components/Allocation/AllocationItemRewards/AllocationItemRewards.tsx index 93bb16dc36..4e8718eb0b 100644 --- a/client/src/components/Allocation/AllocationItemRewards/AllocationItemRewards.tsx +++ b/client/src/components/Allocation/AllocationItemRewards/AllocationItemRewards.tsx @@ -4,13 +4,14 @@ import { useTranslation } from 'react-i18next'; import Svg from 'components/ui/Svg'; import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useProjectsDonors from 'hooks/queries/donors/useProjectsDonors'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useMatchedProjectRewards from 'hooks/queries/useMatchedProjectRewards'; import useUqScore from 'hooks/queries/useUqScore'; import useUserAllocations from 'hooks/queries/useUserAllocations'; -import { person } from 'svg/misc'; +import { hammer, person } from 'svg/misc'; import bigintAbs from 'utils/bigIntAbs'; import getRewardsSumWithValueAndSimulation from 'utils/getRewardsSumWithValueAndSimulation'; import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; @@ -98,7 +99,7 @@ const AllocationItemRewards: FC = ({ value, }) => { const { t } = useTranslation('translation', { - keyPrefix: 'views.allocation.allocationItem', + keyPrefix: 'components.allocation.allocationItem', }); const [isSimulateVisible, setIsSimulateVisible] = useState(false); const { data: currentEpoch } = useCurrentEpoch(); @@ -106,6 +107,7 @@ const AllocationItemRewards: FC = ({ const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { data: matchedProjectRewards } = useMatchedProjectRewards(); const { data: uqScore } = useUqScore(currentEpoch!); + const { isMobile } = useMediaQuery(); const { data: projectsDonors } = useProjectsDonors(); const projectDonors = projectsDonors?.[address]; @@ -137,9 +139,10 @@ const AllocationItemRewards: FC = ({ const getValuesToDisplay = useGetValuesToDisplay(); - const isNewSimulatedPositive = userAllocationToThisProject - ? parseUnitsBigInt(valueToUse) >= userAllocationToThisProject - : true; + const isNewSimulatedPositive = + isDecisionWindowOpen && userAllocationToThisProject + ? parseUnitsBigInt(valueToUse) >= userAllocationToThisProject + : true; const simulatedMatchedBigInt = simulatedMatched ? parseUnitsBigInt(simulatedMatched, 'wei') @@ -221,6 +224,27 @@ const AllocationItemRewards: FC = ({ userAllocationToThisProject={userAllocationToThisProject} valueToUse={valueToUse} /> + {!isMobile && !isLoadingAllocateSimulate && !isSimulateVisible && ( +
+ + {`${isNewSimulatedPositive ? '' : '-'}${yourImpactFormatted.primary}`} +
+ )} ); }; diff --git a/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx b/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx index a730348d73..31fb3c6a5e 100644 --- a/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx +++ b/client/src/components/Allocation/AllocationLowUqScore/AllocationLowUqScore.tsx @@ -1,24 +1,83 @@ -import React, { FC, useState } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; +import { useAccount } from 'wagmi'; import BoxRounded from 'components/ui/BoxRounded'; import Button from 'components/ui/Button'; import InputCheckbox from 'components/ui/InputCheckbox'; -import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; +import { GITCOIN_PASSPORT_CUSTOM_OCTANT_DASHBOARD } from 'constants/urls'; +import useRefreshAntisybilStatus from 'hooks/mutations/useRefreshAntisybilStatus'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useUqScore from 'hooks/queries/useUqScore'; +import toastService from 'services/toastService'; import styles from './AllocationLowUqScore.module.scss'; import AllocationLowUqScoreProps from './types'; -const AllocationLowUqScore: FC = ({ onAllocate }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.allocation.lowUQScoreModal' }); +const AllocationLowUqScore: FC = ({ onAllocate, onCloseModal }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.allocation.lowUQScoreModal', + }); const [isChecked, setIsChecked] = useState(false); - const navigate = useNavigate(); + const [isRefetchRequired, setIsRefetchRequired] = useState(false); + + const { address } = useAccount(); + + const { data: currentEpoch } = useCurrentEpoch(); + const { + data: uqScore, + refetch: refetchUqScore, + isFetching: isFetchingUqScore, + } = useUqScore(currentEpoch!); + const { mutateAsync: refreshAntisybilStatus, isPending: isPendingRefreshAntisybilStatus } = + useRefreshAntisybilStatus(); + + const windowOnFocus = () => { + if (!address) { + return; + } + refreshAntisybilStatus(address).then(() => { + refetchUqScore(); + setIsRefetchRequired(false); + }); + }; + + const windowOnBlur = () => { + setIsRefetchRequired(true); + }; + + useEffect(() => { + window.addEventListener('focus', windowOnFocus); + window.addEventListener('blur', windowOnBlur); + + return () => { + window.removeEventListener('focus', windowOnFocus); + window.removeEventListener('blur', windowOnBlur); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + // When user manages to increase their score to what gives UQ score of 100, close the modal. + if (!uqScore || uqScore < 100n) { + return; + } + toastService.showToast({ + message: t('toasts.success.message'), + name: 'uqScoreSuccessfullyIncreased', + title: t('toasts.success.title'), + type: 'success', + }); + onCloseModal(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [uqScore]); + + const isFetching = isRefetchRequired || isFetchingUqScore || isPendingRefreshAntisybilStatus; return ( <>
- ]} i18nKey="views.allocation.lowUQScoreModal.text" /> + ]} i18nKey="components.allocation.lowUQScoreModal.text" />
= ({ onAllocate }) =>
-
); }; diff --git a/client/src/components/Allocation/AllocationNavigation/types.ts b/client/src/components/Allocation/AllocationNavigation/types.ts index 3e48ea5a3a..a3caaa5724 100644 --- a/client/src/components/Allocation/AllocationNavigation/types.ts +++ b/client/src/components/Allocation/AllocationNavigation/types.ts @@ -1,12 +1,8 @@ -import { CurrentView } from 'views/AllocationView/types'; - export default interface AllocationNavigationProps { areButtonsDisabled: boolean; - currentView: CurrentView; isLeftButtonDisabled: boolean; isLoading: boolean; isWaitingForAllMultisigSignatures?: boolean; onAllocate: () => void; onResetValues: () => void; - setCurrentView: (newView: CurrentView) => void; } diff --git a/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.module.scss b/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.module.scss index ff39724944..b74d49c81f 100644 --- a/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.module.scss +++ b/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.module.scss @@ -1,106 +1,52 @@ .root { position: relative; - color: $color-octant-grey5; - padding: 2rem 2.4rem 2.3rem; - - @media #{$tablet-down} { - padding: 1.6rem; - } -} - -.title { - font-size: $font-size-18; - margin: 0 0 0 0.8rem; - color: $color-octant-green; - line-height: 2.4rem; - - &.greyTitle { - color: $color-octant-grey5; - } - - @media #{$tablet-down} { - font-size: $font-size-16; - line-height: 2rem; - } -} - -.subtitle { - margin-left: 0.8rem; -} - -.isManualBadge { - position: absolute; - top: 2.4rem; - right: 2.4rem; - border: 0.1rem solid $color-octant-green; - padding: 0.2rem 0.5rem; - text-transform: uppercase; - border-radius: $border-radius-04; - color: $color-octant-green; - background: $color-octant-green5; - line-height: 1.4rem; -} - -.sliderWrapper { - display: flex; - align-items: center; - width: 100%; - height: 7.2rem; - background-color: $color-octant-grey6; - border-radius: $border-radius-08; - padding: 0 1.6rem; - margin: 1.6rem 0 1.2rem; + min-height: 7.2rem; + max-height: 7.2rem; + border-radius: $border-radius-16; + background-color: $color-octant-grey8; + padding: 1.6rem 1.6rem 0; + font-weight: $font-weight-bold; + text-align: left; + margin-bottom: 1.6rem; &.isManuallyEdited { background-color: $color-octant-green5; } - @media #{$tablet-down} { - margin: 1.1rem 0 0.8rem; - } -} - -.sections { - display: flex; - justify-content: space-between; - align-self: stretch; - margin: 0 0.8rem; -} - -.section { - &:not(.isDisabled):not(.isLocked) { - cursor: pointer; - } - - .header { - line-height: 1.6rem; - padding: 0.4rem 0 0.1rem 0; + @media #{$tablet-up} { + padding: 2rem 2.4rem 0; } .value { - line-height: 2rem; - font-size: $font-size-18; - color: $color-octant-dark; - height: 1.6rem; + color: $color-octant-green; + font-size: $font-size-16; + line-height: 2.4rem; - &.isGrey { - color: $color-octant-grey5; + @media #{$tablet-up} { + font-size: $font-size-18; } - @media #{$tablet-down} { - font-size: $font-size-16; + &.isGrey { + color: $color-octant-grey5; } } - &:first-child { - text-align: left; - } - - &:last-child { - text-align: right; + .label { + font-size: $font-size-12; + color: $color-octant-grey5; } - &.isDisabled { - color: $color-octant-grey5; + .manuallyEditedLabel { + position: absolute; + top: 50%; + transform: translate(0, -50%); + right: 2.2rem; + border: 0.1rem solid $color-octant-green; + padding: 0.2rem 0.5rem; + text-transform: uppercase; + border-radius: $border-radius-04; + color: $color-octant-green; + background: $color-octant-green5; + line-height: 1.4rem; } } diff --git a/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.tsx b/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.tsx index ca97574569..7ef95b77d7 100644 --- a/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.tsx +++ b/client/src/components/Allocation/AllocationRewardsBox/AllocationRewardsBox.tsx @@ -1,27 +1,19 @@ import cx from 'classnames'; -import throttle from 'lodash/throttle'; -import React, { FC, useState, useCallback, useMemo } from 'react'; +import React, { FC, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import ModalAllocationValuesEdit from 'components/Allocation/ModalAllocationValuesEdit'; -import BoxRounded from 'components/ui/BoxRounded'; -import Slider from 'components/ui/Slider'; import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; import useIndividualReward from 'hooks/queries/useIndividualReward'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useUpcomingBudget from 'hooks/queries/useUpcomingBudget'; -import useAllocationsStore from 'store/allocations/store'; import styles from './AllocationRewardsBox.module.scss'; import AllocationRewardsBoxProps from './types'; const AllocationRewardsBox: FC = ({ - className, isDisabled, - isManuallyEdited, isLocked, - isError, - setRewardsForProjectsCallback, + isManuallyEdited, }) => { const { i18n, t } = useTranslation('translation', { keyPrefix: 'components.dedicated.allocationRewardsBox', @@ -29,78 +21,12 @@ const AllocationRewardsBox: FC = ({ const { data: individualReward } = useIndividualReward(); const { data: upcomingBudget } = useUpcomingBudget(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const [modalMode, setModalMode] = useState<'closed' | 'donate' | 'withdraw'>('closed'); - const { rewardsForProjects, setRewardsForProjects } = useAllocationsStore(state => ({ - rewardsForProjects: state.data.rewardsForProjects, - setRewardsForProjects: state.setRewardsForProjects, - })); const getValuesToDisplay = useGetValuesToDisplay(); const hasUserIndividualReward = !!individualReward && individualReward !== 0n; const isDecisionWindowOpenAndHasIndividualReward = hasUserIndividualReward && isDecisionWindowOpen; - const onSetRewardsForProjects = (rewardsForProjectsNew: bigint) => { - if (!individualReward || isDisabled) { - return; - } - setRewardsForProjects(rewardsForProjectsNew); - setRewardsForProjectsCallback({ rewardsForProjectsNew }); - }; - - const onUpdateValueModal = (newValue: bigint) => { - const rewardsForProjectsNew = modalMode === 'donate' ? newValue : individualReward! - newValue; - onSetRewardsForProjects(rewardsForProjectsNew); - }; - - const onUpdateValueSlider = (index: number) => { - if (!individualReward || isDisabled) { - return; - } - const rewardsForProjectsNew = (individualReward * BigInt(index)) / BigInt(100); - onSetRewardsForProjects(rewardsForProjectsNew); - }; - - // eslint-disable-next-line react-hooks/exhaustive-deps - const onUpdateValueSliderThrottled = useCallback( - throttle(onUpdateValueSlider, 250, { trailing: true }), - [isDisabled, individualReward, setRewardsForProjectsCallback], - ); - - const rewardsForProjectsFinal = isDecisionWindowOpenAndHasIndividualReward - ? rewardsForProjects - : BigInt(0); - const rewardsForWithdraw = isDecisionWindowOpenAndHasIndividualReward - ? individualReward - rewardsForProjects - : BigInt(0); - - const percentRewardsForProjects = isDecisionWindowOpenAndHasIndividualReward - ? Number((rewardsForProjects * BigInt(100)) / individualReward) - : 50; - const percentWithdraw = isDecisionWindowOpenAndHasIndividualReward - ? Number((rewardsForWithdraw * BigInt(100)) / individualReward) - : 50; - const sections = [ - { - header: isLocked ? t('donated') : t('donate'), - value: getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - showLessThanOneCentFiat: !isDisabled, - valueCrypto: rewardsForProjectsFinal, - }).primary, - }, - { - header: i18n.t('common.personal'), - value: getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - showLessThanOneCentFiat: !isDisabled, - valueCrypto: rewardsForWithdraw, - }).primary, - }, - ]; - const subtitle = useMemo(() => { if (isDecisionWindowOpen === false && upcomingBudget) { return t('availableDuringAllocation'); @@ -132,86 +58,22 @@ const AllocationRewardsBox: FC = ({ }, [isDecisionWindowOpen, individualReward, upcomingBudget]); return ( - - {!isDisabled && isManuallyEdited &&
{t('manual')}
} -
- -
-
- {sections.map(({ header, value }, index) => ( -
- isLocked || isDisabled ? {} : setModalMode(index === 0 ? 'donate' : 'withdraw') - } - > -
- {header} -
-
- {value} -
-
- ))} +
+
+
+ { + getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + showLessThanOneCentFiat: !isDisabled, + valueCrypto: budget, + }).primary + } +
+
{subtitle}
- setModalMode('closed'), - }} - onUpdateValue={newValue => onUpdateValueModal(newValue)} - valueCryptoSelected={modalMode === 'donate' ? rewardsForProjects : rewardsForWithdraw} - valueCryptoTotal={individualReward!} - /> - + {isManuallyEdited &&
{t('manual')}
} +
); }; diff --git a/client/src/components/Allocation/AllocationRewardsBox/types.ts b/client/src/components/Allocation/AllocationRewardsBox/types.ts index c3f5c44759..fc6325543e 100644 --- a/client/src/components/Allocation/AllocationRewardsBox/types.ts +++ b/client/src/components/Allocation/AllocationRewardsBox/types.ts @@ -1,8 +1,5 @@ export default interface AllocationRewardsBoxProps { - className?: string; isDisabled?: boolean; - isError: boolean; isLocked?: boolean; - isManuallyEdited?: boolean; - setRewardsForProjectsCallback: ({ rewardsForProjectsNew }) => void; + isManuallyEdited: boolean; } diff --git a/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.module.scss b/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.module.scss new file mode 100644 index 0000000000..3b83c8a8d9 --- /dev/null +++ b/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.module.scss @@ -0,0 +1,70 @@ +.root { + position: relative; + color: $color-octant-grey5; + padding: 1.6rem; + + @media #{$tablet-up} { + padding: 2.4rem; + } +} + +.sliderWrapper { + display: flex; + align-items: center; + width: 100%; + background-color: $color-octant-grey6; + border-radius: $border-radius-08; + padding: 0 1.6rem; + margin-bottom: 0.8rem; + height: 7.2rem; + + @media #{$tablet-up} { + margin-bottom: 1.6rem; + height: 10.4rem; + } +} + +.sections { + display: flex; + justify-content: space-between; + align-self: stretch; + margin: 0 0.8rem; +} + +.section { + &:not(.isDisabled):not(.isLocked) { + cursor: pointer; + } + + .header { + font-size: $font-size-12; + line-height: 2.4rem; + } + + .value { + line-height: 2rem; + font-size: $font-size-16; + color: $color-octant-dark; + height: 1.6rem; + + &.isGrey { + color: $color-octant-grey5; + } + + @media #{$tablet-up} { + font-size: $font-size-18; + } + } + + &:first-child { + text-align: left; + } + + &:last-child { + text-align: right; + } + + &.isDisabled { + color: $color-octant-grey5; + } +} diff --git a/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.tsx b/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.tsx new file mode 100644 index 0000000000..a5da7c8952 --- /dev/null +++ b/client/src/components/Allocation/AllocationSliderBox/AllocationSliderBox.tsx @@ -0,0 +1,168 @@ +import cx from 'classnames'; +import throttle from 'lodash/throttle'; +import React, { FC, useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; + +import ModalAllocationValuesEdit from 'components/Allocation/ModalAllocationValuesEdit'; +import BoxRounded from 'components/ui/BoxRounded'; +import Slider from 'components/ui/Slider'; +import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; +import useIndividualReward from 'hooks/queries/useIndividualReward'; +import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import useAllocationsStore from 'store/allocations/store'; + +import styles from './AllocationSliderBox.module.scss'; +import AllocationSliderBoxProps from './types'; + +const AllocationSliderBox: FC = ({ + className, + isDisabled, + isLocked, + isError, + setRewardsForProjectsCallback, +}) => { + const { i18n, t } = useTranslation('translation', { + keyPrefix: 'components.dedicated.allocationRewardsBox', + }); + const { data: individualReward } = useIndividualReward(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const [modalMode, setModalMode] = useState<'closed' | 'donate' | 'withdraw'>('closed'); + const { rewardsForProjects, setRewardsForProjects } = useAllocationsStore(state => ({ + rewardsForProjects: state.data.rewardsForProjects, + setRewardsForProjects: state.setRewardsForProjects, + })); + const getValuesToDisplay = useGetValuesToDisplay(); + + const hasUserIndividualReward = !!individualReward && individualReward !== 0n; + const isDecisionWindowOpenAndHasIndividualReward = + hasUserIndividualReward && isDecisionWindowOpen; + + const onSetRewardsForProjects = (rewardsForProjectsNew: bigint) => { + if (!individualReward || isDisabled) { + return; + } + setRewardsForProjects(rewardsForProjectsNew); + setRewardsForProjectsCallback({ rewardsForProjectsNew }); + }; + + const onUpdateValueModal = (newValue: bigint) => { + const rewardsForProjectsNew = modalMode === 'donate' ? newValue : individualReward! - newValue; + onSetRewardsForProjects(rewardsForProjectsNew); + }; + + const onUpdateValueSlider = (index: number) => { + if (!individualReward || isDisabled) { + return; + } + const rewardsForProjectsNew = (individualReward * BigInt(index)) / BigInt(100); + onSetRewardsForProjects(rewardsForProjectsNew); + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + const onUpdateValueSliderThrottled = useCallback( + throttle(onUpdateValueSlider, 250, { trailing: true }), + [isDisabled, individualReward, setRewardsForProjectsCallback], + ); + + const rewardsForProjectsFinal = isDecisionWindowOpenAndHasIndividualReward + ? rewardsForProjects + : BigInt(0); + const rewardsForWithdraw = isDecisionWindowOpenAndHasIndividualReward + ? individualReward - rewardsForProjects + : BigInt(0); + + const percentRewardsForProjects = isDecisionWindowOpenAndHasIndividualReward + ? Number((rewardsForProjects * BigInt(100)) / individualReward) + : 50; + const percentWithdraw = isDecisionWindowOpenAndHasIndividualReward + ? Number((rewardsForWithdraw * BigInt(100)) / individualReward) + : 50; + const sections = [ + { + header: isLocked ? i18n.t('common.donated') : t('donate'), + value: getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + showLessThanOneCentFiat: !isDisabled, + valueCrypto: rewardsForProjectsFinal, + }).primary, + }, + { + header: i18n.t('common.personal'), + value: getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + showLessThanOneCentFiat: !isDisabled, + valueCrypto: rewardsForWithdraw, + }).primary, + }, + ]; + + return ( + +
+ +
+
+ {sections.map(({ header, value }, index) => ( +
+ isLocked || isDisabled ? {} : setModalMode(index === 0 ? 'donate' : 'withdraw') + } + > +
+ {header} +
+
+ {value} +
+
+ ))} +
+ setModalMode('closed'), + }} + onUpdateValue={newValue => onUpdateValueModal(newValue)} + valueCryptoSelected={modalMode === 'donate' ? rewardsForProjects : rewardsForWithdraw} + valueCryptoTotal={individualReward!} + /> +
+ ); +}; + +export default AllocationSliderBox; diff --git a/client/src/components/Allocation/AllocationSliderBox/index.tsx b/client/src/components/Allocation/AllocationSliderBox/index.tsx new file mode 100644 index 0000000000..1e04edb4f9 --- /dev/null +++ b/client/src/components/Allocation/AllocationSliderBox/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './AllocationSliderBox'; diff --git a/client/src/components/Allocation/AllocationSliderBox/types.ts b/client/src/components/Allocation/AllocationSliderBox/types.ts new file mode 100644 index 0000000000..2afa91b241 --- /dev/null +++ b/client/src/components/Allocation/AllocationSliderBox/types.ts @@ -0,0 +1,7 @@ +export default interface AllocationSliderBoxProps { + className?: string; + isDisabled?: boolean; + isError: boolean; + isLocked?: boolean; + setRewardsForProjectsCallback: ({ rewardsForProjectsNew }) => void; +} diff --git a/client/src/components/Allocation/AllocationSummaryProject/AllocationSummaryProject.tsx b/client/src/components/Allocation/AllocationSummaryProject/AllocationSummaryProject.tsx index fec865d120..e69d565462 100644 --- a/client/src/components/Allocation/AllocationSummaryProject/AllocationSummaryProject.tsx +++ b/client/src/components/Allocation/AllocationSummaryProject/AllocationSummaryProject.tsx @@ -21,7 +21,7 @@ const AllocationSummaryProject: FC = ({ value, }) => { const { t } = useTranslation('translation', { - keyPrefix: 'views.allocation.allocationItem', + keyPrefix: 'components.allocation.allocationItem', }); const isDonationAboveThreshold = useIsDonationAboveThreshold({ projectAddress: address }); const { diff --git a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.module.scss b/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.module.scss deleted file mode 100644 index 46ac8138a2..0000000000 --- a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.module.scss +++ /dev/null @@ -1,20 +0,0 @@ -.bold { - color: $color-octant-green; - font-weight: $font-weight-bold; -} - -.uqTooLowImage { - height: 7.2rem; - - @media #{$desktop-up} { - height: 10rem; - } -} - -.rewardsImage { - height: 8.5rem; - - @media #{$desktop-up} { - height: 11rem; - } -} diff --git a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx b/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx deleted file mode 100644 index 97e2fd2b3b..0000000000 --- a/client/src/components/Allocation/AllocationTipTiles/AllocationTipTiles.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import React, { FC, Fragment, useEffect } from 'react'; -import { Trans, useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; -import { useAccount } from 'wagmi'; - -import TipTile from 'components/shared/TipTile'; -import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import useRefreshAntisybilStatus from 'hooks/mutations/useRefreshAntisybilStatus'; -import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import useIndividualReward from 'hooks/queries/useIndividualReward'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; -import useUqScore from 'hooks/queries/useUqScore'; -import useUserAllocations from 'hooks/queries/useUserAllocations'; -import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; -import useTipsStore from 'store/tips/store'; - -import styles from './AllocationTipTiles.module.scss'; -import AllocationTipTilesProps from './types'; - -const AllocationTipTiles: FC = ({ className }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.allocation.tip' }); - const navigate = useNavigate(); - const { isDesktop } = useMediaQuery(); - const { address, isConnected } = useAccount(); - const { - mutateAsync: refreshAntisybilStatus, - isPending: isPendingRefreshAntisybilStatus, - isSuccess: isSuccessRefreshAntisybilStatus, - error: refreshAntisybilStatusError, - } = useRefreshAntisybilStatus(); - const { data: currentEpoch } = useCurrentEpoch(); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const { data: individualReward, isFetching: isFetchingIndividualReward } = useIndividualReward(); - const { data: userAllocations, isFetching: isFetchingUserAllocation } = useUserAllocations(); - const { data: uqScore, isFetching: isFetchingUqScore } = useUqScore(currentEpoch!, { - enabled: - isSuccessRefreshAntisybilStatus || - (refreshAntisybilStatusError as null | { message: string })?.message === - 'Address is already used for delegation', - }); - const { - wasRewardsAlreadyClosed, - setWasRewardsAlreadyClosed, - wasUqTooLowAlreadyClosed, - setWasUqTooLowAlreadyClosed, - } = useTipsStore(state => ({ - setWasConnectWalletAlreadyClosed: state.setWasConnectWalletAlreadyClosed, - setWasRewardsAlreadyClosed: state.setWasRewardsAlreadyClosed, - setWasUqTooLowAlreadyClosed: state.setWasUqTooLowAlreadyClosed, - wasConnectWalletAlreadyClosed: state.data.wasConnectWalletAlreadyClosed, - wasRewardsAlreadyClosed: state.data.wasRewardsAlreadyClosed, - wasUqTooLowAlreadyClosed: state.data.wasUqTooLowAlreadyClosed, - })); - - useEffect(() => { - if (!address) { - return; - } - /** - * The initial value of UQ for every user is 0.2. - * It does not update automatically after delegation nor after change in Gitcoin Passport itself. - * - * We need to refreshAntisybilStatus to force BE to refetch current values from Gitcoin Passport - * and return true value. - */ - refreshAntisybilStatus(address!); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const isEpoch1 = currentEpoch === 1; - - const isUqTooLowTipVisible = - !!isDecisionWindowOpen && - !isPendingRefreshAntisybilStatus && - !isFetchingUqScore && - uqScore === 20n && - !wasUqTooLowAlreadyClosed; - - const isRewardsTipVisible = - !isEpoch1 && - isConnected && - !isFetchingIndividualReward && - !!individualReward && - individualReward !== 0n && - !isFetchingUserAllocation && - !userAllocations?.hasUserAlreadyDoneAllocation && - !!isDecisionWindowOpen && - !wasRewardsAlreadyClosed; - - return ( - - navigate(ROOT_ROUTES.settings.absolute)} - onClose={() => setWasUqTooLowAlreadyClosed(true)} - text={ - ]} - i18nKey={ - isDesktop - ? 'views.allocation.tip.uqTooLow.text.desktop' - : 'views.allocation.tip.uqTooLow.text.mobile' - } - /> - } - title={isDesktop ? t('uqTooLow.title.desktop') : t('uqTooLow.title.mobile')} - /> - setWasRewardsAlreadyClosed(true)} - text={isDesktop ? t('rewards.text.desktop') : t('rewards.text.mobile')} - title={t('rewards.title')} - /> - - ); -}; - -export default AllocationTipTiles; diff --git a/client/src/components/Allocation/AllocationTipTiles/types.ts b/client/src/components/Allocation/AllocationTipTiles/types.ts deleted file mode 100644 index 6ce64c2c9c..0000000000 --- a/client/src/components/Allocation/AllocationTipTiles/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface AllocationTipTilesProps { - className?: string; -} diff --git a/client/src/components/Allocation/ModalAllocationLowUqScore/ModalAllocationLowUqScore.tsx b/client/src/components/Allocation/ModalAllocationLowUqScore/ModalAllocationLowUqScore.tsx index 01fa5a8e8d..9477c4439c 100644 --- a/client/src/components/Allocation/ModalAllocationLowUqScore/ModalAllocationLowUqScore.tsx +++ b/client/src/components/Allocation/ModalAllocationLowUqScore/ModalAllocationLowUqScore.tsx @@ -1,19 +1,20 @@ import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; +import AllocationLowUqScore from 'components/Allocation/AllocationLowUqScore'; import Img from 'components/ui/Img'; import Modal from 'components/ui/Modal'; import styles from './ModalAllocationLowUqScore.module.scss'; import ModalAllocationLowUqScoreProps from './types'; -import AllocationLowUqScore from '../AllocationLowUqScore'; - const ModalAllocationLowUqScore: FC = ({ modalProps, onAllocate, }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.allocation.lowUQScoreModal' }); + const { t } = useTranslation('translation', { + keyPrefix: 'components.allocation.lowUQScoreModal', + }); return ( = ({ isOverflowOnClickDisabled {...modalProps} > - + ); }; diff --git a/client/src/components/Earn/EarnHistory/index.tsx b/client/src/components/Allocation/index.tsx similarity index 54% rename from client/src/components/Earn/EarnHistory/index.tsx rename to client/src/components/Allocation/index.tsx index fffff26b2a..9532b5daf5 100644 --- a/client/src/components/Earn/EarnHistory/index.tsx +++ b/client/src/components/Allocation/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './EarnHistory'; +export { default } from './Allocation'; diff --git a/client/src/views/AllocationView/types.ts b/client/src/components/Allocation/types.ts similarity index 90% rename from client/src/views/AllocationView/types.ts rename to client/src/components/Allocation/types.ts index f0b7a2af45..dc9a9db4e7 100644 --- a/client/src/views/AllocationView/types.ts +++ b/client/src/components/Allocation/types.ts @@ -4,8 +4,6 @@ export type UserAllocationElementString = Omit & value: string; }; -export type CurrentView = 'edit' | 'summary'; - export type PercentageProportions = { [key: string]: number; }; diff --git a/client/src/views/AllocationView/utils.test.ts b/client/src/components/Allocation/utils.test.ts similarity index 100% rename from client/src/views/AllocationView/utils.test.ts rename to client/src/components/Allocation/utils.test.ts diff --git a/client/src/views/AllocationView/utils.ts b/client/src/components/Allocation/utils.ts similarity index 100% rename from client/src/views/AllocationView/utils.ts rename to client/src/components/Allocation/utils.ts diff --git a/client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.module.scss b/client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.module.scss deleted file mode 100644 index 2708f078c1..0000000000 --- a/client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.module.scss +++ /dev/null @@ -1,17 +0,0 @@ -.tooltip { - width: 10.4rem !important; -} - -.calculateRewards { - display: flex; - align-items: center; - justify-content: center; - width: 3.2rem; - height: 3.2rem; - background: $color-octant-grey8; - border-radius: $border-radius-10; -} - -.effectiveTooltip { - padding: 1.8rem 1.4rem 1.4rem 2.4rem !important; -} diff --git a/client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.tsx b/client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.tsx deleted file mode 100644 index c3801b49af..0000000000 --- a/client/src/components/Earn/EarnBoxGlmLock/EarnBoxGlmLock.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import _first from 'lodash/first'; -import React, { FC, Fragment, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useAccount } from 'wagmi'; - -import ModalEarnGlmLock from 'components/Earn/ModalEarnGlmLock/ModalEarnGlmLock'; -import ModalEarnRewardsCalculator from 'components/Earn/ModalEarnRewardsCalculator'; -import BoxRounded from 'components/ui/BoxRounded'; -import Sections from 'components/ui/BoxRounded/Sections/Sections'; -import { SectionProps } from 'components/ui/BoxRounded/Sections/types'; -import Svg from 'components/ui/Svg'; -import Tooltip from 'components/ui/Tooltip'; -import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import useDepositValue from 'hooks/queries/useDepositValue'; -import useEstimatedEffectiveDeposit from 'hooks/queries/useEstimatedEffectiveDeposit'; -import useTransactionLocalStore from 'store/transactionLocal/store'; -import { calculator } from 'svg/misc'; -import getIsPreLaunch from 'utils/getIsPreLaunch'; - -import styles from './EarnBoxGlmLock.module.scss'; -import EarnBoxGlmLockProps from './types'; - -const EarnBoxGlmLock: FC = ({ classNameBox }) => { - const { t, i18n } = useTranslation('translation', { - keyPrefix: 'components.dedicated.boxGlmLock', - }); - const { isConnected } = useAccount(); - const { isAppWaitingForTransactionToBeIndexed, transactionsPending } = useTransactionLocalStore( - state => ({ - isAppWaitingForTransactionToBeIndexed: state.data.isAppWaitingForTransactionToBeIndexed, - transactionsPending: state.data.transactionsPending, - }), - ); - - const [isModalGlmLockOpen, setIsModalGlmLockOpen] = useState(false); - const { data: estimatedEffectiveDeposit, isFetching: isFetchingEstimatedEffectiveDeposit } = - useEstimatedEffectiveDeposit(); - const [isModalRewardsCalculatorOpen, setIsModalRewardsCalculatorOpen] = useState(false); - const { data: depositsValue, isFetching: isFetchingDepositValue } = useDepositValue(); - const { data: currentEpoch } = useCurrentEpoch(); - - const isPreLaunch = getIsPreLaunch(currentEpoch); - - const sections: SectionProps[] = [ - { - dataTest: 'BoxGlmLock__Section--current', - doubleValueProps: { - cryptoCurrency: 'golem', - dataTest: 'BoxGlmLock__Section--current__DoubleValue', - isFetching: - isFetchingDepositValue || - (isAppWaitingForTransactionToBeIndexed && - _first(transactionsPending)?.type !== 'withdrawal'), - showCryptoSuffix: true, - valueCrypto: depositsValue, - }, - isDisabled: isPreLaunch && !isConnected, - label: t('current'), - }, - { - dataTest: 'BoxGlmLock__Section--effective', - doubleValueProps: { - coinPricesServerDowntimeText: '...', - cryptoCurrency: 'golem', - dataTest: 'BoxGlmLock__Section--effective__DoubleValue', - isFetching: - isFetchingEstimatedEffectiveDeposit || - (isAppWaitingForTransactionToBeIndexed && - _first(transactionsPending)?.type !== 'withdrawal'), - showCryptoSuffix: true, - valueCrypto: estimatedEffectiveDeposit, - }, - isDisabled: isPreLaunch && !isConnected, - label: t('effective'), - tooltipProps: { - dataTest: 'TooltipEffectiveLockedBalance', - position: 'bottom-right', - text: t('tooltipText'), - tooltipClassName: styles.effectiveTooltip, - }, - }, - ]; - - return ( - - setIsModalGlmLockOpen(true), - variant: 'cta', - }} - className={classNameBox} - dataTest="BoxGlmLock__BoxRounded" - hasSections - isVertical - title={t('lockedBalance')} - titleSuffix={ - -
setIsModalRewardsCalculatorOpen(true)} - > - -
-
- } - > - -
- setIsModalGlmLockOpen(false), - }} - /> - setIsModalRewardsCalculatorOpen(false), - }} - /> -
- ); -}; - -export default EarnBoxGlmLock; diff --git a/client/src/components/Earn/EarnBoxGlmLock/types.ts b/client/src/components/Earn/EarnBoxGlmLock/types.ts deleted file mode 100644 index 10b646c2a4..0000000000 --- a/client/src/components/Earn/EarnBoxGlmLock/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface EarnBoxGlmLockProps { - classNameBox: string; -} diff --git a/client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.module.scss b/client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.module.scss deleted file mode 100644 index 7130b1e153..0000000000 --- a/client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -.pendingTooltip { - margin-left: -2.6rem; -} - -.pendingTooltipLabel { - font-size: $font-size-12; - font-weight: $font-weight-semibold; - line-height: 2rem; -} - -.pendingTooltipDate { - font-size: $font-size-20; - font-weight: $font-weight-semibold; - margin: 0.8rem 0 0.4rem; -} diff --git a/client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.tsx b/client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.tsx deleted file mode 100644 index c2dfb6ee78..0000000000 --- a/client/src/components/Earn/EarnBoxPersonalAllocation/EarnBoxPersonalAllocation.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { format } from 'date-fns'; -import _first from 'lodash/first'; -import React, { FC, Fragment, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useAccount } from 'wagmi'; - -import ModalEarnWithdrawEth from 'components/Earn/ModalEarnWithdrawEth'; -import BoxRounded from 'components/ui/BoxRounded'; -import Sections from 'components/ui/BoxRounded/Sections/Sections'; -import { SectionProps } from 'components/ui/BoxRounded/Sections/types'; -import useEpochAndAllocationTimestamps from 'hooks/helpers/useEpochAndAllocationTimestamps'; -import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; -import useTotalPatronDonations from 'hooks/helpers/useTotalPatronDonations'; -import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import useCurrentEpochProps from 'hooks/queries/useCurrentEpochProps'; -import useIndividualReward from 'hooks/queries/useIndividualReward'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; -import useIsPatronMode from 'hooks/queries/useIsPatronMode'; -import useWithdrawals from 'hooks/queries/useWithdrawals'; -import useTransactionLocalStore from 'store/transactionLocal/store'; -import getIsPreLaunch from 'utils/getIsPreLaunch'; - -import styles from './EarnBoxPersonalAllocation.module.scss'; -import EarnBoxPersonalAllocationProps from './types'; - -const EarnBoxPersonalAllocation: FC = ({ className }) => { - const { i18n, t } = useTranslation('translation', { - keyPrefix: 'components.dedicated.boxPersonalAllocation', - }); - const [isModalOpen, setIsModalOpen] = useState(false); - const { isConnected } = useAccount(); - const { data: currentEpoch } = useCurrentEpoch(); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const { timeCurrentEpochStart, timeCurrentAllocationEnd } = useEpochAndAllocationTimestamps(); - const { data: currentEpochProps } = useCurrentEpochProps(); - const { data: withdrawals, isFetching: isFetchingWithdrawals } = useWithdrawals(); - const { data: individualReward, isFetching: isFetchingIndividualReward } = useIndividualReward(); - const { data: isPatronMode } = useIsPatronMode(); - const { data: totalPatronDonations, isFetching: isFetchingTotalPatronDonations } = - useTotalPatronDonations({ isEnabledAdditional: !!isPatronMode }); - const { isAppWaitingForTransactionToBeIndexed, transactionsPending } = useTransactionLocalStore( - state => ({ - isAppWaitingForTransactionToBeIndexed: state.data.isAppWaitingForTransactionToBeIndexed, - transactionsPending: state.data.transactionsPending, - }), - ); - - const isPreLaunch = getIsPreLaunch(currentEpoch); - const isProjectAdminMode = useIsProjectAdminMode(); - - const sections: SectionProps[] = [ - ...(!isProjectAdminMode - ? [ - { - dataTest: 'BoxPersonalAllocation__Section', - doubleValueProps: { - cryptoCurrency: 'ethereum', - dataTest: 'BoxPersonalAllocation__Section--pending__DoubleValue', - isFetching: - (isPatronMode ? isFetchingIndividualReward : isFetchingWithdrawals) || - (isAppWaitingForTransactionToBeIndexed && - _first(transactionsPending)?.type === 'withdrawal'), - showCryptoSuffix: true, - valueCrypto: isPatronMode ? individualReward : withdrawals?.sums.pending, - }, - label: isPatronMode ? t('currentEpoch') : t('pending'), - tooltipProps: isPatronMode - ? undefined - : { - position: 'bottom-right', - text: ( -
-
- {t('pendingFundsAvailableAfter')} -
-
- {/* TODO OCT-1041 fetch next epoch props instead of assuming the same length */} - {currentEpochProps && timeCurrentEpochStart && timeCurrentAllocationEnd - ? format( - new Date( - isDecisionWindowOpen - ? timeCurrentAllocationEnd - : // When AW is closed, it's when the last AW closed. - timeCurrentEpochStart + currentEpochProps.decisionWindow, - ), - 'haaa z, d LLLL', - ) - : ''} -
-
- ), - }, - } as SectionProps, - ] - : []), - { - dataTest: 'BoxPersonalAllocation__Section', - doubleValueProps: { - coinPricesServerDowntimeText: !isProjectAdminMode ? '...' : undefined, - cryptoCurrency: 'ethereum', - dataTest: 'BoxPersonalAllocation__Section--availableNow__DoubleValue', - isFetching: - (isPatronMode ? isFetchingTotalPatronDonations : isFetchingWithdrawals) || - (isAppWaitingForTransactionToBeIndexed && - _first(transactionsPending)?.type === 'withdrawal'), - showCryptoSuffix: true, - valueCrypto: isPatronMode ? totalPatronDonations?.value : withdrawals?.sums.available, - }, - label: isPatronMode && !isProjectAdminMode ? t('allTime') : i18n.t('common.availableNow'), - }, - ]; - - const title = useMemo(() => { - if (isProjectAdminMode) { - return i18n.t('common.donations'); - } - if (isPatronMode) { - return t('patronEarnings'); - } - return i18n.t('common.personalAllocation'); - }, [isProjectAdminMode, isPatronMode, i18n, t]); - - return ( - - setIsModalOpen(true), - variant: isProjectAdminMode ? 'cta' : 'secondary', - } - } - className={className} - dataTest="BoxPersonalAllocation" - hasSections - isVertical - title={title} - > - - - setIsModalOpen(false), - }} - /> - - ); -}; - -export default EarnBoxPersonalAllocation; diff --git a/client/src/components/Earn/EarnBoxPersonalAllocation/index.tsx b/client/src/components/Earn/EarnBoxPersonalAllocation/index.tsx deleted file mode 100644 index dedc208a26..0000000000 --- a/client/src/components/Earn/EarnBoxPersonalAllocation/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnBoxPersonalAllocation'; diff --git a/client/src/components/Earn/EarnBoxPersonalAllocation/types.ts b/client/src/components/Earn/EarnBoxPersonalAllocation/types.ts deleted file mode 100644 index c73db828a2..0000000000 --- a/client/src/components/Earn/EarnBoxPersonalAllocation/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface EarnBoxPersonalAllocationProps { - className?: string; -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.tsx deleted file mode 100644 index 2dc3545210..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useFormikContext } from 'formik'; -import React, { FC } from 'react'; - -import EarnGlmLockBudgetBox from 'components/Earn/EarnGlmLock/EarnGlmLockBudgetBox'; -import { FormFields } from 'components/Earn/EarnGlmLock/types'; - -import styles from './EarnGlmLockBudget.module.scss'; -import EarnGlmLockBudgetProps from './types'; - -const EarnGlmLockBudget: FC = ({ isVisible }) => { - const { errors } = useFormikContext(); - - if (!isVisible) { - return null; - } - - return ( - - ); -}; - -export default EarnGlmLockBudget; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/types.ts b/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/types.ts deleted file mode 100644 index 4be2791aed..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface EarnGlmLockBudgetProps { - isVisible: boolean; -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/index.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/index.tsx deleted file mode 100644 index 6f753d1088..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnGlmLockNotification'; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/types.ts b/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/types.ts deleted file mode 100644 index 44c72a0b37..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { CurrentMode } from 'components/Earn/EarnGlmLock/types'; - -export default interface EarnGlmLockNotificationProps { - className?: string; - currentMode: CurrentMode; - isLockingApproved: boolean; - transactionHash?: string; - type: 'success' | 'info'; -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/types.ts b/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/types.ts deleted file mode 100644 index 5eee7e952c..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { CurrentMode } from '../types'; - -export default interface EarnGlmLockStepperProps { - className?: string; - currentMode: CurrentMode; - step: 1 | 2 | 3; -} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabsInputs/index.tsx b/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabsInputs/index.tsx deleted file mode 100644 index 74a7f24751..0000000000 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabsInputs/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnGlmLockTabsInputs'; diff --git a/client/src/components/Earn/EarnHistory/EarnHistory.module.scss b/client/src/components/Earn/EarnHistory/EarnHistory.module.scss deleted file mode 100644 index 3d6d260a79..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistory.module.scss +++ /dev/null @@ -1,43 +0,0 @@ -.root { - width: 100%; - padding: 0 0.8rem; - - @media #{$desktop-up} { - max-height: 100%; - - &.isProjectAdminMode { - min-height: 22.5rem; - } - } -} - -.skeleton { - padding: 0 $historyHorizontalPadding; -} - -.title { - padding: 0 $historyHorizontalPadding; -} - -.childrenWrapper { - display: block; - - @media #{$desktop-up} { - overflow: auto; - } -} - -.loader { - margin: 0 auto; -} - -.header { - font-size: $font-size-16; - font-weight: $font-weight-bold; - margin: 2rem 0; - text-align: left; - - @media #{$desktop-up} { - display: none; - } -} diff --git a/client/src/components/Earn/EarnHistory/EarnHistory.tsx b/client/src/components/Earn/EarnHistory/EarnHistory.tsx deleted file mode 100644 index 2391ad3646..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistory.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import cx from 'classnames'; -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; -import InfiniteScroll from 'react-infinite-scroller'; - -import EarnHistoryList from 'components/Earn/EarnHistory/EarnHistoryList'; -import EarnHistorySkeleton from 'components/Earn/EarnHistory/EarnHistorySkeleton'; -import BoxRounded from 'components/ui/BoxRounded'; -import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; -import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import useHistory from 'hooks/queries/useHistory'; -import useTransactionLocalStore from 'store/transactionLocal/store'; -import getIsPreLaunch from 'utils/getIsPreLaunch'; - -import styles from './EarnHistory.module.scss'; -import EarnHistoryProps from './types'; - -const EarnHistory: FC = ({ className }) => { - const { i18n } = useTranslation('translation'); - const { transactionsPending } = useTransactionLocalStore(state => ({ - transactionsPending: state.data.transactionsPending, - })); - - const { data: currentEpoch } = useCurrentEpoch(); - const { fetchNextPage, history, hasNextPage, isFetching: isFetchingHistory } = useHistory(); - const isProjectAdminMode = useIsProjectAdminMode(); - - const isPreLaunch = getIsPreLaunch(currentEpoch); - const showLoader = isFetchingHistory && !isPreLaunch && !history?.length; - - const transactionsPendingSorted = transactionsPending?.sort( - ({ timestamp: timestampA }, { timestamp: timestampB }) => { - if (timestampA < timestampB) { - return 1; - } - if (timestampA > timestampB) { - return -1; - } - return 0; - }, - ); - - return ( - - {showLoader ? ( -
- -
- ) : ( - } - loadMore={fetchNextPage} - pageStart={0} - > - - - )} -
- ); -}; - -export default EarnHistory; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItem/EarnHistoryItem.module.scss b/client/src/components/Earn/EarnHistory/EarnHistoryItem/EarnHistoryItem.module.scss deleted file mode 100644 index 44d5a79741..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItem/EarnHistoryItem.module.scss +++ /dev/null @@ -1,41 +0,0 @@ -.box { - display: flex; - position: relative; - border-radius: 0; - padding: 0.8rem 0; -} - -.child { - padding: 1.3rem $historyHorizontalPadding; - border-radius: $border-radius-06; - - &:hover { - background: $color-octant-grey6; - } -} - -.titleAndSubtitle { - display: flex; - flex: 1; - flex-direction: column; - align-items: flex-start; -} - -.title { - font-size: $font-size-14; -} - -.separator { - width: calc(100% - $historyHorizontalPadding * 2); - height: 0.1rem; - margin-left: $historyHorizontalPadding; - background: $color-octant-grey8; -} - -.patronDonationTimestamp { - color: $color-octant-grey5; - font-size: $font-size-10; - font-weight: $font-weight-semibold; - margin-top: 0.2rem; - line-height: 1.5rem; -} diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/EarnHistoryItemDateAndTime.tsx b/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/EarnHistoryItemDateAndTime.tsx deleted file mode 100644 index a76a811502..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/EarnHistoryItemDateAndTime.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React, { FC } from 'react'; - -import styles from './EarnHistoryItemDateAndTime.module.scss'; -import EarnHistoryItemDateAndTimeProps from './types'; -import { getHistoryItemDateAndTime } from './utils'; - -const EarnHistoryItemDateAndTime: FC = ({ timestamp }) => ( -
{getHistoryItemDateAndTime(timestamp)}
-); - -export default EarnHistoryItemDateAndTime; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/index.tsx b/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/index.tsx deleted file mode 100644 index ff21a46282..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnHistoryItemDateAndTime'; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/types.ts b/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/types.ts deleted file mode 100644 index 4b3e75c970..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDateAndTime/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface EarnHistoryItemDateAndTimeProps { - timestamp: string; -} diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetails.tsx b/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetails.tsx deleted file mode 100644 index b368f0ce2a..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetails.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React, { FC } from 'react'; - -import EarnHistoryItemDetailsAllocation from './EarnHistoryItemDetailsAllocation'; -import EarnHistoryItemDetailsAllocationProps from './EarnHistoryItemDetailsAllocation/types'; -import EarnHistoryItemDetailsRest from './EarnHistoryItemDetailsRest'; -import EarnHistoryItemDetailsModalProps from './types'; - -const EarnHistoryItemDetails: FC = props => - props.type === 'allocation' ? ( - - ) : ( - - ); - -export default EarnHistoryItemDetails; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsAllocation/index.tsx b/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsAllocation/index.tsx deleted file mode 100644 index 0434055672..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsAllocation/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnHistoryItemDetailsAllocation'; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsAllocation/types.ts b/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsAllocation/types.ts deleted file mode 100644 index a3d57d3ee4..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsAllocation/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import HistoryItemDetailsProps from 'components/Earn/EarnHistory/EarnHistoryItemDetails/types'; -import { AllocationEventTypeParsed } from 'hooks/queries/useHistory'; - -type HistoryItemDetailsAllocationProps = Omit & { - eventData: AllocationEventTypeParsed & { amount: bigint }; -}; - -export default HistoryItemDetailsAllocationProps; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsRest/index.tsx b/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsRest/index.tsx deleted file mode 100644 index b717fcdd7f..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsRest/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnHistoryItemDetailsRest'; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsRest/types.ts b/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsRest/types.ts deleted file mode 100644 index 810fe51082..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/EarnHistoryItemDetailsRest/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import HistoryItemDetailsProps from 'components/Earn/EarnHistory/EarnHistoryItemDetails/types'; - -type HistoryItemDetailsRestProps = HistoryItemDetailsProps; - -export default HistoryItemDetailsRestProps; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/index.tsx b/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/index.tsx deleted file mode 100644 index f9763b32cb..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnHistoryItemDetails'; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/types.ts b/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/types.ts deleted file mode 100644 index 4f52d52c5a..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetails/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import EarnHistoryItemProps from 'components/Earn/EarnHistory/EarnHistoryItem/types'; - -type EarnHistoryItemDetailsProps = Omit; - -export default EarnHistoryItemDetailsProps; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetailsModal/index.tsx b/client/src/components/Earn/EarnHistory/EarnHistoryItemDetailsModal/index.tsx deleted file mode 100644 index a4668c4897..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetailsModal/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnHistoryItemDetailsModal'; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetailsModal/types.ts b/client/src/components/Earn/EarnHistory/EarnHistoryItemDetailsModal/types.ts deleted file mode 100644 index 31e7f15111..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItemDetailsModal/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import HistoryItemProps from 'components/Earn/EarnHistory/EarnHistoryItem/types'; -import ModalProps from 'components/ui/Modal/types'; - -export default interface HistoryItemDetailsModalProps extends Omit { - modalProps: Omit; -} diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryList/EarnHistoryList.module.scss b/client/src/components/Earn/EarnHistory/EarnHistoryList/EarnHistoryList.module.scss deleted file mode 100644 index bb61975271..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryList/EarnHistoryList.module.scss +++ /dev/null @@ -1,10 +0,0 @@ -.emptyHistoryInfo { - height: 7.2rem; - padding: 0 $historyHorizontalPadding; - display: flex; - align-items: center; - color: $color-octant-grey5; - font-size: $font-size-14; - font-weight: $font-weight-semibold; - width: 100%; -} diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryList/EarnHistoryList.tsx b/client/src/components/Earn/EarnHistory/EarnHistoryList/EarnHistoryList.tsx deleted file mode 100644 index 191f421ecc..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryList/EarnHistoryList.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { FC, Fragment } from 'react'; -import { useTranslation } from 'react-i18next'; - -import EarnHistoryItem from 'components/Earn/EarnHistory/EarnHistoryItem'; - -import styles from './EarnHistoryList.module.scss'; -import EarnHistoryListProps from './types'; - -const EarnHistoryList: FC = ({ history }) => { - const { t } = useTranslation('translation', { keyPrefix: 'components.dedicated.historyList' }); - - if (!history?.length) { - return
{t('emptyHistory')}
; - } - - return ( - - {history.map((element, index) => ( - // eslint-disable-next-line react/no-array-index-key - - ))} - - ); -}; - -export default EarnHistoryList; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryList/index.tsx b/client/src/components/Earn/EarnHistory/EarnHistoryList/index.tsx deleted file mode 100644 index 30ab58f35f..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryList/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnHistoryList'; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryList/types.ts b/client/src/components/Earn/EarnHistory/EarnHistoryList/types.ts deleted file mode 100644 index 7b2d5ec9bf..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryList/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import EarnHistoryItemProps from 'components/Earn/EarnHistory/EarnHistoryItem/types'; - -export default interface EarnHistoryListProps { - history: Omit[] | undefined; -} diff --git a/client/src/components/Earn/EarnHistory/EarnHistorySkeleton/index.tsx b/client/src/components/Earn/EarnHistory/EarnHistorySkeleton/index.tsx deleted file mode 100644 index f135d4aeab..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistorySkeleton/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnHistorySkeleton'; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryTransactionLabel/index.tsx b/client/src/components/Earn/EarnHistory/EarnHistoryTransactionLabel/index.tsx deleted file mode 100644 index 89d0ec7f40..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryTransactionLabel/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnHistoryTransactionLabel'; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryTransactionLabel/types.ts b/client/src/components/Earn/EarnHistory/EarnHistoryTransactionLabel/types.ts deleted file mode 100644 index 48ecf3a83a..0000000000 --- a/client/src/components/Earn/EarnHistory/EarnHistoryTransactionLabel/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default interface EarnHistoryTransactionLabelProps { - isFinalized: boolean; - isMultisig?: boolean; -} diff --git a/client/src/components/Earn/EarnHistory/types.ts b/client/src/components/Earn/EarnHistory/types.ts deleted file mode 100644 index 36f13f4510..0000000000 --- a/client/src/components/Earn/EarnHistory/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface EarnHistoryProps { - className?: string; -} diff --git a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculator.module.scss b/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculator.module.scss deleted file mode 100644 index cd51f05d94..0000000000 --- a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculator.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.glmInput { - margin-bottom: 1.4rem; -} diff --git a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEpochDaysSelector/index.tsx b/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEpochDaysSelector/index.tsx deleted file mode 100644 index 8a5a8975ad..0000000000 --- a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEpochDaysSelector/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnRewardsCalculatorEpochDaysSelector'; diff --git a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/EarnRewardsCalculatorEstimates.module.scss b/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/EarnRewardsCalculatorEstimates.module.scss deleted file mode 100644 index 9fe43db3c0..0000000000 --- a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/EarnRewardsCalculatorEstimates.module.scss +++ /dev/null @@ -1,56 +0,0 @@ -.root { - width: 100%; - - .estimates { - border-radius: $border-radius-04; - border: 1px solid $color-octant-grey1; - background: $color-octant-grey8; - } - - .estimatesLabel { - color: $color-octant-grey5; - font-size: $font-size-10; - font-weight: $font-weight-bold; - line-height: normal; - margin-bottom: 0.6rem; - text-align: left; - width: 100%; - } - - .row { - display: flex; - padding: 1.2rem 1.6rem; - justify-content: space-between; - position: relative; - - &:first-child { - &::after { - position: absolute; - bottom: 0; - left: 1.6rem; - content: ''; - width: calc(100% - 3.2rem); - height: 0.1rem; - background: $color-octant-grey1; - } - } - - .label, - .value { - display: flex; - align-items: center; - height: 2.4rem; - color: $color-octant-dark; - font-size: $font-size-14; - font-weight: $font-weight-bold; - } - - .value { - &.showSkeleton { - @include skeleton($color-octant-grey1, $color-octant-grey12); - width: 10.4rem; - height: 2.4rem; - } - } - } -} diff --git a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/EarnRewardsCalculatorEstimates.tsx b/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/EarnRewardsCalculatorEstimates.tsx deleted file mode 100644 index 0525ae5229..0000000000 --- a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/EarnRewardsCalculatorEstimates.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import cx from 'classnames'; -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import styles from './EarnRewardsCalculatorEstimates.module.scss'; -import { EarnRewardsCalculatorEstimatesProps } from './types'; - -const EarnRewardsCalculatorEstimates: FC = ({ - estimatedRewards, - matchFunding, - isLoading, -}) => { - const { i18n, t } = useTranslation('translation', { - keyPrefix: 'components.dedicated.rewardsCalculator', - }); - - const dataTest = 'EarnRewardsCalculatorEstimates'; - - return ( -
-
- {t('estimates')} -
-
-
-
- {i18n.t('common.rewards', { rewards: '' })} -
-
- {estimatedRewards ? estimatedRewards.primary : ''} -
-
-
-
- {i18n.t('common.matchFunding')} -
-
- {matchFunding ? matchFunding.primary : ''} -
-
-
-
- ); -}; - -export default EarnRewardsCalculatorEstimates; diff --git a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/index.tsx b/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/index.tsx deleted file mode 100644 index c66017dc17..0000000000 --- a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorEstimates/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnRewardsCalculatorEstimates'; diff --git a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/EarnRewardsCalculatorUqSelector.module.scss b/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/EarnRewardsCalculatorUqSelector.module.scss deleted file mode 100644 index 6cbabb095f..0000000000 --- a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/EarnRewardsCalculatorUqSelector.module.scss +++ /dev/null @@ -1,63 +0,0 @@ -.root { - width: 100%; - - .daysSelectorLabel, - .estimatesLabel { - color: $color-octant-grey5; - font-size: $font-size-10; - font-weight: $font-weight-bold; - margin-bottom: 0.6rem; - text-align: left; - width: 100%; - } - - .daysSelector { - margin-bottom: 1rem; - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - height: 4.8rem; - background: $color-white; - border-radius: $border-radius-04; - border: 0.1rem solid $color-octant-grey1; - padding: 0.4rem 1.6rem 0.4rem 0.6rem; - - .daysWrapper { - display: flex; - flex: 1; - } - - .day { - position: relative; - cursor: pointer; - color: $color-octant-grey2; - font-weight: $font-weight-bold; - display: flex; - align-items: center; - justify-content: center; - flex: 1; - height: 3.8rem; - - .dayLabel { - z-index: $z-index-2; - } - - &.isSelected { - color: $color-octant-dark; - transition: all $transition-time-4; - } - - .selectedItemBackground { - position: absolute; - z-index: $z-index-1; - width: 100%; - height: 100%; - top: 0; - left: 0; - background-color: $color-octant-grey1; - border-radius: $border-radius-04; - } - } - } -} diff --git a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/EarnRewardsCalculatorUqSelector.tsx b/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/EarnRewardsCalculatorUqSelector.tsx deleted file mode 100644 index 725a5ff1f1..0000000000 --- a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/EarnRewardsCalculatorUqSelector.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import cx from 'classnames'; -import { motion } from 'framer-motion'; -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import styles from './EarnRewardsCalculatorUqSelector.module.scss'; -import EarnRewardsCalculatorUqSelectorProps from './types'; - -const EarnRewardsCalculatorUqSelector: FC = ({ - isUqScoreOver20, - onChange, -}) => { - const { t } = useTranslation('translation', { - keyPrefix: 'components.dedicated.rewardsCalculator.uqSelector', - }); - - const selectorOptions = [true, false]; // isUqScoreOver20 - - const dataTest = 'EarnRewardsCalculatorUqSelector'; - - return ( -
-
- {t('header')} -
-
-
- {/* eslint-disable-next-line @typescript-eslint/naming-convention */} - {selectorOptions.map((option, index) => ( -
onChange(option)} - > - - {option ? t('isUqScoreOver20_true') : t('isUqScoreOver20_false')} - - {isUqScoreOver20 === option ? ( - - ) : null} -
- ))} -
-
-
- ); -}; - -export default EarnRewardsCalculatorUqSelector; diff --git a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/index.tsx b/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/index.tsx deleted file mode 100644 index c15db5a662..0000000000 --- a/client/src/components/Earn/EarnRewardsCalculator/EarnRewardsCalculatorUqSelector/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnRewardsCalculatorUqSelector'; diff --git a/client/src/components/Earn/EarnRewardsCalculator/index.tsx b/client/src/components/Earn/EarnRewardsCalculator/index.tsx deleted file mode 100644 index e80032b867..0000000000 --- a/client/src/components/Earn/EarnRewardsCalculator/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnRewardsCalculator'; diff --git a/client/src/components/Earn/EarnTipTiles/EarnTipTiles.module.scss b/client/src/components/Earn/EarnTipTiles/EarnTipTiles.module.scss deleted file mode 100644 index ba0bfc9c8c..0000000000 --- a/client/src/components/Earn/EarnTipTiles/EarnTipTiles.module.scss +++ /dev/null @@ -1,27 +0,0 @@ -.lockGlmImage { - height: 10.5rem; - - @media #{$desktop-up} { - height: 11.7rem; - } -} - -.connectWalletImage { - @include tipTileConnectWalletImage(); -} - -.withdrawImage { - height: 7.8rem; - - @media #{$desktop-up} { - height: 10.4rem; - } -} - -.allocateYourRewardsImage { - height: 7.5rem; - - @media #{$desktop-up} { - height: 10.4rem; - } -} diff --git a/client/src/components/Earn/EarnTipTiles/EarnTipTiles.tsx b/client/src/components/Earn/EarnTipTiles/EarnTipTiles.tsx deleted file mode 100644 index b8404e488a..0000000000 --- a/client/src/components/Earn/EarnTipTiles/EarnTipTiles.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { differenceInCalendarDays } from 'date-fns'; -import React, { Fragment, ReactElement } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useAccount } from 'wagmi'; - -import TipTile from 'components/shared/TipTile'; -import useEpochAndAllocationTimestamps from 'hooks/helpers/useEpochAndAllocationTimestamps'; -import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import useDepositValue from 'hooks/queries/useDepositValue'; -import useIndividualReward from 'hooks/queries/useIndividualReward'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; -import useUserAllocations from 'hooks/queries/useUserAllocations'; -import useWithdrawals from 'hooks/queries/useWithdrawals'; -import useTipsStore from 'store/tips/store'; -import getIsPreLaunch from 'utils/getIsPreLaunch'; - -import styles from './EarnTipTiles.module.scss'; - -const EarnTipTiles = (): ReactElement => { - const { t } = useTranslation('translation', { - keyPrefix: 'views.earn.tips', - }); - const { isConnected } = useAccount(); - const { isDesktop } = useMediaQuery(); - const { data: withdrawals } = useWithdrawals(); - const { data: currentEpoch } = useCurrentEpoch(); - const isPreLaunch = getIsPreLaunch(currentEpoch); - const { data: depositsValue, isFetching: isFetchingDepositsValue } = useDepositValue(); - const { data: individualReward } = useIndividualReward(); - const { data: userAllocations } = useUserAllocations(); - const { timeCurrentAllocationEnd } = useEpochAndAllocationTimestamps(); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - - const { - wasWithdrawAlreadyClosed, - setWasWithdrawAlreadyClosed, - wasConnectWalletAlreadyClosed, - setWasConnectWalletAlreadyClosed, - wasLockGLMAlreadyClosed, - setWasLockGLMAlreadyClosed, - wasAllocateRewardsAlreadyClosed, - setWasAllocateRewardsAlreadyClosed, - } = useTipsStore(state => ({ - setWasAllocateRewardsAlreadyClosed: state.setWasAllocateRewardsAlreadyClosed, - setWasConnectWalletAlreadyClosed: state.setWasConnectWalletAlreadyClosed, - setWasLockGLMAlreadyClosed: state.setWasLockGLMAlreadyClosed, - setWasWithdrawAlreadyClosed: state.setWasWithdrawAlreadyClosed, - wasAllocateRewardsAlreadyClosed: state.data.wasAllocateRewardsAlreadyClosed, - wasConnectWalletAlreadyClosed: state.data.wasConnectWalletAlreadyClosed, - wasLockGLMAlreadyClosed: state.data.wasLockGLMAlreadyClosed, - wasWithdrawAlreadyClosed: state.data.wasWithdrawAlreadyClosed, - })); - - const isLockGlmTipVisible = - !isFetchingDepositsValue && - (!depositsValue || (!!depositsValue && depositsValue === 0n)) && - isConnected && - !wasLockGLMAlreadyClosed; - const isConnectWalletTipVisible = !isPreLaunch && !isConnected && !wasConnectWalletAlreadyClosed; - const isWithdrawTipVisible = - !!currentEpoch && - currentEpoch > 1 && - !!withdrawals && - withdrawals.sums.available !== 0n && - !wasWithdrawAlreadyClosed; - - const isAllocateRewardsTipVisible = - (!wasAllocateRewardsAlreadyClosed && - isDecisionWindowOpen && - !!(individualReward && individualReward !== 0n) && - !userAllocations?.hasUserAlreadyDoneAllocation && - differenceInCalendarDays(new Date(), timeCurrentAllocationEnd!) <= 2) ?? - false; - - return ( - - setWasLockGLMAlreadyClosed(true)} - text={t(isDesktop ? 'lockGlm.text.desktop' : 'lockGlm.text.mobile')} - title={t('lockGlm.title')} - /> - setWasConnectWalletAlreadyClosed(true)} - text={t(isDesktop ? 'connectWallet.text.desktop' : 'connectWallet.text.mobile')} - title={t(isDesktop ? 'connectWallet.title.desktop' : 'connectWallet.title.mobile')} - /> - setWasWithdrawAlreadyClosed(true)} - text={t(isDesktop ? 'withdrawEth.text.desktop' : 'withdrawEth.text.mobile')} - title={t('withdrawEth.title')} - /> - setWasAllocateRewardsAlreadyClosed(true)} - text={t(isDesktop ? 'allocateYourRewards.text.desktop' : 'allocateYourRewards.text.mobile')} - title={t('allocateYourRewards.title')} - /> - - ); -}; - -export default EarnTipTiles; diff --git a/client/src/components/Earn/EarnWithdrawEth/EarnWithdrawEth.module.scss b/client/src/components/Earn/EarnWithdrawEth/EarnWithdrawEth.module.scss deleted file mode 100644 index 067ce0ebaf..0000000000 --- a/client/src/components/Earn/EarnWithdrawEth/EarnWithdrawEth.module.scss +++ /dev/null @@ -1,20 +0,0 @@ -.root { - width: 100%; - height: 100%; -} - -.inputs { - display: flex; - justify-content: space-between; - align-items: flex-end; - width: 100%; -} - -.input { - @include flexBasisGutter(2, 1.6rem); -} - -.button { - width: 100%; - margin: 2.4rem 0; -} diff --git a/client/src/components/Earn/EarnWithdrawEth/index.tsx b/client/src/components/Earn/EarnWithdrawEth/index.tsx deleted file mode 100644 index 0939f1d9b8..0000000000 --- a/client/src/components/Earn/EarnWithdrawEth/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './EarnWithdrawEth'; diff --git a/client/src/components/Earn/EarnWithdrawEth/types.ts b/client/src/components/Earn/EarnWithdrawEth/types.ts deleted file mode 100644 index e332c4c258..0000000000 --- a/client/src/components/Earn/EarnWithdrawEth/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface EarnWithdrawEthProps { - onCloseModal: () => void; -} diff --git a/client/src/components/Earn/ModalEarnGlmLock/index.tsx b/client/src/components/Earn/ModalEarnGlmLock/index.tsx deleted file mode 100644 index 252540a7ba..0000000000 --- a/client/src/components/Earn/ModalEarnGlmLock/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalEarnGlmLock'; diff --git a/client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.module.scss b/client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.module.scss deleted file mode 100644 index f73dec9c84..0000000000 --- a/client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.module.scss +++ /dev/null @@ -1,21 +0,0 @@ -.root { - .header { - display: flex; - align-items: center; - overflow: visible; - } -} - -.tooltip { - margin-left: 0.8rem; - position: relative; - - .tooltipContainer { - top: 3.6rem; - left: -16.7rem; - } -} - -.tooltipWrapper { - position: relative; -} diff --git a/client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.tsx b/client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.tsx deleted file mode 100644 index aa83945606..0000000000 --- a/client/src/components/Earn/ModalEarnRewardsCalculator/ModalEarnRewardsCalculator.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import EarnRewardsCalculator from 'components/Earn/EarnRewardsCalculator'; -import Modal from 'components/ui/Modal'; -import Svg from 'components/ui/Svg'; -import Tooltip from 'components/ui/Tooltip'; -import { questionMark } from 'svg/misc'; - -import styles from './ModalEarnRewardsCalculator.module.scss'; -import ModalEarnRewardsCalculatorProps from './types'; - -const ModalEarnRewardsCalculator: FC = ({ modalProps }) => { - const { i18n, t } = useTranslation('translation', { - keyPrefix: 'components.dedicated.rewardsCalculator', - }); - - return ( - -
{i18n.t('common.estimateRewards')}
- - - - - } - headerClassName={styles.header} - isOpen={modalProps.isOpen} - onClosePanel={modalProps.onClosePanel} - > - -
- ); -}; - -export default ModalEarnRewardsCalculator; diff --git a/client/src/components/Earn/ModalEarnRewardsCalculator/index.tsx b/client/src/components/Earn/ModalEarnRewardsCalculator/index.tsx deleted file mode 100644 index 975cda7194..0000000000 --- a/client/src/components/Earn/ModalEarnRewardsCalculator/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalEarnRewardsCalculator'; diff --git a/client/src/components/Earn/ModalEarnWithdrawEth/ModalEarnWithdrawEth.tsx b/client/src/components/Earn/ModalEarnWithdrawEth/ModalEarnWithdrawEth.tsx deleted file mode 100644 index 1004f04d8d..0000000000 --- a/client/src/components/Earn/ModalEarnWithdrawEth/ModalEarnWithdrawEth.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import EarnWithdrawEth from 'components/Earn/EarnWithdrawEth'; -import Modal from 'components/ui/Modal'; - -import ModalEarnWithdrawingProps from './types'; - -const ModalEarnWithdrawEth: FC = ({ modalProps }) => { - const { t } = useTranslation('translation', { - keyPrefix: 'components.dedicated.modalWithdrawEth', - }); - - return ( - - - - ); -}; - -export default ModalEarnWithdrawEth; diff --git a/client/src/components/Earn/ModalEarnWithdrawEth/index.tsx b/client/src/components/Earn/ModalEarnWithdrawEth/index.tsx deleted file mode 100644 index 1744f844c5..0000000000 --- a/client/src/components/Earn/ModalEarnWithdrawEth/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalEarnWithdrawEth'; diff --git a/client/src/components/Home/HomeGrid/HomeGrid.module.scss b/client/src/components/Home/HomeGrid/HomeGrid.module.scss new file mode 100644 index 0000000000..24821649c5 --- /dev/null +++ b/client/src/components/Home/HomeGrid/HomeGrid.module.scss @@ -0,0 +1,3 @@ +.gridTile { + height: 32.8rem; +} diff --git a/client/src/components/Home/HomeGrid/HomeGrid.tsx b/client/src/components/Home/HomeGrid/HomeGrid.tsx new file mode 100644 index 0000000000..4cae2de059 --- /dev/null +++ b/client/src/components/Home/HomeGrid/HomeGrid.tsx @@ -0,0 +1,39 @@ +import React, { memo, ReactNode } from 'react'; + +import HomeGridCurrentGlmLock from 'components/Home/HomeGridCurrentGlmLock'; +import HomeGridDivider from 'components/Home/HomeGridDivider'; +import HomeGridDonations from 'components/Home/HomeGridDonations'; +import HomeGridEpochResults from 'components/Home/HomeGridEpochResults'; +import HomeGridPersonalAllocation from 'components/Home/HomeGridPersonalAllocation'; +import HomeGridRewardsEstimator from 'components/Home/HomeGridRewardsEstimator'; +import HomeGridTransactions from 'components/Home/HomeGridTransactions'; +import HomeGridUQScore from 'components/Home/HomeGridUQScore'; +import Grid from 'components/shared/Grid'; +import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; +import useIsPatronMode from 'hooks/queries/useIsPatronMode'; + +import styles from './HomeGrid.module.scss'; + +const HomeGrid = (): ReactNode => { + const { isLargeDesktop, isDesktop, isTablet } = useMediaQuery(); + const isProjectAdminMode = useIsProjectAdminMode(); + const { data: isPatronMode } = useIsPatronMode(); + + return ( + + {!isProjectAdminMode && } + {!isProjectAdminMode && !isPatronMode && } + {!isPatronMode && } + {!isProjectAdminMode && } + {!isProjectAdminMode && !isPatronMode && isLargeDesktop && } + + + {((!isLargeDesktop && (isDesktop || isTablet)) || + (isLargeDesktop && (isProjectAdminMode || isPatronMode))) && } + + + ); +}; + +export default memo(HomeGrid); diff --git a/client/src/components/Earn/EarnGlmLock/index.tsx b/client/src/components/Home/HomeGrid/index.tsx similarity index 54% rename from client/src/components/Earn/EarnGlmLock/index.tsx rename to client/src/components/Home/HomeGrid/index.tsx index 51932492ba..5bde98c328 100644 --- a/client/src/components/Earn/EarnGlmLock/index.tsx +++ b/client/src/components/Home/HomeGrid/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './EarnGlmLock'; +export { default } from './HomeGrid'; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.module.scss new file mode 100644 index 0000000000..37af860bfa --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.module.scss @@ -0,0 +1,15 @@ +.root { + padding: 0.8rem 2.4rem 2.4rem; + + .divider { + margin-top: 2rem; + width: 100%; + height: 0.1rem; + background-color: $color-octant-grey1; + } + + .lockGlmButton { + margin-top: 2.4rem; + width: 100%; + } +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.tsx new file mode 100644 index 0000000000..25f58f1579 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/HomeGridCurrentGlmLock.tsx @@ -0,0 +1,114 @@ +import _first from 'lodash/first'; +import React, { FC, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useAccount } from 'wagmi'; + +import ModalLockGlm from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm'; +import GridTile from 'components/shared/Grid/GridTile'; +import Sections from 'components/ui/BoxRounded/Sections/Sections'; +import Button from 'components/ui/Button'; +import DoubleValue from 'components/ui/DoubleValue'; +import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useDepositValue from 'hooks/queries/useDepositValue'; +import useEstimatedEffectiveDeposit from 'hooks/queries/useEstimatedEffectiveDeposit'; +import useTransactionLocalStore from 'store/transactionLocal/store'; +import getIsPreLaunch from 'utils/getIsPreLaunch'; + +import styles from './HomeGridCurrentGlmLock.module.scss'; +import HomeGridCurrentGlmLockProps from './types'; + +const HomeGridCurrentGlmLock: FC = ({ className }) => { + const { isConnected } = useAccount(); + const { i18n, t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridCurrentGlmLock', + }); + const { data: currentEpoch } = useCurrentEpoch(); + const { isMobile } = useMediaQuery(); + const { isAppWaitingForTransactionToBeIndexed, transactionsPending } = useTransactionLocalStore( + state => ({ + isAppWaitingForTransactionToBeIndexed: state.data.isAppWaitingForTransactionToBeIndexed, + transactionsPending: state.data.transactionsPending, + }), + ); + + const [isModalLockGlmOpen, setIsModalLockGlmOpen] = useState(false); + const { data: estimatedEffectiveDeposit, isFetching: isFetchingEstimatedEffectiveDeposit } = + useEstimatedEffectiveDeposit(); + const { data: depositsValue, isFetching: isFetchingDepositValue } = useDepositValue(); + + const isPreLaunch = getIsPreLaunch(currentEpoch); + const isProjectAdminMode = useIsProjectAdminMode(); + + return ( + <> + +
+ +
+ + +
+ + setIsModalLockGlmOpen(false), + }} + /> + + ); +}; + +export default HomeGridCurrentGlmLock; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLock.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.module.scss similarity index 100% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLock.module.scss rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.module.scss diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLock.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.tsx similarity index 90% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLock.tsx rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.tsx index 3a966b3735..283cbce4ad 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLock.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/LockGlm.tsx @@ -4,10 +4,10 @@ import { useTranslation } from 'react-i18next'; import { useAccount, useWalletClient, usePublicClient, useWaitForTransactionReceipt } from 'wagmi'; import { apiGetSafeTransactions } from 'api/calls/safeTransactions'; -import EarnGlmLockBudget from 'components/Earn/EarnGlmLock/EarnGlmLockBudget'; -import EarnGlmLockNotification from 'components/Earn/EarnGlmLock/EarnGlmLockNotification'; -import EarnGlmLockStepper from 'components/Earn/EarnGlmLock/EarnGlmLockStepper'; -import EarnGlmLockTabs from 'components/Earn/EarnGlmLock/EarnGlmLockTabs'; +import LockGlmBudget from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget'; +import LockGlmNotification from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification'; +import LockGlmStepper from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper'; +import LockGlmTabs from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs'; import networkConfig from 'constants/networkConfig'; import env from 'env'; import { writeContractERC20 } from 'hooks/contracts/writeContracts'; @@ -26,11 +26,11 @@ import toastService from 'services/toastService'; import useTransactionLocalStore from 'store/transactionLocal/store'; import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; -import styles from './EarnGlmLock.module.scss'; -import EarnGlmLockProps, { Step, OnReset } from './types'; +import styles from './LockGlm.module.scss'; +import LockGlmProps, { Step, OnReset } from './types'; import { formInitialValues, validationSchema } from './utils'; -const EarnGlmLock: FC = ({ currentMode, onCurrentModeChange, onCloseModal }) => { +const LockGlm: FC = ({ currentMode, onCurrentModeChange, onCloseModal }) => { const { i18n } = useTranslation(); const { address } = useAccount(); const publicClient = usePublicClient({ chainId: networkConfig.id }); @@ -83,7 +83,7 @@ const EarnGlmLock: FC = ({ currentMode, onCurrentModeChange, o /** * When input is focused isCryptoOrFiatInputFocused is true. * Clicking "use max" blurs inputs, setting isCryptoOrFiatInputFocused to false. - * EarnGlmLockTabs onMax sets the focus back on inputs, triggering isCryptoOrFiatInputFocused to true. + * LockGlmTabs onMax sets the focus back on inputs, triggering isCryptoOrFiatInputFocused to true. * * Between second and third update flickering can occur, when focus is already set to input, * but state didn't update yet. @@ -211,11 +211,11 @@ const EarnGlmLock: FC = ({ currentMode, onCurrentModeChange, o {props => (
{isDesktop && ( - + )} {(step === 2 && currentMode === 'lock' && isApprovalKnown && !isLockingApproved) || step === 3 ? ( - = ({ currentMode, onCurrentModeChange, o type={step === 3 ? 'success' : 'info'} /> ) : ( - + )} - = ({ currentMode, onCurrentModeChange, o ); }; -export default EarnGlmLock; +export default LockGlm; diff --git a/client/src/components/shared/TipTile/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/index.tsx similarity index 57% rename from client/src/components/shared/TipTile/index.tsx rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/index.tsx index 20b8333882..90241ca3cc 100644 --- a/client/src/components/shared/TipTile/index.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './TipTile'; +export { default } from './LockGlm'; diff --git a/client/src/components/Earn/EarnGlmLock/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types.ts similarity index 91% rename from client/src/components/Earn/EarnGlmLock/types.ts rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types.ts index 29497d8612..bd8d5fe2d2 100644 --- a/client/src/components/Earn/EarnGlmLock/types.ts +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types.ts @@ -17,7 +17,7 @@ export type OnReset = ({ setFieldValue?: FormikHelpers['setFieldValue']; }) => void; -export default interface EarnGlmLockProps { +export default interface LockGlmProps { currentMode: CurrentMode; onCloseModal: () => void; onCurrentModeChange: (currentMode: CurrentMode) => void; diff --git a/client/src/components/Earn/EarnGlmLock/utils.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/utils.ts similarity index 100% rename from client/src/components/Earn/EarnGlmLock/utils.ts rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/utils.ts diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.module.scss similarity index 100% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockBudget/EarnGlmLockBudget.module.scss rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.module.scss diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.tsx new file mode 100644 index 0000000000..b5d65bbb09 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/LockGlmBudget.tsx @@ -0,0 +1,26 @@ +import { useFormikContext } from 'formik'; +import React, { FC } from 'react'; + +import { FormFields } from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; +import LockGlmBudgetBox from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox'; + +import styles from './LockGlmBudget.module.scss'; +import LockGlmBudgetProps from './types'; + +const LockGlmBudget: FC = ({ isVisible }) => { + const { errors } = useFormikContext(); + + if (!isVisible) { + return null; + } + + return ( + + ); +}; + +export default LockGlmBudget; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/index.tsx new file mode 100644 index 0000000000..52502f923e --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LockGlmBudget'; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/types.ts new file mode 100644 index 0000000000..34f7b64b31 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudget/types.ts @@ -0,0 +1,3 @@ +export default interface LockGlmBudgetProps { + isVisible: boolean; +} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.module.scss similarity index 63% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.module.scss rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.module.scss index 8762572265..aafbfc59d3 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.module.scss +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.module.scss @@ -1,29 +1,3 @@ -.transactionHash { - display: flex; - justify-content: center; - margin: 1rem 0 0 0; -} - -.availableFunds { - height: 3.2rem; - display: flex; - align-items: flex-end; - border-top: 0.1rem solid $color-white; - margin-top: 1.6rem; - - .value { - color: $color-black; - - &.isError { - color: $color-octant-orange; - } - } -} - -.button { - padding: 0; -} - .budgetRow { position: relative; height: 5.6rem; @@ -50,7 +24,6 @@ width: 10rem; } - .budgetLabel { color: $color-octant-grey5; } diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.tsx similarity index 81% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.tsx rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.tsx index 37733eaf77..1713e0581d 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/EarnGlmLockBudgetBox.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/LockGlmBudgetBox.tsx @@ -7,10 +7,10 @@ import useAvailableFundsGlm from 'hooks/helpers/useAvailableFundsGlm'; import useDepositValue from 'hooks/queries/useDepositValue'; import getFormattedGlmValue from 'utils/getFormattedGlmValue'; -import styles from './EarnGlmLockBudgetBox.module.scss'; -import EarnGlmLockBudgetBoxProps from './types'; +import styles from './LockGlmBudgetBox.module.scss'; +import LockGlmBudgetBoxProps from './types'; -const EarnGlmLockBudgetBox: FC = ({ +const LockGlmBudgetBox: FC = ({ className, isWalletBalanceError, isCurrentlyLockedError, @@ -19,7 +19,7 @@ const EarnGlmLockBudgetBox: FC = ({ const { data: availableFundsGlm, isFetched: isFetchedAvailableFundsGlm } = useAvailableFundsGlm(); const { t } = useTranslation('translation', { - keyPrefix: 'components.dedicated.budgetBox', + keyPrefix: 'components.home.homeGridCurrentGlmLock.modalLockGlm.lockGlmBudgetBox', }); const depositsValueString = useMemo( @@ -35,7 +35,7 @@ const EarnGlmLockBudgetBox: FC = ({ = ({ ) : (
{depositsValueString}
@@ -60,7 +60,7 @@ const EarnGlmLockBudgetBox: FC = ({ ) : (
{availableFundsGlmString}
@@ -70,4 +70,4 @@ const EarnGlmLockBudgetBox: FC = ({ ); }; -export default EarnGlmLockBudgetBox; +export default LockGlmBudgetBox; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/index.tsx new file mode 100644 index 0000000000..479b4382ca --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LockGlmBudgetBox'; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/types.ts similarity index 63% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/types.ts rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/types.ts index 74d224f84c..182be764b7 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/types.ts +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmBudgetBox/types.ts @@ -1,4 +1,4 @@ -export default interface EarnGlmLockBudgetBoxProps { +export default interface LockGlmBudgetBoxProps { className?: string; isCurrentlyLockedError?: boolean; isWalletBalanceError?: boolean; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.module.scss similarity index 65% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.module.scss rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.module.scss index 8c77ef5984..5199cae37c 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.module.scss +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.module.scss @@ -20,16 +20,5 @@ .text { color: $color-octant-grey5; text-align: left; - - .link { - font-size: $font-size-12; - min-height: 2rem; - font-weight: $font-weight-semibold; - - &:hover { - cursor: pointer; - transform: none; - } - } } } diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx similarity index 63% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.tsx rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx index e32df3f928..4e857801d6 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockNotification/EarnGlmLockNotification.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/LockGlmNotification.tsx @@ -2,30 +2,15 @@ import React, { FC, useMemo } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import BoxRounded from 'components/ui/BoxRounded'; -import Button from 'components/ui/Button'; import Svg from 'components/ui/Svg'; -import networkConfig from 'constants/networkConfig'; -import { arrowTopRight, checkMark, notificationIconWarning } from 'svg/misc'; +import { checkMark, notificationIconWarning } from 'svg/misc'; -import styles from './EarnGlmLockNotification.module.scss'; -import EarnGlmLockNotificationProps from './types'; +import styles from './LockGlmNotification.module.scss'; +import LockGlmNotificationProps from './types'; -const ButtonLinkWithIcon: FC<{ children?: React.ReactNode; transactionHash: string }> = ({ - children, - transactionHash, -}) => { - return ( - - ); -}; +import LockGlmNotificationLinkButton from '../LockGlmNotificationLinkButton'; -const EarnGlmLockNotification: FC = ({ +const LockGlmNotification: FC = ({ className, isLockingApproved, type, @@ -68,11 +53,7 @@ const EarnGlmLockNotification: FC = ({ isVertical >
- +
{label &&
{label}
} {text && ( @@ -80,7 +61,7 @@ const EarnGlmLockNotification: FC = ({ ] + ? [] : undefined } i18nKey={text} @@ -93,4 +74,4 @@ const EarnGlmLockNotification: FC = ({ ); }; -export default EarnGlmLockNotification; +export default LockGlmNotification; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/index.tsx new file mode 100644 index 0000000000..c72b658630 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LockGlmNotification'; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/types.ts new file mode 100644 index 0000000000..bd4f878d22 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotification/types.ts @@ -0,0 +1,9 @@ +import { CurrentMode } from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; + +export default interface LockGlmNotificationProps { + className?: string; + currentMode: CurrentMode; + isLockingApproved: boolean; + transactionHash?: string; + type: 'success' | 'info'; +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.module.scss new file mode 100644 index 0000000000..c430af7485 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.module.scss @@ -0,0 +1,10 @@ +.root { + font-size: $font-size-12; + min-height: 2rem; + font-weight: $font-weight-semibold; + + &:hover { + cursor: pointer; + transform: none; + } +} diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.tsx new file mode 100644 index 0000000000..752f2b2ab3 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/LockGlmNotificationLinkButton.tsx @@ -0,0 +1,26 @@ +import React, { FC, memo } from 'react'; + +import Button from 'components/ui/Button'; +import Svg from 'components/ui/Svg'; +import networkConfig from 'constants/networkConfig'; +import { arrowTopRight } from 'svg/misc'; + +import styles from './LockGlmNotificationLinkButton.module.scss'; +import LockGlmNotificationLinkButtonProps from './types'; + +const LockGlmNotificationLinkButton: FC = ({ + children, + transactionHash, +}) => { + return ( + + ); +}; + +export default memo(LockGlmNotificationLinkButton); diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/index.tsx new file mode 100644 index 0000000000..8941cd49be --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LockGlmNotificationLinkButton'; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/types.ts new file mode 100644 index 0000000000..c96ea3baec --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmNotificationLinkButton/types.ts @@ -0,0 +1,6 @@ +import { ReactNode } from 'react'; + +export default interface LockGlmNotificationLinkButtonProps { + children?: ReactNode; + transactionHash: string; +} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/EarnGlmLockStepper.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/LockGlmStepper.tsx similarity index 76% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/EarnGlmLockStepper.tsx rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/LockGlmStepper.tsx index e2c30eb9dd..8efd0b967b 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/EarnGlmLockStepper.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/LockGlmStepper.tsx @@ -2,13 +2,13 @@ import { useFormikContext } from 'formik'; import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; -import { FormFields } from 'components/Earn/EarnGlmLock/types'; +import { FormFields } from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; import BoxRounded from 'components/ui/BoxRounded'; import ProgressStepper from 'components/ui/ProgressStepper'; -import EarnGlmLockStepperProps from './types'; +import LockGlmStepperProps from './types'; -const EarnGlmLockStepper: FC = ({ currentMode, step, className }) => { +const LockGlmStepper: FC = ({ currentMode, step, className }) => { const { t, i18n } = useTranslation('translation', { keyPrefix: 'components.dedicated.glmLock', }); @@ -29,4 +29,4 @@ const EarnGlmLockStepper: FC = ({ currentMode, step, cl ); }; -export default EarnGlmLockStepper; +export default LockGlmStepper; diff --git a/client/src/components/Earn/EarnBoxGlmLock/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/index.tsx similarity index 53% rename from client/src/components/Earn/EarnBoxGlmLock/index.tsx rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/index.tsx index ced474dc20..c34128c49f 100644 --- a/client/src/components/Earn/EarnBoxGlmLock/index.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './EarnBoxGlmLock'; +export { default } from './LockGlmStepper'; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/types.ts new file mode 100644 index 0000000000..6c5ded6a40 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmStepper/types.ts @@ -0,0 +1,7 @@ +import { CurrentMode } from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; + +export default interface LockGlmStepperProps { + className?: string; + currentMode: CurrentMode; + step: 1 | 2 | 3; +} diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.module.scss similarity index 100% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.module.scss rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.module.scss diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.tsx similarity index 92% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.tsx rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.tsx index 02b418d638..dc6615d128 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/EarnGlmLockTabs.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/LockGlmTabs.tsx @@ -3,8 +3,8 @@ import { useFormikContext } from 'formik'; import React, { FC, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import EarnGlmLockTabsInputs from 'components/Earn/EarnGlmLock/EarnGlmLockTabsInputs'; -import { FormFields } from 'components/Earn/EarnGlmLock/types'; +import { FormFields } from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; +import LockGlmTabsInputs from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabsInputs'; import BoxRounded from 'components/ui/BoxRounded'; import Button from 'components/ui/Button'; import ButtonProps from 'components/ui/Button/types'; @@ -14,10 +14,10 @@ import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; import getFormattedGlmValue from 'utils/getFormattedGlmValue'; import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; -import styles from './EarnGlmLockTabs.module.scss'; -import EarnGlmLockTabsProps from './types'; +import styles from './LockGlmTabs.module.scss'; +import LockGlmTabsProps from './types'; -const EarnGlmLockTabs: FC = ({ +const LockGlmTabs: FC = ({ buttonUseMaxRef, className, currentMode, @@ -88,7 +88,7 @@ const EarnGlmLockTabs: FC = ({ return ( = ({ > {t('glmLockTabs.useMax')} - = ({ ); }; -export default EarnGlmLockTabs; +export default LockGlmTabs; diff --git a/client/src/components/Earn/EarnTipTiles/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/index.tsx similarity index 54% rename from client/src/components/Earn/EarnTipTiles/index.tsx rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/index.tsx index 6211f7158a..62e75a195e 100644 --- a/client/src/components/Earn/EarnTipTiles/index.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './EarnTipTiles'; +export { default } from './LockGlmTabs'; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/types.ts similarity index 72% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/types.ts rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/types.ts index 988071327b..3f1f959ff4 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/types.ts +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabs/types.ts @@ -1,9 +1,13 @@ import { FormikHelpers } from 'formik'; import { RefObject } from 'react'; -import { FormFields, CurrentMode, OnReset } from 'components/Earn/EarnGlmLock/types'; +import { + FormFields, + CurrentMode, + OnReset, +} from 'components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlm/types'; -export default interface EarnGlmLockTabsProps { +export default interface LockGlmTabsProps { buttonUseMaxRef: RefObject; className?: string; currentMode: CurrentMode; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabsInputs/EarnGlmLockTabsInputs.module.scss b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabsInputs/LockGlmTabsInputs.module.scss similarity index 100% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockTabsInputs/EarnGlmLockTabsInputs.module.scss rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabsInputs/LockGlmTabsInputs.module.scss diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabsInputs/EarnGlmLockTabsInputs.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabsInputs/LockGlmTabsInputs.tsx similarity index 95% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockTabsInputs/EarnGlmLockTabsInputs.tsx rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabsInputs/LockGlmTabsInputs.tsx index b0824eb32f..0adace6884 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabsInputs/EarnGlmLockTabsInputs.tsx +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/LockGlmTabsInputs/LockGlmTabsInputs.tsx @@ -10,10 +10,10 @@ import { floatNumberWithUpTo2DecimalPlaces, } from 'utils/regExp'; -import styles from './EarnGlmLockTabsInputs.module.scss'; -import EarnGlmLockTabsInputsProps from './types'; +import styles from './LockGlmTabsInputs.module.scss'; +import LockGlmTabsInputsProps from './types'; -const EarnGlmLockTabsInputs = forwardRef( +const LockGlmTabsInputs = forwardRef( ( { areInputsDisabled, @@ -146,4 +146,4 @@ const EarnGlmLockTabsInputs = forwardRef = ({ modalProps }) => { +const ModalLockGlm: FC = ({ modalProps }) => { const { t, i18n } = useTranslation('translation', { - keyPrefix: 'components.dedicated.modalGlmLock', + keyPrefix: 'components.home.homeGridCurrentGlmLock.modalLockGlm', }); const [currentMode, setCurrentMode] = useState('lock'); @@ -17,7 +17,7 @@ const ModalEarnGlmLock: FC = ({ modalProps }) => { return ( - = ({ modalProps }) => { ); }; -export default ModalEarnGlmLock; +export default ModalLockGlm; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/index.tsx new file mode 100644 index 0000000000..fafd32c588 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ModalLockGlm'; diff --git a/client/src/components/Earn/ModalEarnGlmLock/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/types.ts similarity index 69% rename from client/src/components/Earn/ModalEarnGlmLock/types.ts rename to client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/types.ts index a282b96bad..b30ac68564 100644 --- a/client/src/components/Earn/ModalEarnGlmLock/types.ts +++ b/client/src/components/Home/HomeGridCurrentGlmLock/ModalLockGlm/types.ts @@ -1,5 +1,5 @@ import ModalProps from 'components/ui/Modal/types'; -export default interface ModalEarnGlmLockProps { +export default interface ModalLockGlmProps { modalProps: Omit; } diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/index.tsx b/client/src/components/Home/HomeGridCurrentGlmLock/index.tsx new file mode 100644 index 0000000000..6a972ebcc8 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeGridCurrentGlmLock'; diff --git a/client/src/components/Home/HomeGridCurrentGlmLock/types.ts b/client/src/components/Home/HomeGridCurrentGlmLock/types.ts new file mode 100644 index 0000000000..da4457ce45 --- /dev/null +++ b/client/src/components/Home/HomeGridCurrentGlmLock/types.ts @@ -0,0 +1,3 @@ +export default interface HomeGridCurrentGlmLockProps { + className?: string; +} diff --git a/client/src/components/Home/HomeGridDivider/HomeGridDivider.module.scss b/client/src/components/Home/HomeGridDivider/HomeGridDivider.module.scss new file mode 100644 index 0000000000..568cbd4dbf --- /dev/null +++ b/client/src/components/Home/HomeGridDivider/HomeGridDivider.module.scss @@ -0,0 +1,19 @@ +.root { + height: 0.1rem; + background: $color-octant-grey1; + width: 100%; + margin: 4rem 0; + grid-column: span 1; + + @media #{$tablet-up} { + grid-column: span 2; + } + + @media #{$desktop-up} { + grid-column: span 3; + } + + @media #{$large-desktop-up} { + grid-column: span 4; + } +} diff --git a/client/src/components/Home/HomeGridDivider/HomeGridDivider.tsx b/client/src/components/Home/HomeGridDivider/HomeGridDivider.tsx new file mode 100644 index 0000000000..a447c2187f --- /dev/null +++ b/client/src/components/Home/HomeGridDivider/HomeGridDivider.tsx @@ -0,0 +1,9 @@ +import React, { memo, ReactNode } from 'react'; + +import styles from './HomeGridDivider.module.scss'; + +const HomeGridDivider = (): ReactNode => { + return
; +}; + +export default memo(HomeGridDivider); diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/index.tsx b/client/src/components/Home/HomeGridDivider/index.tsx similarity index 52% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/index.tsx rename to client/src/components/Home/HomeGridDivider/index.tsx index 3df89b0dc9..717c940407 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockTabs/index.tsx +++ b/client/src/components/Home/HomeGridDivider/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './EarnGlmLockTabs'; +export { default } from './HomeGridDivider'; diff --git a/client/src/components/Home/HomeGridDonations/DonationsList/DonationsList.module.scss b/client/src/components/Home/HomeGridDonations/DonationsList/DonationsList.module.scss new file mode 100644 index 0000000000..bdca81f093 --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/DonationsList/DonationsList.module.scss @@ -0,0 +1,9 @@ +.root { + position: relative; + + .donationsList { + max-height: 100%; + overflow: auto; + margin-right: 0.8rem; + } +} diff --git a/client/src/components/Home/HomeGridDonations/DonationsList/DonationsList.tsx b/client/src/components/Home/HomeGridDonations/DonationsList/DonationsList.tsx new file mode 100644 index 0000000000..f4a7917890 --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/DonationsList/DonationsList.tsx @@ -0,0 +1,50 @@ +import React, { FC } from 'react'; + +import DonationsListItem from 'components/Home/HomeGridDonations/DonationsListItem'; +import DonationsListSkeletonItem from 'components/Home/HomeGridDonations/DonationsListSkeletonItem'; +import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; + +import styles from './DonationsList.module.scss'; +import DonationsListProps from './types'; + +const DonationsList: FC = ({ + donations, + isLoading, + numberOfSkeletons, + dataTest = 'DonationsList', +}) => { + const getValuesToDisplay = useGetValuesToDisplay(); + + return ( +
+
+ {isLoading + ? Array.from(Array(numberOfSkeletons)).map((_, idx) => ( + // eslint-disable-next-line react/no-array-index-key + + )) + : donations.map(donation => ( + + ))} +
+
+ ); +}; + +export default DonationsList; diff --git a/client/src/components/Home/HomeGridDonations/DonationsList/index.tsx b/client/src/components/Home/HomeGridDonations/DonationsList/index.tsx new file mode 100644 index 0000000000..66ce92c89e --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/DonationsList/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './DonationsList'; diff --git a/client/src/components/Home/HomeGridDonations/DonationsList/types.ts b/client/src/components/Home/HomeGridDonations/DonationsList/types.ts new file mode 100644 index 0000000000..1a0f5fc634 --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/DonationsList/types.ts @@ -0,0 +1,8 @@ +import { ResponseItem } from 'hooks/helpers/useUserAllocationsAllEpochs'; + +export default interface DonationsListProps { + dataTest?: string; + donations: ResponseItem['elements']; + isLoading: boolean; + numberOfSkeletons: number; +} diff --git a/client/src/components/Home/HomeGridDonations/DonationsListItem/DonationsListItem.module.scss b/client/src/components/Home/HomeGridDonations/DonationsListItem/DonationsListItem.module.scss new file mode 100644 index 0000000000..dbc6f9f717 --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/DonationsListItem/DonationsListItem.module.scss @@ -0,0 +1,37 @@ +.root { + display: flex; + align-items: center; + justify-content: space-between; + height: 5.6rem; + + &:not(:last-child) { + border-bottom: 0.1rem solid $color-octant-grey3; + } + + .image { + height: 2.4rem; + width: 2.4rem; + border-radius: 100%; + margin-right: 1.6rem; + } + + .name, + .value { + font-size: $font-size-12; + font-weight: $font-weight-bold; + color: $color-octant-dark; + } + + .name { + width: 100%; + text-align: left; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .value { + margin-left: 1rem; + flex-shrink: 0; + } +} diff --git a/client/src/components/Home/HomeGridDonations/DonationsListItem/DonationsListItem.tsx b/client/src/components/Home/HomeGridDonations/DonationsListItem/DonationsListItem.tsx new file mode 100644 index 0000000000..0a48c005d7 --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/DonationsListItem/DonationsListItem.tsx @@ -0,0 +1,39 @@ +import React, { FC, memo } from 'react'; + +import Img from 'components/ui/Img/Img'; +import env from 'env'; +import useProjectsIpfs from 'hooks/queries/useProjectsIpfs'; + +import styles from './DonationsListItem.module.scss'; +import DonationsListItemProps from './types'; + +const DonationsListItem: FC = ({ + address, + epoch, + value, + dataTest = 'DonationsListItem', +}) => { + const { ipfsGateways } = env; + const { data: projectsIpfs } = useProjectsIpfs([address], epoch); + + const image = projectsIpfs.at(0)?.profileImageSmall; + const name = projectsIpfs.at(0)?.name; + + return ( +
+ project logo `${element}${image}`)} + /> +
+ {name} +
+
+ {value} +
+
+ ); +}; + +export default memo(DonationsListItem); diff --git a/client/src/components/Home/HomeGridDonations/DonationsListItem/index.tsx b/client/src/components/Home/HomeGridDonations/DonationsListItem/index.tsx new file mode 100644 index 0000000000..7ec3ac8698 --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/DonationsListItem/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './DonationsListItem'; diff --git a/client/src/components/Home/HomeGridDonations/DonationsListItem/types.ts b/client/src/components/Home/HomeGridDonations/DonationsListItem/types.ts new file mode 100644 index 0000000000..e346209a81 --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/DonationsListItem/types.ts @@ -0,0 +1,6 @@ +export default interface DonationsListItemProps { + address: string; + dataTest?: string; + epoch?: number; + value: string; +} diff --git a/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/DonationsListSkeletonItem.module.scss b/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/DonationsListSkeletonItem.module.scss new file mode 100644 index 0000000000..be193215bf --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/DonationsListSkeletonItem.module.scss @@ -0,0 +1,31 @@ +.root { + display: flex; + align-items: center; + justify-content: space-between; + height: 5.6rem; + + &:not(:last-child) { + border-bottom: 0.1rem solid $color-octant-grey3; + } + + .image { + @include skeleton(); + height: 2.4rem; + width: 2.4rem; + border-radius: 100%; + margin-right: 1.6rem; + } + + .name { + @include skeleton(); + height: 1.5rem; + width: 18rem; + } + + .value { + @include skeleton(); + height: 1.5rem; + width: 4rem; + margin-left: auto; + } +} diff --git a/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/DonationsListSkeletonItem.tsx b/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/DonationsListSkeletonItem.tsx new file mode 100644 index 0000000000..bb623e4ffd --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/DonationsListSkeletonItem.tsx @@ -0,0 +1,15 @@ +import React, { ReactElement, memo } from 'react'; + +import styles from './DonationsListSkeletonItem.module.scss'; + +const DonationsListSkeletonItem = (): ReactElement => { + return ( +
+
+
+
+
+ ); +}; + +export default memo(DonationsListSkeletonItem); diff --git a/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/index.tsx b/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/index.tsx new file mode 100644 index 0000000000..e6039ffa45 --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/DonationsListSkeletonItem/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './DonationsListSkeletonItem'; diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.module.scss b/client/src/components/Home/HomeGridDonations/HomeGridDonations.module.scss new file mode 100644 index 0000000000..3ad82331e5 --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.module.scss @@ -0,0 +1,51 @@ +.root { + padding: 0rem 2.4rem 2.4rem; + overflow: auto; +} + +.titleWrapper { + display: flex; + + .numberOfAllocations { + display: flex; + align-items: center; + justify-content: center; + color: $color-white; + background-color: $color-octant-dark; + font-size: $font-size-08; + font-weight: $font-weight-superbold; + border-radius: 50%; + width: 1.6rem; + height: 1.6rem; + margin-left: 1rem; + } +} + +.noDonationsYet { + height: 100%; + width: 100%; + + .noDonationsYetImage { + width: 12.9rem; + margin-top: 0.6rem; + } + + .noDonationsYetLabel { + line-height: 2rem; + margin-top: 4.6rem; + font-size: $font-size-14; + font-weight: $font-weight-bold; + color: $color-octant-grey5; + } +} + +.editButton { + margin-left: auto; + width: 6rem; + padding: 0; + min-height: 3.2rem; +} + +.projectsLink { + font-size: $font-size-14; +} diff --git a/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx new file mode 100644 index 0000000000..4eefa3406f --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/HomeGridDonations.tsx @@ -0,0 +1,112 @@ +import React, { FC } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { useAccount } from 'wagmi'; + +import DonationsList from 'components/Home/HomeGridDonations/DonationsList'; +import GridTile from 'components/shared/Grid/GridTile'; +import Button from 'components/ui/Button'; +import Img from 'components/ui/Img'; +import useUserAllocationsAllEpochs from 'hooks/helpers/useUserAllocationsAllEpochs'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import useUserAllocations from 'hooks/queries/useUserAllocations'; +import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; +import useAllocationsStore from 'store/allocations/store'; +import useLayoutStore from 'store/layout/store'; + +import styles from './HomeGridDonations.module.scss'; +import HomeGridDonationsProps from './types'; +import { getReducedUserAllocationsAllEpochs } from './utils'; + +const HomeGridDonations: FC = ({ className }) => { + const { i18n, t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridDonations', + }); + const { isConnected } = useAccount(); + const { data: userAllocationsAllEpochs, isFetching: isFetchingUserAllocationsAllEpochs } = + useUserAllocationsAllEpochs(); + const reducedUserAllocationsAllEpochs = + getReducedUserAllocationsAllEpochs(userAllocationsAllEpochs); + const { data: userAllocations, isFetching: isFetchingUserAllocations } = useUserAllocations(); + const { data: currentEpoch } = useCurrentEpoch(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const { isAllocationDrawerOpen, setIsAllocationDrawerOpen } = useLayoutStore(state => ({ + isAllocationDrawerOpen: state.data.isAllocationDrawerOpen, + setIsAllocationDrawerOpen: state.setIsAllocationDrawerOpen, + })); + + const { setCurrentView } = useAllocationsStore(state => ({ + setCurrentView: state.setCurrentView, + })); + + const areAllocationsEmpty = + !isConnected || + (isDecisionWindowOpen + ? !isFetchingUserAllocations && userAllocations?.elements.length === 0 + : !isFetchingUserAllocationsAllEpochs && reducedUserAllocationsAllEpochs?.length === 0); + + return ( + + {!isDecisionWindowOpen && !areAllocationsEmpty + ? t('donationHistory') + : i18n.t('common.donations')} + {isDecisionWindowOpen && userAllocations?.elements !== undefined && ( +
{userAllocations?.elements?.length}
+ )} +
+ } + titleSuffix={ + isDecisionWindowOpen ? ( + + ) : null + } + > +
+ {areAllocationsEmpty ? ( +
+ +
+ , + ]} + i18nKey={`components.home.homeGridDonations.${isDecisionWindowOpen ? 'noDonationsYetAWOpen' : 'noDonationsYet'}`} + /> +
+
+ ) : ( + ({ ...a, epoch: currentEpoch! - 1 })) + : reducedUserAllocationsAllEpochs + } + isLoading={ + isDecisionWindowOpen ? isFetchingUserAllocations : isFetchingUserAllocationsAllEpochs + } + numberOfSkeletons={4} + /> + )} +
+ + ); +}; + +export default HomeGridDonations; diff --git a/client/src/components/Home/HomeGridDonations/index.tsx b/client/src/components/Home/HomeGridDonations/index.tsx new file mode 100644 index 0000000000..0a66ad2aec --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeGridDonations'; diff --git a/client/src/components/Home/HomeGridDonations/types.ts b/client/src/components/Home/HomeGridDonations/types.ts new file mode 100644 index 0000000000..bb192c9fba --- /dev/null +++ b/client/src/components/Home/HomeGridDonations/types.ts @@ -0,0 +1,3 @@ +export default interface HomeGridDonationsProps { + className?: string; +} diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils.ts b/client/src/components/Home/HomeGridDonations/utils.ts similarity index 100% rename from client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils.ts rename to client/src/components/Home/HomeGridDonations/utils.ts diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.module.scss b/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.module.scss new file mode 100644 index 0000000000..81c019bed0 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.module.scss @@ -0,0 +1,33 @@ +.root { + display: flex; + flex-direction: column; + flex: 1; + + .image { + height: 11.1rem; + margin: 0 auto 0; + opacity: 0.5; + } + + .graphContainer { + margin-bottom: 0.8rem; + flex: 1; + display: flex; + padding: 4.8rem 1.6rem 0; + margin-top: -3.2rem; + overflow-y: hidden; + overflow-x: auto; + + &.isLoading { + margin: 0; + padding-top: 2.4rem; + } + } + + .details { + width: 100%; + height: 3.2rem; + border-radius: $border-radius-08; + background-color: $color-octant-grey6; + } +} diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx new file mode 100644 index 0000000000..c02851514e --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResults/EpochResults.tsx @@ -0,0 +1,90 @@ +import cx from 'classnames'; +import { maxBy } from 'lodash'; +import React, { FC, useEffect, useState } from 'react'; + +import EpochResultsBar from 'components/Home/HomeGridEpochResults/EpochResultsBar'; +import EpochResultsDetails from 'components/Home/HomeGridEpochResults/EpochResultsDetails'; +import Img from 'components/ui/Img'; +import { EPOCH_RESULTS_BAR_ID } from 'constants/domElementsIds'; +import env from 'env'; +import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards'; + +import styles from './EpochResults.module.scss'; +import EpochResultsProps from './types'; + +const EpochResults: FC = ({ projects, isLoading }) => { + const [highlightedBarAddress, setHighlightedBarAddress] = useState(null); + const { ipfsGateways } = env; + + const details = projects.find(d => d.address === highlightedBarAddress); + + const getMaxValue = (): bigint => { + const { matchedRewards, donations } = maxBy(projects, d => { + if (d.donations > d.matchedRewards) { + return d.donations; + } + return d.matchedRewards; + }) as ProjectIpfsWithRewards; + return matchedRewards > donations ? matchedRewards : donations; + }; + + const getBarHeightPercentage = (value: bigint) => { + const maxValue = getMaxValue(); + if (!maxValue || !value) { + return 0; + } + return (Number(value) / Number(maxValue)) * 100; + }; + + useEffect(() => { + if (!highlightedBarAddress) { + return; + } + + const listener = e => { + if ( + e.target.id === EPOCH_RESULTS_BAR_ID || + e.target.parentElement.id === EPOCH_RESULTS_BAR_ID + ) { + return; + } + + setHighlightedBarAddress(null); + }; + + document.addEventListener('click', listener); + + return () => document.removeEventListener('click', listener); + }, [highlightedBarAddress]); + + return ( +
+
+ {isLoading ? ( + + ) : ( + projects.map(({ address, matchedRewards, donations, profileImageSmall, epoch }) => ( + `${element}${profileImageSmall}`)} + isHighlighted={!!(highlightedBarAddress && highlightedBarAddress === address)} + isLowlighted={!!(highlightedBarAddress && highlightedBarAddress !== address)} + onClick={setHighlightedBarAddress} + topBarHeightPercentage={getBarHeightPercentage(matchedRewards)} + /> + )) + )} +
+ +
+ ); +}; + +export default EpochResults; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResults/index.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResults/index.tsx new file mode 100644 index 0000000000..f409db3473 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResults/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './EpochResults'; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResults/types.ts b/client/src/components/Home/HomeGridEpochResults/EpochResults/types.ts new file mode 100644 index 0000000000..00f3c5a546 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResults/types.ts @@ -0,0 +1,7 @@ +import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards'; + +export default interface EpochResultsProps { + className?: string; + isLoading?: boolean; + projects: (ProjectIpfsWithRewards & { epoch: number })[]; +} diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.module.scss b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.module.scss new file mode 100644 index 0000000000..610f036022 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.module.scss @@ -0,0 +1,72 @@ +.root { + height: 100%; + min-width: 0.8rem; + position: relative; + + &.hasValue { + cursor: pointer; + } + + &:not(:first-child) { + margin-left: 1.4rem; + } + + @media #{$desktop-up} { + width: 1.6rem; + &:not(:first-child) { + margin-left: 1.8rem; + } + } + + @media #{$large-desktop-up} { + width: 0.8rem; + } + + .topBar { + background-color: $color-octant-orange2; + border-radius: $border-radius-08 $border-radius-08 0 0; + bottom: 0; + left: 0; + width: 100%; + position: absolute; + } + + .bottomBar { + background-color: $color-octant-green; + border-radius: $border-radius-08 $border-radius-08 0 0; + bottom: 0; + left: 0; + width: 100%; + position: absolute; + z-index: $z-index-2; + } + + .projectLogo { + position: absolute; + right: 50%; + transform: translate(50%); + display: flex; + align-items: center; + justify-content: center; + height: 3.2rem; + width: 3.2rem; + border-radius: 100%; + background-color: $color-octant-grey2; + box-shadow: 0 0 2.5rem 0 $color-black-10; + z-index: $z-index-3; + + .projectLogoImg { + height: 2.4rem; + width: 2.4rem; + border-radius: 100%; + } + + .triangle { + position: absolute; + bottom: -0.7rem; + border-top: 0.8rem solid $color-octant-grey2; + border-left: 0.4rem solid transparent; + border-right: 0.4rem solid transparent; + } + } +} diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.tsx new file mode 100644 index 0000000000..bc41c28531 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/EpochResultsBar.tsx @@ -0,0 +1,90 @@ +import cx from 'classnames'; +import { animate, AnimatePresence, motion, useInView } from 'framer-motion'; +import React, { FC, useEffect, useRef, useState } from 'react'; + +import Img from 'components/ui/Img'; +import { EPOCH_RESULTS_BAR_ID } from 'constants/domElementsIds'; + +import styles from './EpochResultsBar.module.scss'; +import EpochResultsBarProps from './types'; + +const EpochResultsBar: FC = ({ + address, + topBarHeightPercentage, + bottomBarHeightPercentage, + onClick, + isHighlighted, + isLowlighted, + imageSources, +}) => { + const topBarRef = useRef(null); + const bottomBarRef = useRef(null); + const ref = useRef(null); + + const isInView = useInView(ref); + + const [isProjectLogoVisible, setIsProjectLogoVisible] = useState(false); + + useEffect(() => { + if (!isInView) { + return; + } + const a = animate(topBarRef.current, { height: `${topBarHeightPercentage}%` }); + return () => { + a.cancel(); + }; + }, [topBarHeightPercentage, isInView]); + + useEffect(() => { + if (!isInView) { + return; + } + + const a = animate(bottomBarRef.current, { height: `${bottomBarHeightPercentage}%` }); + return () => { + a.cancel(); + }; + }, [bottomBarHeightPercentage, isInView]); + + return ( + topBarHeightPercentage && onClick(address)} + onMouseLeave={() => topBarHeightPercentage && setIsProjectLogoVisible(false)} + onMouseOver={() => topBarHeightPercentage && setIsProjectLogoVisible(true)} + whileHover={{ opacity: 1 }} + > + + {(isProjectLogoVisible || isHighlighted) && ( + bottomBarHeightPercentage ? topBarHeightPercentage : bottomBarHeightPercentage}% + 1rem)`, + opacity: 0, + scale: 0.5, + x: '50%', + }} + > + +
+ + )} + + + + + ); +}; + +export default EpochResultsBar; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItem/index.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/index.tsx similarity index 52% rename from client/src/components/Earn/EarnHistory/EarnHistoryItem/index.tsx rename to client/src/components/Home/HomeGridEpochResults/EpochResultsBar/index.tsx index fa37985d9d..829caeb836 100644 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItem/index.tsx +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './EarnHistoryItem'; +export { default } from './EpochResultsBar'; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/types.ts b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/types.ts new file mode 100644 index 0000000000..dc55ec5465 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsBar/types.ts @@ -0,0 +1,9 @@ +export default interface EpochResultsBarProps { + address: string; + bottomBarHeightPercentage: number; + imageSources: string[]; + isHighlighted: boolean; + isLowlighted: boolean; + onClick: (address: string) => void; + topBarHeightPercentage: number; +} diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.module.scss b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.module.scss new file mode 100644 index 0000000000..9b9a5a1f09 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.module.scss @@ -0,0 +1,64 @@ +.root { + display: flex; + font-size: $font-size-12; + height: 3.2rem; + align-items: center; + padding: 0 0.8rem 0rem 1.6rem; + background-color: $color-octant-grey6; + border-radius: $border-radius-08; + + .loading { + color: $color-octant-grey5; + font-weight: $font-weight-bold; + } + + .projectName { + color: $color-octant-dark; + font-weight: $font-weight-bold; + width: 9rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: left; + } + + .donations { + color: $color-octant-green; + } + + .matching { + color: $color-octant-orange2; + } + + .total { + color: $color-octant-dark; + } + + .donations, + .matching, + .total { + white-space: nowrap; + font-weight: $font-weight-semibold; + margin-left: 0.8rem; + + @media #{$tablet-up} { + margin-left: 1.6rem; + } + } + + .link { + white-space: nowrap; + color: $color-octant-grey5; + margin-left: auto; + font-weight: $font-weight-semibold; + padding: 0 1.6rem; + + &:hover { + color: $color-octant-dark; + } + + path { + stroke: $color-octant-grey5; + } + } +} diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx new file mode 100644 index 0000000000..5dbe53530a --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/EpochResultsDetails.tsx @@ -0,0 +1,121 @@ +import React, { FC, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; + +import Button from 'components/ui/Button'; +import useGetValuesToDisplay, { + GetValuesToDisplayProps, +} from 'hooks/helpers/useGetValuesToDisplay'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; +import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; + +import styles from './EpochResultsDetails.module.scss'; +import EpochResultsDetailsProps from './types'; + +const EpochResultsDetails: FC = ({ details, isLoading }) => { + const { i18n, t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridEpochResults', + }); + const { isMobile } = useMediaQuery(); + const navigate = useNavigate(); + const getValuesToDisplay = useGetValuesToDisplay(); + const [dots, setDots] = useState(0); + + const getValuesToDisplayCommonProps: GetValuesToDisplayProps = { + cryptoCurrency: 'ethereum', + getFormattedEthValueProps: { + maxNumberOfDigitsToShow: 5, + shouldIgnoreGwei: true, + shouldIgnoreWei: true, + showShortFormat: isMobile, + }, + showCryptoSuffix: true, + }; + + const donationsToDisplay = details + ? getValuesToDisplay({ + ...getValuesToDisplayCommonProps, + valueCrypto: details.donations, + }).primary + : null; + + const matchingToDisplay = details + ? getValuesToDisplay({ + ...getValuesToDisplayCommonProps, + valueCrypto: details.matchedRewards, + }).primary + : null; + + const totalToDisplay = details + ? getValuesToDisplay({ + ...getValuesToDisplayCommonProps, + valueCrypto: details.totalValueOfAllocations, + }).primary + : null; + + useEffect(() => { + if (!isLoading) { + return; + } + const id = setInterval( + () => + setDots(prev => { + if (prev === 3) { + return 0; + } + return prev + 1; + }), + 300, + ); + return () => { + clearInterval(id); + setDots(0); + }; + }, [isLoading]); + + return ( +
+ {isLoading && ( +
+ {t('loadingChartData')} + {[...Array(dots).keys()].map(key => ( + . + ))} +
+ )} + {details && ( + <> +
{details.name}
+
+ {isMobile ? t('donationsShort') : i18n.t('common.donations')} + {isMobile ? '' : ' '} + {donationsToDisplay} +
+
+ {isMobile ? t('matchingShort') : i18n.t('common.matching')} + {isMobile ? '' : ' '} + {matchingToDisplay} +
+
+ {isMobile ? t('totalShort') : i18n.t('common.total')} + {isMobile ? '' : ' '} + {totalToDisplay} +
+ {!isMobile && ( + + )} + + )} +
+ ); +}; + +export default EpochResultsDetails; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/index.tsx b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/index.tsx new file mode 100644 index 0000000000..77c87aedaa --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './EpochResultsDetails'; diff --git a/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts new file mode 100644 index 0000000000..a7a3b15f05 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/EpochResultsDetails/types.ts @@ -0,0 +1,6 @@ +import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards'; + +export default interface EpochResultsDetailsProps { + details?: ProjectIpfsWithRewards & { epoch: number }; + isLoading?: boolean; +} diff --git a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.module.scss b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.module.scss new file mode 100644 index 0000000000..76c86f4691 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.module.scss @@ -0,0 +1,65 @@ +.gridTile { + @media #{$tablet-up} { + grid-column: span 2; + } + + @media #{$desktop-up} { + grid-column: span 3; + } + + @media #{$large-desktop-up} { + grid-column: span 2; + + &.isProjectAdminMode, + &.isPatronMode { + grid-column: span 4; + } + } +} + +.root { + padding: 0 0.8rem 0.8rem; + display: flex; + flex-direction: column; + flex: 1; +} + +.arrowsWrapper { + user-select: none; + display: flex; + margin-left: auto; + + .arrow { + cursor: pointer; + width: 3.2rem; + height: 3.2rem; + background: $color-octant-grey8; + border-radius: $border-radius-10; + transition: all $transition-time-5; + display: flex; + align-items: center; + justify-content: center; + + &.leftArrow { + svg { + transform: rotate(180deg); + } + } + + &.isDisabled { + background: $color-octant-grey6; + + svg path { + fill: $color-octant-grey5; + } + } + + &:not(.isDisabled):hover { + background: $color-octant-grey1; + } + + &:first-child { + margin-right: 1.6rem; + } + } +} diff --git a/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx new file mode 100644 index 0000000000..ec08e395f2 --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/HomeGridEpochResults.tsx @@ -0,0 +1,118 @@ +import cx from 'classnames'; +import _first from 'lodash/first'; +import React, { FC, useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import GridTile from 'components/shared/Grid/GridTile'; +import Svg from 'components/ui/Svg'; +import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import useIsPatronMode from 'hooks/queries/useIsPatronMode'; +import useProjectsIpfsWithRewards, { + ProjectIpfsWithRewards, +} from 'hooks/queries/useProjectsIpfsWithRewards'; +import { arrowRight } from 'svg/misc'; + +import EpochResults from './EpochResults'; +import styles from './HomeGridEpochResults.module.scss'; +import HomeGridEpochResultsProps from './types'; + +const HomeGridEpochResults: FC = ({ className }) => { + const initalLoadingRef = useRef(true); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const { data: currentEpoch } = useCurrentEpoch(); + const [epoch, setEpoch] = useState(currentEpoch! - 1); + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridEpochResults', + }); + const { data: projectsIpfsWithRewards, isFetching: isFetchingProjectsIpfsWithRewards } = + useProjectsIpfsWithRewards(epoch); + const isProjectAdminMode = useIsProjectAdminMode(); + const { data: isPatronMode } = useIsPatronMode(); + + const projects = projectsIpfsWithRewards.reduce( + (acc, curr) => { + if (!curr.totalValueOfAllocations) { + return acc; + } + acc.unshift({ + ...curr, + epoch, + }); + return acc; + }, + [] as (ProjectIpfsWithRewards & { epoch: number })[], + ); + + const isAnyProjectDonated = projects.some(({ donations }) => donations > 0n); + + const isLoading = isFetchingProjectsIpfsWithRewards && !isAnyProjectDonated; + + const isRightArrowDisabled = + (isLoading && initalLoadingRef.current) || epoch === currentEpoch! - 1; + const isLeftArrowDisabled = (isLoading && initalLoadingRef.current) || epoch < 2; + + useEffect(() => { + if (!isDecisionWindowOpen || isLoading || epoch !== currentEpoch! - 1 || isAnyProjectDonated) { + return; + } + + setEpoch(prev => prev - 1); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoading]); + + useEffect(() => { + if ((initalLoadingRef.current && isLoading) || !initalLoadingRef.current) { + return; + } + + initalLoadingRef.current = false; + }, [isLoading]); + + return ( + +
{ + if (isLeftArrowDisabled) { + return; + } + setEpoch(prev => prev - 1); + }} + > + +
+
{ + if (isRightArrowDisabled) { + return; + } + setEpoch(prev => prev + 1); + }} + > + +
+
+ } + > +
+ +
+ + ); +}; + +export default HomeGridEpochResults; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/index.tsx b/client/src/components/Home/HomeGridEpochResults/index.tsx similarity index 50% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/index.tsx rename to client/src/components/Home/HomeGridEpochResults/index.tsx index 07c38db419..ef38dc65b4 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockBudgetBox/index.tsx +++ b/client/src/components/Home/HomeGridEpochResults/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './EarnGlmLockBudgetBox'; +export { default } from './HomeGridEpochResults'; diff --git a/client/src/components/Home/HomeGridEpochResults/types.ts b/client/src/components/Home/HomeGridEpochResults/types.ts new file mode 100644 index 0000000000..b1b0054ced --- /dev/null +++ b/client/src/components/Home/HomeGridEpochResults/types.ts @@ -0,0 +1,3 @@ +export default interface HomeGridEpochResultsProps { + className?: string; +} diff --git a/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.module.scss b/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.module.scss new file mode 100644 index 0000000000..0c76d6639c --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.module.scss @@ -0,0 +1,15 @@ +.root { + padding: 0.8rem 2.4rem 2.4rem; + + .divider { + margin-top: 2rem; + width: 100%; + height: 0.1rem; + background-color: $color-octant-grey1; + } + + .withdrawEthButton { + margin-top: 2.4rem; + width: 100%; + } +} diff --git a/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.tsx b/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.tsx new file mode 100644 index 0000000000..e2978f9f27 --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/HomeGridPersonalAllocation.tsx @@ -0,0 +1,134 @@ +import { format } from 'date-fns'; +import _first from 'lodash/first'; +import React, { FC, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useAccount } from 'wagmi'; + +import ModalWithdrawEth from 'components/Home/HomeGridPersonalAllocation/ModalWithdrawEth'; +import GridTile from 'components/shared/Grid/GridTile'; +import Sections from 'components/ui/BoxRounded/Sections/Sections'; +import Button from 'components/ui/Button'; +import DoubleValue from 'components/ui/DoubleValue'; +import useEpochAndAllocationTimestamps from 'hooks/helpers/useEpochAndAllocationTimestamps'; +import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useCurrentEpochProps from 'hooks/queries/useCurrentEpochProps'; +import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import useWithdrawals from 'hooks/queries/useWithdrawals'; +import useTransactionLocalStore from 'store/transactionLocal/store'; +import getIsPreLaunch from 'utils/getIsPreLaunch'; + +import styles from './HomeGridPersonalAllocation.module.scss'; +import HomeGridPersonalAllocationProps from './types'; + +const HomeGridPersonalAllocation: FC = ({ className }) => { + const { isConnected } = useAccount(); + const { isMobile } = useMediaQuery(); + const { i18n, t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridPersonalAllocation', + }); + const [isModalWithdrawEthOpen, setIsModalWithdrawEthOpen] = useState(false); + const { data: currentEpoch } = useCurrentEpoch(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const { timeCurrentEpochStart, timeCurrentAllocationEnd } = useEpochAndAllocationTimestamps(); + const { data: currentEpochProps } = useCurrentEpochProps(); + const { data: withdrawals, isFetching: isFetchingWithdrawals } = useWithdrawals(); + const { isAppWaitingForTransactionToBeIndexed, transactionsPending } = useTransactionLocalStore( + state => ({ + isAppWaitingForTransactionToBeIndexed: state.data.isAppWaitingForTransactionToBeIndexed, + transactionsPending: state.data.transactionsPending, + }), + ); + + const isPreLaunch = getIsPreLaunch(currentEpoch); + const isProjectAdminMode = useIsProjectAdminMode(); + + return ( + <> + +
+ + +
+ +
{t('pendingFundsAvailableAfter')}
+
+ {/* TODO OCT-1041 fetch next epoch props instead of assuming the same length */} + {currentEpochProps && timeCurrentEpochStart && timeCurrentAllocationEnd + ? format( + new Date( + isDecisionWindowOpen + ? timeCurrentAllocationEnd + : // When AW is closed, it's when the last AW closed. + timeCurrentEpochStart + currentEpochProps.decisionWindow, + ), + 'haaa z, d LLLL', + ) + : ''} +
+
+ ), + }, + }, + ]} + variant="standard" + /> + +
+
+ setIsModalWithdrawEthOpen(false), + }} + /> + + ); +}; + +export default HomeGridPersonalAllocation; diff --git a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/ModalWithdrawEth.tsx b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/ModalWithdrawEth.tsx new file mode 100644 index 0000000000..eba45daf2e --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/ModalWithdrawEth.tsx @@ -0,0 +1,21 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import WithdrawEth from 'components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth'; +import Modal from 'components/ui/Modal'; + +import ModalWithdrawEthProps from './types'; + +const ModalWithdrawEth: FC = ({ modalProps }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridPersonalAllocation.modalWithdrawEth', + }); + + return ( + + + + ); +}; + +export default ModalWithdrawEth; diff --git a/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.module.scss b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.module.scss new file mode 100644 index 0000000000..3bfdb2167a --- /dev/null +++ b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.module.scss @@ -0,0 +1,9 @@ +.root { + width: 100%; + height: 100%; +} + +.button { + width: 100%; + margin: 2.4rem 0; +} diff --git a/client/src/components/Earn/EarnWithdrawEth/EarnWithdrawEth.tsx b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.tsx similarity index 89% rename from client/src/components/Earn/EarnWithdrawEth/EarnWithdrawEth.tsx rename to client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.tsx index 9f916abb7a..f489c977d1 100644 --- a/client/src/components/Earn/EarnWithdrawEth/EarnWithdrawEth.tsx +++ b/client/src/components/Home/HomeGridPersonalAllocation/ModalWithdrawEth/WithdrawEth/WithdrawEth.tsx @@ -10,12 +10,12 @@ import useWithdrawEth, { BatchWithdrawRequest } from 'hooks/mutations/useWithdra import useWithdrawals from 'hooks/queries/useWithdrawals'; import useTransactionLocalStore from 'store/transactionLocal/store'; -import styles from './EarnWithdrawEth.module.scss'; -import EarnWithdrawEthProps from './types'; +import WithdrawEthProps from './types'; +import styles from './WithdrawEth.module.scss'; -const EarnWithdrawEth: FC = ({ onCloseModal }) => { +const WithdrawEth: FC = ({ onCloseModal }) => { const { i18n, t } = useTranslation('translation', { - keyPrefix: 'components.dedicated.withdrawEth', + keyPrefix: 'components.home.homeGridPersonalAllocation.modalWithdrawEth', }); const { data: feeData, isFetching: isFetchingFeeData } = useFeeData(); const { isAppWaitingForTransactionToBeIndexed, addTransactionPending } = useTransactionLocalStore( @@ -59,7 +59,7 @@ const EarnWithdrawEth: FC = ({ onCloseModal }) => { showCryptoSuffix: true, valueCrypto: withdrawals?.sums.available, }, - label: t('amount'), + label: i18n.t('common.amount'), }, { doubleValueProps: { @@ -74,7 +74,7 @@ const EarnWithdrawEth: FC = ({ onCloseModal }) => { return (
- +
) : ( type !== 'allocation' && ( - + ) )}
@@ -88,9 +88,8 @@ const EarnHistoryItem: FC = ({ isLast, ...rest }) => { variant="tiny" /> - {!isLast &&
} - setIsModalOpen(false), @@ -100,4 +99,4 @@ const EarnHistoryItem: FC = ({ isLast, ...rest }) => { ); }; -export default memo(EarnHistoryItem); +export default memo(TransactionsListItem); diff --git a/client/src/components/Home/HomeGridTransactions/TransactionsListItem/index.tsx b/client/src/components/Home/HomeGridTransactions/TransactionsListItem/index.tsx new file mode 100644 index 0000000000..103b277ac4 --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/TransactionsListItem/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './TransactionsListItem'; diff --git a/client/src/components/Earn/EarnHistory/EarnHistoryItem/types.ts b/client/src/components/Home/HomeGridTransactions/TransactionsListItem/types.ts similarity index 75% rename from client/src/components/Earn/EarnHistory/EarnHistoryItem/types.ts rename to client/src/components/Home/HomeGridTransactions/TransactionsListItem/types.ts index 1d6312c52f..012f045937 100644 --- a/client/src/components/Earn/EarnHistory/EarnHistoryItem/types.ts +++ b/client/src/components/Home/HomeGridTransactions/TransactionsListItem/types.ts @@ -1,13 +1,12 @@ import { HistoryElement, EventData } from 'hooks/queries/useHistory'; import { TransactionPending } from 'store/transactionLocal/types'; -type EarnHistoryItemProps = Omit & { +type TransactionsListItemProps = Omit & { eventData: Partial & { amount: bigint }; } & { isFinalized?: TransactionPending['isFinalized']; - isLast: boolean; isMultisig?: boolean; isWaitingForTransactionInitialized?: TransactionPending['isWaitingForTransactionInitialized']; }; -export default EarnHistoryItemProps; +export default TransactionsListItemProps; diff --git a/client/src/components/Earn/EarnHistory/EarnHistorySkeleton/EarnHistorySkeleton.module.scss b/client/src/components/Home/HomeGridTransactions/TransactionsSkeleton/TransactionsSkeleton.module.scss similarity index 100% rename from client/src/components/Earn/EarnHistory/EarnHistorySkeleton/EarnHistorySkeleton.module.scss rename to client/src/components/Home/HomeGridTransactions/TransactionsSkeleton/TransactionsSkeleton.module.scss diff --git a/client/src/components/Earn/EarnHistory/EarnHistorySkeleton/EarnHistorySkeleton.tsx b/client/src/components/Home/HomeGridTransactions/TransactionsSkeleton/TransactionsSkeleton.tsx similarity index 73% rename from client/src/components/Earn/EarnHistory/EarnHistorySkeleton/EarnHistorySkeleton.tsx rename to client/src/components/Home/HomeGridTransactions/TransactionsSkeleton/TransactionsSkeleton.tsx index 83456c4b48..e8375e2e91 100644 --- a/client/src/components/Earn/EarnHistory/EarnHistorySkeleton/EarnHistorySkeleton.tsx +++ b/client/src/components/Home/HomeGridTransactions/TransactionsSkeleton/TransactionsSkeleton.tsx @@ -1,9 +1,9 @@ import cx from 'classnames'; import React, { ReactElement } from 'react'; -import styles from './EarnHistorySkeleton.module.scss'; +import styles from './TransactionsSkeleton.module.scss'; -const EarnHistorySkeleton = (): ReactElement => { +const TransactionsSkeleton = (): ReactElement => { return (
@@ -18,4 +18,4 @@ const EarnHistorySkeleton = (): ReactElement => { ); }; -export default EarnHistorySkeleton; +export default TransactionsSkeleton; diff --git a/client/src/components/Home/HomeGridTransactions/TransactionsSkeleton/index.tsx b/client/src/components/Home/HomeGridTransactions/TransactionsSkeleton/index.tsx new file mode 100644 index 0000000000..ba5cbd5467 --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/TransactionsSkeleton/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './TransactionsSkeleton'; diff --git a/client/src/components/Home/HomeGridTransactions/index.tsx b/client/src/components/Home/HomeGridTransactions/index.tsx new file mode 100644 index 0000000000..fa3e746f54 --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeGridTransactions'; diff --git a/client/src/components/Home/HomeGridTransactions/types.ts b/client/src/components/Home/HomeGridTransactions/types.ts new file mode 100644 index 0000000000..4cbc3cbfba --- /dev/null +++ b/client/src/components/Home/HomeGridTransactions/types.ts @@ -0,0 +1,3 @@ +export default interface HomeGridTransactionsProps { + className?: string; +} diff --git a/client/src/components/Settings/SettingsAddressScore/SettingsAddressScore.module.scss b/client/src/components/Home/HomeGridUQScore/AddressScore/AddressScore.module.scss similarity index 100% rename from client/src/components/Settings/SettingsAddressScore/SettingsAddressScore.module.scss rename to client/src/components/Home/HomeGridUQScore/AddressScore/AddressScore.module.scss diff --git a/client/src/components/Settings/SettingsAddressScore/SettingsAddressScore.tsx b/client/src/components/Home/HomeGridUQScore/AddressScore/AddressScore.tsx similarity index 89% rename from client/src/components/Settings/SettingsAddressScore/SettingsAddressScore.tsx rename to client/src/components/Home/HomeGridUQScore/AddressScore/AddressScore.tsx index ca9ef29e18..654d18ca73 100644 --- a/client/src/components/Settings/SettingsAddressScore/SettingsAddressScore.tsx +++ b/client/src/components/Home/HomeGridUQScore/AddressScore/AddressScore.tsx @@ -10,10 +10,10 @@ import Svg from 'components/ui/Svg'; import { checkMark } from 'svg/misc'; import truncateEthAddress from 'utils/truncateEthAddress'; -import styles from './SettingsAddressScore.module.scss'; -import SettingsAddressScoreProps from './types'; +import styles from './AddressScore.module.scss'; +import AddressScoreProps from './types'; -const SettingsAddressScore: FC = ({ +const AddressScore: FC = ({ address, badge, score, @@ -26,7 +26,9 @@ const SettingsAddressScore: FC = ({ mode, showActiveDot, }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore.addressScore', + }); const { address: activeAddress } = useAccount(); const isActive = activeAddress === address; @@ -92,4 +94,4 @@ const SettingsAddressScore: FC = ({ ); }; -export default memo(SettingsAddressScore); +export default memo(AddressScore); diff --git a/client/src/components/Home/HomeGridUQScore/AddressScore/index.tsx b/client/src/components/Home/HomeGridUQScore/AddressScore/index.tsx new file mode 100644 index 0000000000..772b0e39ed --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/AddressScore/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './AddressScore'; diff --git a/client/src/components/Settings/SettingsAddressScore/types.ts b/client/src/components/Home/HomeGridUQScore/AddressScore/types.ts similarity index 85% rename from client/src/components/Settings/SettingsAddressScore/types.ts rename to client/src/components/Home/HomeGridUQScore/AddressScore/types.ts index dfaace9d8b..807f77fa20 100644 --- a/client/src/components/Settings/SettingsAddressScore/types.ts +++ b/client/src/components/Home/HomeGridUQScore/AddressScore/types.ts @@ -1,4 +1,4 @@ -export default interface SettingsAddressScoreProps { +export default interface AddressScoreProps { address: string; areBottomCornersRounded?: boolean; badge: 'primary' | 'secondary'; diff --git a/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.module.scss b/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.module.scss new file mode 100644 index 0000000000..2a6bc91cba --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.module.scss @@ -0,0 +1,34 @@ +.root { + padding: 0 2.4rem 2.4rem; + + .furtherActions { + height: 4.8rem; + justify-content: left; + font-size: 1rem; + border-bottom: 0.1rem solid $color-octant-grey3; + margin-bottom: 2.4rem; + padding: 0; + } + + .buttonsWrapper { + width: 100%; + display: flex; + justify-content: space-between; + + .button { + flex: 1; + + &:first-child { + margin-right: 1.2rem; + } + } + } +} + +.titleSuffix { + margin-left: auto; + cursor: pointer; + color: $color-octant-green; + font-size: $font-size-10; + font-weight: $font-weight-bold; +} diff --git a/client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.tsx b/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx similarity index 65% rename from client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.tsx rename to client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx index 61118f8b5f..649b95a325 100644 --- a/client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.tsx +++ b/client/src/components/Home/HomeGridUQScore/HomeGridUQScore.tsx @@ -1,18 +1,21 @@ import { watchAccount } from '@wagmi/core'; import uniq from 'lodash/uniq'; -import React, { ReactNode, useEffect, useMemo, useState } from 'react'; +import React, { FC, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useAccount, useConnectors } from 'wagmi'; import { wagmiConfig } from 'api/clients/client-wagmi'; -import ModalSettingsCalculatingUQScore from 'components/Settings/ModalSettingsCalculatingUQScore'; -import ModalSettingsCalculatingYourUniqueness from 'components/Settings/ModalSettingsCalculatingYourUniqueness'; -import ModalSettingsRecalculatingScore from 'components/Settings/ModalSettingsRecalculatingScore'; -import SettingsUniquenessScoreAddresses from 'components/Settings/SettingsUniquenessScoreAddresses'; -import BoxRounded from 'components/ui/BoxRounded'; +import HomeGridUQScoreAddresses from 'components/Home/HomeGridUQScore/HomeGridUQScoreAddresses'; +import ModalCalculatingUQScore from 'components/Home/HomeGridUQScore/ModalCalculatingUQScore'; +import ModalCalculatingYourUniqueness from 'components/Home/HomeGridUQScore/ModalCalculatingYourUniqueness'; +import ModalRecalculatingScore from 'components/Home/HomeGridUQScore/ModalRecalculatingScore'; +import GridTile from 'components/shared/Grid/GridTile'; import Button from 'components/ui/Button'; -import { DELEGATION_MIN_SCORE } from 'constants/delegation'; -import { GITCOIN_PASSPORT_CUSTOM_OCTANT_DASHBOARD } from 'constants/urls'; +import { UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1 } from 'constants/uq'; +import { + GITCOIN_PASSPORT_CUSTOM_OCTANT_DASHBOARD, + TIME_OUT_LIST_DISPUTE_FORM, +} from 'constants/urls'; import useCheckDelegation from 'hooks/mutations/useCheckDelegation'; import useRefreshAntisybilStatus from 'hooks/mutations/useRefreshAntisybilStatus'; import useAntisybilStatusScore from 'hooks/queries/useAntisybilStatusScore'; @@ -20,13 +23,16 @@ import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useUqScore from 'hooks/queries/useUqScore'; import useUserTOS from 'hooks/queries/useUserTOS'; import toastService from 'services/toastService'; -import useSettingsStore from 'store/settings/store'; +import useDelegationStore from 'store/delegation/store'; -import styles from './SettingsUniquenessScoreBox.module.scss'; +import styles from './HomeGridUQScore.module.scss'; +import HomeGridUQScoreProps from './types'; -const SettingsUniquenessScoreBox = (): ReactNode => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); - const { address } = useAccount(); +const HomeGridUQScore: FC = ({ className }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore', + }); + const { address, isConnected } = useAccount(); const connectors = useConnectors(); const { data: currentEpoch } = useCurrentEpoch(); const { data: isUserTOSAccepted } = useUserTOS(); @@ -52,7 +58,7 @@ const SettingsUniquenessScoreBox = (): ReactNode => { setDelegationSecondaryAddress, setIsDelegationCalculatingUQScoreModalOpen, setIsDelegationCompleted, - } = useSettingsStore(state => ({ + } = useDelegationStore(state => ({ delegationPrimaryAddress: state.data.delegationPrimaryAddress, delegationSecondaryAddress: state.data.delegationSecondaryAddress, isDelegationCalculatingUQScoreModalOpen: state.data.isDelegationCalculatingUQScoreModalOpen, @@ -75,22 +81,6 @@ const SettingsUniquenessScoreBox = (): ReactNode => { : primaryAddressScore === null || primaryAddressScore !== address, ); - const isRecalculateButtonDisabled = - isDelegationCompleted || - isFetchingScore || - isFetchingUqScore || - delegationSecondaryAddress === '0x???'; - - const isDelegateButtonDisabled = - isDelegationCompleted || - isFetchingScore || - primaryAddressScore === null || - primaryAddressScore === undefined || - primaryAddressScore >= 20 || - isFetchingUqScore || - uqScore === 100n || - delegationSecondaryAddress === '0x???'; - const { mutateAsync: checkDelegationMutation } = useCheckDelegation(); const { mutateAsync: refreshAntisybilStatus, @@ -111,7 +101,7 @@ const SettingsUniquenessScoreBox = (): ReactNode => { }, ); - const modalSettingsCalculatingUQScoreProps = useMemo(() => { + const modalCalculatingUQScoreProps = useMemo(() => { return { isOpen: isDelegationCalculatingUQScoreModalOpen, onClosePanel: () => setIsDelegationCalculatingUQScoreModalOpen(false), @@ -119,7 +109,7 @@ const SettingsUniquenessScoreBox = (): ReactNode => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isDelegationCalculatingUQScoreModalOpen]); - const modalSettingsRecalculatingScoreProps = useMemo(() => { + const modalRecalculatingScoreProps = useMemo(() => { return { isOpen: isRecalculatingScoreModalOpen, onClosePanel: () => setIsRecalculatingScoreModalOpen(false), @@ -127,7 +117,7 @@ const SettingsUniquenessScoreBox = (): ReactNode => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isRecalculatingScoreModalOpen]); - const modalSettingsCalculatingYourUniquenessProps = useMemo(() => { + const modalCalculatingYourUniquenessProps = useMemo(() => { return { isOpen: isCalculatingYourUniquenessModalOpen, onClosePanel: () => setIsCalculatingYourUniquenessModalOpen(false), @@ -179,16 +169,16 @@ const SettingsUniquenessScoreBox = (): ReactNode => { return; } if (isDelegationCompleted) { - setSecondaryAddressScore(antisybilStatusScore); + setSecondaryAddressScore(antisybilStatusScore?.score); } else { if (refreshAntisybilStatusError) { setDelegationPrimaryAddress(address); setDelegationSecondaryAddress('0x???'); } setPrimaryAddressScore( - antisybilStatusScore < DELEGATION_MIN_SCORE && uqScore === 100n - ? DELEGATION_MIN_SCORE - : antisybilStatusScore, + antisybilStatusScore?.score < UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1 && uqScore === 100n + ? UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1 + : antisybilStatusScore?.score, ); } setIsFetchingScore(false); @@ -230,64 +220,91 @@ const SettingsUniquenessScoreBox = (): ReactNode => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isUserTOSAccepted, address]); + const isRecalculateButtonDisabled = + !isConnected || + isDelegationCompleted || + isFetchingScore || + isFetchingUqScore || + delegationSecondaryAddress === '0x???' || + antisybilStatusScore?.isOnTimeOutList; + + const isDelegateButtonDisabled = + !isConnected || + isDelegationCompleted || + isFetchingScore || + primaryAddressScore === null || + primaryAddressScore === undefined || + primaryAddressScore >= 20 || + isFetchingUqScore || + uqScore === 100n || + delegationSecondaryAddress === '0x???' || + antisybilStatusScore?.isOnTimeOutList; + return ( - setIsCalculatingYourUniquenessModalOpen(true)} - > - {t('whatIsThis')} -
- } - > - <> - - + <> + { - setIsDelegationInProgress(true); - setDelegationPrimaryAddress(address); - setIsDelegationConnectModalOpen(true); - }} - > - {t('delegate')} - + className={styles.titleSuffix} + isButtonScalingUpOnHover={false} + label={t('whatIsThis')} + onClick={() => setIsCalculatingYourUniquenessModalOpen(true)} + variant="link3" + /> + } + > +
+ + {antisybilStatusScore?.isOnTimeOutList ? ( + + +
- - - - - + + + + + ); }; -export default SettingsUniquenessScoreBox; +export default HomeGridUQScore; diff --git a/client/src/components/Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.module.scss b/client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.module.scss similarity index 71% rename from client/src/components/Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.module.scss rename to client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.module.scss index 05ba83fcad..b2d5a6f105 100644 --- a/client/src/components/Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.module.scss +++ b/client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.module.scss @@ -1,12 +1,40 @@ .root { + position: relative; width: 100%; - height: 7.2rem; + height: 10.4rem; padding: 0 2.4rem; display: flex; align-items: center; border-radius: $border-radius-16; background-color: $color-octant-grey3; - margin: 1.6rem 0 1.4rem; + + &.noWalletConnected { + .octantLogo { + path { + fill: $color-octant-grey2; + } + } + + .addresses .addressesGroup { + max-width: 13.2rem; + opacity: 0.5; + } + + .score { + color: $color-octant-grey5; + } + } + + &.isOnTimeOutList { + background: $color-octant-orange6; + } + + .isOnTimeOutListLabel { + position: absolute; + top: 1.6rem; + left: 2.4rem; + text-transform: uppercase; + } .avatarsGroup { position: relative; @@ -51,12 +79,12 @@ font-weight: $font-weight-bold; letter-spacing: 0.03rem; text-transform: uppercase; + text-align: left; } } .score { - font-size: $font-size-24; - font-weight: $font-weight-bold; + @include fontBig($font-size-32); color: $color-octant-dark; margin-left: auto; diff --git a/client/src/components/Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.tsx b/client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.tsx similarity index 62% rename from client/src/components/Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.tsx rename to client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.tsx index 5c9ee98235..36f7874897 100644 --- a/client/src/components/Settings/SettingsUniquenessScoreAddresses/SettingsUniquenessScoreAddresses.tsx +++ b/client/src/components/Home/HomeGridUQScore/HomeGridUQScoreAddresses/HomeGridUQScoreAddresses.tsx @@ -6,20 +6,24 @@ import { useAccount } from 'wagmi'; import Identicon from 'components/ui/Identicon'; import Svg from 'components/ui/Svg'; -import useSettingsStore from 'store/settings/store'; +import TinyLabel from 'components/ui/TinyLabel'; +import useDelegationStore from 'store/delegation/store'; import { octant } from 'svg/logo'; import truncateEthAddress from 'utils/truncateEthAddress'; -import styles from './SettingsUniquenessScoreAddresses.module.scss'; -import SettingsUniquenessScoreAddressesProps from './types'; +import styles from './HomeGridUQScoreAddresses.module.scss'; +import HomeGridUQScoreAddressesProps from './types'; -const SettingsUniquenessScoreAddresses: FC = ({ +const HomeGridUQScoreAddresses: FC = ({ isFetchingScore, + isOnTimeOutList, }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore', + }); const ref = useRef(null); - const { address: accountAddress } = useAccount(); + const { address: accountAddress, isConnected } = useAccount(); const { isDelegationCompleted, @@ -27,7 +31,7 @@ const SettingsUniquenessScoreAddresses: FC ({ + } = useDelegationStore(state => ({ delegationPrimaryAddress: state.data.delegationPrimaryAddress, delegationSecondaryAddress: state.data.delegationSecondaryAddress, isDelegationCompleted: state.data.isDelegationCompleted, @@ -43,20 +47,25 @@ const SettingsUniquenessScoreAddresses: FC 1; const addressesToShow = useMemo(() => { + if (!isConnected) { + return t('noWalletConnected'); + } if (showMoreThanOneAddress) { return addresses.map(address => address.slice(0, 5)).join(', '); } return truncateEthAddress(addresses.at(0) || ''); - }, [showMoreThanOneAddress, addresses]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [showMoreThanOneAddress, addresses, isConnected]); useEffect(() => { - if (isFetchingScore || !ref?.current) { + if (isFetchingScore || !ref?.current || !isConnected) { return; } const controls = animate( @@ -74,15 +83,35 @@ const SettingsUniquenessScoreAddresses: FC controls.complete(); - }, [isFetchingScore, isDelegationCompleted, secondaryAddressScore, primaryAddressScore]); + }, [ + isFetchingScore, + isDelegationCompleted, + secondaryAddressScore, + primaryAddressScore, + isConnected, + ]); return ( -
+
+ {isOnTimeOutList && ( + + )}
- {addresses.map(address => ( -
- {address === '0x???' ? ( - + {addresses?.map((address, index) => ( +
+ {!isConnected || address === '0x???' ? ( + ) : ( )} @@ -102,4 +131,4 @@ const SettingsUniquenessScoreAddresses: FC = ({ - setShowCloseButton, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); +const CalculatingUQScore: FC = ({ setShowCloseButton }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore.modalCalculatingUQScore', + }); const { address } = useAccount(); const { data: isUserTOSAccepted } = useUserTOS(); @@ -41,7 +41,7 @@ const SettingsCalculatingUQScore: FC = ({ setCalculatingUQScoreMode, setIsDelegationCompleted, setSecondaryAddressScore, - } = useSettingsStore(state => ({ + } = useDelegationStore(state => ({ calculatingUQScoreMode: state.data.calculatingUQScoreMode, delegationPrimaryAddress: state.data.delegationPrimaryAddress, delegationSecondaryAddress: state.data.delegationSecondaryAddress, @@ -69,13 +69,13 @@ const SettingsCalculatingUQScore: FC = ({ const showLowScoreInfo = isScoreHighlighted && secondaryAddressAntisybilStatusScore !== undefined && - secondaryAddressAntisybilStatusScore < DELEGATION_MIN_SCORE; + secondaryAddressAntisybilStatusScore.score < UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1; const scoreHighlight = useMemo(() => { if (!isScoreHighlighted || secondaryAddressAntisybilStatusScore === undefined) { return undefined; } - if (secondaryAddressAntisybilStatusScore < DELEGATION_MIN_SCORE) { + if (secondaryAddressAntisybilStatusScore.score < UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1) { return 'red'; } return 'black'; @@ -146,12 +146,12 @@ const SettingsCalculatingUQScore: FC = ({ setLastDoneStep(1); setTimeout(() => { setLastDoneStep(2); - if (secondaryAddressAntisybilStatusScore < DELEGATION_MIN_SCORE) { + if (secondaryAddressAntisybilStatusScore.score < UQ_SCORE_THRESHOLD_FOR_LEVERAGE_1) { setShowCloseButton(true); setIsDelegationInProgress(false); return; } - setSecondaryAddressScore(secondaryAddressAntisybilStatusScore); + setSecondaryAddressScore(secondaryAddressAntisybilStatusScore.score); setCalculatingUQScoreMode('sign'); }, 2500); }, 2500); @@ -160,7 +160,7 @@ const SettingsCalculatingUQScore: FC = ({ return ( - = ({ score={primaryAddressScore ?? 0} showActiveDot={calculatingUQScoreMode === 'sign'} /> - = ({ } mode={calculatingUQScoreMode} onSignMessage={() => signMessageAndDelegate(true)} - score={secondaryAddressAntisybilStatusScore ?? 0} + score={secondaryAddressAntisybilStatusScore?.score ?? 0} scoreHighlight={scoreHighlight} showActiveDot={calculatingUQScoreMode === 'sign'} /> {calculatingUQScoreMode === 'score' && ( - + )} {showLowScoreInfo && ( = ({ ); }; -export default SettingsCalculatingUQScore; +export default CalculatingUQScore; diff --git a/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/index.tsx b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/index.tsx similarity index 51% rename from client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/index.tsx rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/index.tsx index 002295c96d..7a7fa10116 100644 --- a/client/src/components/Earn/EarnGlmLock/EarnGlmLockStepper/index.tsx +++ b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/index.tsx @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './EarnGlmLockStepper'; +export { default } from './CalculatingUQScore'; diff --git a/client/src/components/Settings/SettingsCalculatingUQScore/types.ts b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/types.ts similarity index 100% rename from client/src/components/Settings/SettingsCalculatingUQScore/types.ts rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore/types.ts diff --git a/client/src/components/Settings/ModalSettingsCalculatingUQScore/ModalSettingsCalculatingUQScore.module.scss b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/ModalCalculatingUQScore.module.scss similarity index 100% rename from client/src/components/Settings/ModalSettingsCalculatingUQScore/ModalSettingsCalculatingUQScore.module.scss rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/ModalCalculatingUQScore.module.scss diff --git a/client/src/components/Settings/ModalSettingsCalculatingUQScore/ModalSettingsCalculatingUQScore.tsx b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/ModalCalculatingUQScore.tsx similarity index 61% rename from client/src/components/Settings/ModalSettingsCalculatingUQScore/ModalSettingsCalculatingUQScore.tsx rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/ModalCalculatingUQScore.tsx index a2048c5968..227295311c 100644 --- a/client/src/components/Settings/ModalSettingsCalculatingUQScore/ModalSettingsCalculatingUQScore.tsx +++ b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/ModalCalculatingUQScore.tsx @@ -1,20 +1,20 @@ import React, { FC, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import SettingsCalculatingUQScore from 'components/Settings/SettingsCalculatingUQScore'; +import CalculatingUQScore from 'components/Home/HomeGridUQScore/ModalCalculatingUQScore/CalculatingUQScore'; import Button from 'components/ui/Button'; import Modal from 'components/ui/Modal'; -import useSettingsStore from 'store/settings/store'; +import useDelegationStore from 'store/delegation/store'; -import styles from './ModalSettingsCalculatingUQScore.module.scss'; -import ModalSettingsCalculatingUQScoreProps from './types'; +import styles from './ModalCalculatingUQScore.module.scss'; +import ModalCalculatingUQScoreProps from './types'; -const ModalSettingsCalculatingUQScore: FC = ({ - modalProps, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); +const ModalCalculatingUQScore: FC = ({ modalProps }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore.modalCalculatingUQScore', + }); - const { calculatingUQScoreMode, setIsDelegationConnectModalOpen } = useSettingsStore(state => ({ + const { calculatingUQScoreMode, setIsDelegationConnectModalOpen } = useDelegationStore(state => ({ calculatingUQScoreMode: state.data.calculatingUQScoreMode, setIsDelegationConnectModalOpen: state.setIsDelegationConnectModalOpen, })); @@ -24,7 +24,7 @@ const ModalSettingsCalculatingUQScore: FC return ( @@ -45,9 +45,9 @@ const ModalSettingsCalculatingUQScore: FC showCloseButton={showCloseButton} {...modalProps} > - + ); }; -export default ModalSettingsCalculatingUQScore; +export default ModalCalculatingUQScore; diff --git a/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/index.tsx b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/index.tsx new file mode 100644 index 0000000000..1ecc801c97 --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ModalCalculatingUQScore'; diff --git a/client/src/components/Settings/ModalSettingsCalculatingUQScore/types.ts b/client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/types.ts similarity index 100% rename from client/src/components/Settings/ModalSettingsCalculatingUQScore/types.ts rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingUQScore/types.ts diff --git a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/ModalSettingsCalculatingYourUniqueness.module.scss b/client/src/components/Home/HomeGridUQScore/ModalCalculatingYourUniqueness/ModalCalculatingYourUniqueness.module.scss similarity index 100% rename from client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/ModalSettingsCalculatingYourUniqueness.module.scss rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingYourUniqueness/ModalCalculatingYourUniqueness.module.scss diff --git a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/ModalSettingsCalculatingYourUniqueness.tsx b/client/src/components/Home/HomeGridUQScore/ModalCalculatingYourUniqueness/ModalCalculatingYourUniqueness.tsx similarity index 73% rename from client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/ModalSettingsCalculatingYourUniqueness.tsx rename to client/src/components/Home/HomeGridUQScore/ModalCalculatingYourUniqueness/ModalCalculatingYourUniqueness.tsx index 58a900748b..95ad8f9a00 100644 --- a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/ModalSettingsCalculatingYourUniqueness.tsx +++ b/client/src/components/Home/HomeGridUQScore/ModalCalculatingYourUniqueness/ModalCalculatingYourUniqueness.tsx @@ -13,18 +13,21 @@ import { } from 'constants/urls'; import useModalStepperNavigation from 'hooks/helpers/useModalStepperNavigation'; -import styles from './ModalSettingsCalculatingYourUniqueness.module.scss'; -import ModalSettingsCalculatingYourUniquenessProps from './types'; +import styles from './ModalCalculatingYourUniqueness.module.scss'; +import ModalCalculatingYourUniquenessProps from './types'; -const ModalSettingsCalculatingYourUniqueness: FC = ({ +const ModalCalculatingYourUniqueness: FC = ({ modalProps, }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); + const translationKeyPrefix = 'components.home.homeGridUQScore.modalCalculatingYourUniqueness'; + const { t } = useTranslation('translation', { + keyPrefix: translationKeyPrefix, + }); const steps = [ ]} - i18nKey="views.settings.calculatingYourUniquenessStep1" + i18nKey={`${translationKeyPrefix}.calculatingYourUniquenessStep1`} />, , ]} - i18nKey="views.settings.calculatingYourUniquenessStep2" + i18nKey={`${translationKeyPrefix}.calculatingYourUniquenessStep2`} />, , ]} - i18nKey="views.settings.calculatingYourUniquenessStep3" + i18nKey={`${translationKeyPrefix}.calculatingYourUniquenessStep3`} />, ]; @@ -55,7 +58,7 @@ const ModalSettingsCalculatingYourUniqueness: FC @@ -75,7 +78,7 @@ const ModalSettingsCalculatingYourUniqueness: FC { if (stepIndex === currentStepIndex && stepIndex !== steps.length - 1) { @@ -89,4 +92,4 @@ const ModalSettingsCalculatingYourUniqueness: FC; } diff --git a/client/src/components/Settings/ModalSettingsRecalculatingScore/ModalSettingsRecalculatingScore.module.scss b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/ModalRecalculatingScore.module.scss similarity index 100% rename from client/src/components/Settings/ModalSettingsRecalculatingScore/ModalSettingsRecalculatingScore.module.scss rename to client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/ModalRecalculatingScore.module.scss diff --git a/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/ModalRecalculatingScore.tsx b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/ModalRecalculatingScore.tsx new file mode 100644 index 0000000000..38d8b676b2 --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/ModalRecalculatingScore.tsx @@ -0,0 +1,27 @@ +import React, { FC, memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +import RecalculatingScore from 'components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore'; +import Modal from 'components/ui/Modal'; + +import styles from './ModalRecalculatingScore.module.scss'; +import ModalRecalculatingScoreProps from './types'; + +const ModalRecalculatingScore: FC = ({ modalProps }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore.modalRecalculatingScore', + }); + + return ( + + + + ); +}; + +export default memo(ModalRecalculatingScore); diff --git a/client/src/components/Settings/SettingsRecalculatingScore/SettingsRecalculatingScore.tsx b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/RecalculatingScore.tsx similarity index 84% rename from client/src/components/Settings/SettingsRecalculatingScore/SettingsRecalculatingScore.tsx rename to client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/RecalculatingScore.tsx index e09a3339ff..c1954de503 100644 --- a/client/src/components/Settings/SettingsRecalculatingScore/SettingsRecalculatingScore.tsx +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/RecalculatingScore.tsx @@ -1,19 +1,19 @@ import React, { FC, useEffect, useMemo, useState } from 'react'; import { useAccount } from 'wagmi'; -import SettingsAddressScore from 'components/Settings/SettingsAddressScore'; -import SettingsProgressPath from 'components/Settings/SettingsProgressPath'; +import AddressScore from 'components/Home/HomeGridUQScore/AddressScore'; +import ProgressPath from 'components/Home/HomeGridUQScore/ProgressPath'; import { DELEGATION_MIN_SCORE } from 'constants/delegation'; import useRefreshAntisybilStatus from 'hooks/mutations/useRefreshAntisybilStatus'; import useAntisybilStatusScore from 'hooks/queries/useAntisybilStatusScore'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useUqScore from 'hooks/queries/useUqScore'; import useUserTOS from 'hooks/queries/useUserTOS'; -import useSettingsStore from 'store/settings/store'; +import useDelegationStore from 'store/delegation/store'; -import SettingsRecalculatingScoreProps from './types'; +import RecalculatingScoreProps from './types'; -const SettingsRecalculatingScore: FC = ({ onLastStepDone }) => { +const RecalculatingScore: FC = ({ onLastStepDone }) => { const { data: currentEpoch } = useCurrentEpoch(); const { address } = useAccount(); const { data: isUserTOSAccepted } = useUserTOS(); @@ -25,7 +25,7 @@ const SettingsRecalculatingScore: FC = ({ onLas setSecondaryAddressScore, isDelegationCompleted, delegationSecondaryAddress, - } = useSettingsStore(state => ({ + } = useDelegationStore(state => ({ delegationSecondaryAddress: state.data.delegationSecondaryAddress, isDelegationCompleted: state.data.isDelegationCompleted, setPrimaryAddressScore: state.setPrimaryAddressScore, @@ -56,10 +56,14 @@ const SettingsRecalculatingScore: FC = ({ onLas ) { return 0; } - if (!isDelegationCompleted && antisybilStatusScore < DELEGATION_MIN_SCORE && uqScore === 100n) { + if ( + !isDelegationCompleted && + antisybilStatusScore.score < DELEGATION_MIN_SCORE && + uqScore === 100n + ) { return DELEGATION_MIN_SCORE; } - return antisybilStatusScore; + return antisybilStatusScore.score; }, [antisybilStatusScore, uqScore, lastDoneStep, isDelegationCompleted, isErrorUqScore]); const scoreHighlight = lastDoneStep && lastDoneStep >= 1 ? 'black' : undefined; @@ -110,7 +114,7 @@ const SettingsRecalculatingScore: FC = ({ onLas return ( <> - = ({ onLas score={calculatedUqScore} scoreHighlight={scoreHighlight} /> - + ); }; -export default SettingsRecalculatingScore; +export default RecalculatingScore; diff --git a/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/index.tsx b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/index.tsx new file mode 100644 index 0000000000..05b1f8c13d --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './RecalculatingScore'; diff --git a/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/types.ts b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/types.ts new file mode 100644 index 0000000000..64f5c89c10 --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/RecalculatingScore/types.ts @@ -0,0 +1,3 @@ +export default interface RecalculatingScoreProps { + onLastStepDone: () => void; +} diff --git a/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/index.tsx b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/index.tsx new file mode 100644 index 0000000000..b5db14a56d --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ModalRecalculatingScore'; diff --git a/client/src/components/Earn/ModalEarnRewardsCalculator/types.ts b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/types.ts similarity index 65% rename from client/src/components/Earn/ModalEarnRewardsCalculator/types.ts rename to client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/types.ts index 04220a105c..7615d40575 100644 --- a/client/src/components/Earn/ModalEarnRewardsCalculator/types.ts +++ b/client/src/components/Home/HomeGridUQScore/ModalRecalculatingScore/types.ts @@ -1,5 +1,5 @@ import ModalProps from 'components/ui/Modal/types'; -export default interface ModalEarnRewardsCalculatorProps { +export default interface ModalRecalculatingScoreProps { modalProps: Omit; } diff --git a/client/src/components/Settings/SettingsProgressPath/SettingsProgressPath.module.scss b/client/src/components/Home/HomeGridUQScore/ProgressPath/ProgressPath.module.scss similarity index 100% rename from client/src/components/Settings/SettingsProgressPath/SettingsProgressPath.module.scss rename to client/src/components/Home/HomeGridUQScore/ProgressPath/ProgressPath.module.scss diff --git a/client/src/components/Settings/SettingsProgressPath/SettingsProgressPath.tsx b/client/src/components/Home/HomeGridUQScore/ProgressPath/ProgressPath.tsx similarity index 84% rename from client/src/components/Settings/SettingsProgressPath/SettingsProgressPath.tsx rename to client/src/components/Home/HomeGridUQScore/ProgressPath/ProgressPath.tsx index 0dd0001970..85edc6c1d3 100644 --- a/client/src/components/Settings/SettingsProgressPath/SettingsProgressPath.tsx +++ b/client/src/components/Home/HomeGridUQScore/ProgressPath/ProgressPath.tsx @@ -3,11 +3,13 @@ import { motion } from 'framer-motion'; import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; -import styles from './SettingsProgressPath.module.scss'; -import SettingsProgressPathProps from './types'; +import styles from './ProgressPath.module.scss'; +import ProgressPathProps from './types'; -const SettingsProgressPath: FC = ({ lastDoneStep }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); +const ProgressPath: FC = ({ lastDoneStep }) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.home.homeGridUQScore.progressPath', + }); const steps = [t('checkingPassportScore'), t('checkingAllowlist'), t('finished')]; return ( @@ -54,4 +56,4 @@ const SettingsProgressPath: FC = ({ lastDoneStep }) = ); }; -export default SettingsProgressPath; +export default ProgressPath; diff --git a/client/src/components/Home/HomeGridUQScore/ProgressPath/index.tsx b/client/src/components/Home/HomeGridUQScore/ProgressPath/index.tsx new file mode 100644 index 0000000000..204ba1a586 --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ProgressPath/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ProgressPath'; diff --git a/client/src/components/Home/HomeGridUQScore/ProgressPath/types.ts b/client/src/components/Home/HomeGridUQScore/ProgressPath/types.ts new file mode 100644 index 0000000000..f4980ed0bc --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/ProgressPath/types.ts @@ -0,0 +1,3 @@ +export default interface ProgressPathProps { + lastDoneStep: null | 0 | 1 | 2; +} diff --git a/client/src/components/Home/HomeGridUQScore/index.tsx b/client/src/components/Home/HomeGridUQScore/index.tsx new file mode 100644 index 0000000000..c98a2ffb08 --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeGridUQScore'; diff --git a/client/src/components/Home/HomeGridUQScore/types.ts b/client/src/components/Home/HomeGridUQScore/types.ts new file mode 100644 index 0000000000..369ea383ce --- /dev/null +++ b/client/src/components/Home/HomeGridUQScore/types.ts @@ -0,0 +1,3 @@ +export default interface HomeGridUQScoreProps { + className?: string; +} diff --git a/client/src/components/Home/HomeRewards/HomeRewards.module.scss b/client/src/components/Home/HomeRewards/HomeRewards.module.scss new file mode 100644 index 0000000000..5bba8f75ee --- /dev/null +++ b/client/src/components/Home/HomeRewards/HomeRewards.module.scss @@ -0,0 +1,96 @@ +.root { + display: flex; + width: 100%; + margin-bottom: 1.6rem; + + @media #{$tablet-up} { + gap: 1.6rem; + } + + .tile { + display: flex; + flex-direction: column; + justify-content: center; + flex: 1; + height: 7.2rem; + padding: 0 1.6rem; + background: $color-octant-grey8; + text-align: left; + font-weight: $font-weight-bold; + + &:first-child { + border-radius: $border-radius-16 0 0 $border-radius-16; + } + + &:last-child { + border-radius: 0 $border-radius-16 $border-radius-16 0; + } + + .label { + display: flex; + font-size: $font-size-10; + color: $color-octant-grey5; + margin-bottom: 0.2rem; + + .tooltipBox { + margin-left: 0.6rem; + + @media #{$tablet-up} { + margin-left: 1rem; + } + } + } + + .value { + font-size: $font-size-16; + color: $color-octant-dark; + + &.isLoadingValue { + @include skeleton($color-octant-grey1, $color-octant-grey12); + min-height: 1.95rem; + width: 8rem; + + @media #{$tablet-up} { + min-height: 2.2rem; + } + } + } + + @media #{$tablet-up} { + &, + &:first-child, + &:last-child { + border-radius: $border-radius-16; + } + + .label { + font-size: $font-size-14; + } + + .value { + font-size: $font-size-18; + } + } + } +} + +.svgWrapper { + width: 1.2rem; + height: 1.2rem; + + @media #{$tablet-up} { + width: 1.6rem; + height: 1.6rem; + } +} + +.tooltipWrapper { + right: 0; + left: auto; + width: 34.2rem; + top: auto; + + .tooltip { + position: initial; + } +} diff --git a/client/src/components/Home/HomeRewards/HomeRewards.tsx b/client/src/components/Home/HomeRewards/HomeRewards.tsx new file mode 100644 index 0000000000..c268355bf6 --- /dev/null +++ b/client/src/components/Home/HomeRewards/HomeRewards.tsx @@ -0,0 +1,199 @@ +import cx from 'classnames'; +import React, { ReactElement, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useAccount } from 'wagmi'; + +import Svg from 'components/ui/Svg'; +import Tooltip from 'components/ui/Tooltip'; +import useEpochPatronsAllEpochs from 'hooks/helpers/useEpochPatronsAllEpochs'; +import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; +import useIndividualRewardAllEpochs from 'hooks/helpers/useIndividualRewardAllEpochs'; +import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; +import useUserAllocationsAllEpochs from 'hooks/helpers/useUserAllocationsAllEpochs'; +import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; +import useIndividualReward from 'hooks/queries/useIndividualReward'; +import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import useIsPatronMode from 'hooks/queries/useIsPatronMode'; +import useMatchedProjectRewards from 'hooks/queries/useMatchedProjectRewards'; +import useRewardsRate from 'hooks/queries/useRewardsRate'; +import { questionMark } from 'svg/misc'; + +import styles from './HomeRewards.module.scss'; + +const HomeRewards = (): ReactElement => { + const { i18n, t } = useTranslation('translation', { keyPrefix: 'components.home.homeRewards' }); + const { address, isConnected } = useAccount(); + const { data: individualReward, isFetching: isFetchingIndividualReward } = useIndividualReward(); + const { data: individualRewardAllEpochs, isFetching: isFetchingIndividualRewardAllEpochs } = + useIndividualRewardAllEpochs(); + const { data: userAllocationsAllEpochs, isFetching: isFetchingUserAllAllocations } = + useUserAllocationsAllEpochs(); + const { data: epochPatronsAllEpochs, isFetching: isFetchingEpochPatronsAllEpochs } = + useEpochPatronsAllEpochs(); + const getValuesToDisplay = useGetValuesToDisplay(); + const isProjectAdminMode = useIsProjectAdminMode(); + const { data: isPatronMode } = useIsPatronMode(); + const { data: currentEpoch } = useCurrentEpoch(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + const { data: rewardsRate, isFetching: isFetchingRewardsRate } = useRewardsRate(currentEpoch!); + + const { isMobile } = useMediaQuery(); + + const { data: matchedProjectRewards, isFetching: isFetchingMatchedProjectRewards } = + useMatchedProjectRewards(isDecisionWindowOpen ? undefined : currentEpoch! - 1, { + enabled: isProjectAdminMode || isPatronMode, + }); + + const projectMatchedProjectRewards = isProjectAdminMode + ? matchedProjectRewards?.find( + ({ address: matchedProjectRewardsAddress }) => address === matchedProjectRewardsAddress, + ) + : undefined; + + const totalMatechProjectsRewards = + isProjectAdminMode || isPatronMode + ? matchedProjectRewards?.reduce((acc, { matched }) => acc + matched, 0n) + : undefined; + + // We count only rewards from epochs user did an action -- allocation or was a patron. + const totalRewards = individualRewardAllEpochs.reduce((acc, curr, currentIndex) => { + const hasUserAlreadyDoneAllocationInGivenEpoch = + userAllocationsAllEpochs[currentIndex]?.hasUserAlreadyDoneAllocation || false; + const wasPatronInGivenEpoch = + epochPatronsAllEpochs[currentIndex]?.includes(address as string) || false; + const wasBudgetEffective = hasUserAlreadyDoneAllocationInGivenEpoch || wasPatronInGivenEpoch; + + return wasBudgetEffective ? acc + curr : acc; + }, BigInt(0)); + + const currentRewardsToDisplay = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: individualReward, + }).primary; + + const totalRewardsToDisplay = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: totalRewards, + }).primary; + + const currentDonationsToDisplay = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: projectMatchedProjectRewards?.allocated, + }).primary; + + const currentMatchFundingToDisplay = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: projectMatchedProjectRewards?.matched, + }).primary; + + const epochTotalMatchFundingToDisplay = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: totalMatechProjectsRewards, + }).primary; + + const currentRewardsLabel = useMemo(() => { + if (isProjectAdminMode) { + if (isMobile) { + return i18n.t('common.donations'); + } + return t('currentDonations'); + } + return t('currentRewards'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isProjectAdminMode, isMobile]); + + const totalRewardsLabel = useMemo(() => { + if (isProjectAdminMode) { + if (isMobile) { + return i18n.t('common.matchFunding'); + } + return t('currentMatchFunding'); + } + return t('totalRewards'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isProjectAdminMode, isMobile]); + + const rewardsRateLabel = useMemo(() => { + if (isProjectAdminMode || isPatronMode) { + if (isMobile) { + return t('epochMF'); + } + return t('epochTotalMatchFunding'); + } + return t('rewardsRate'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isProjectAdminMode, isMobile, isPatronMode]); + + const tiles = [ + { + isLoadingValue: isProjectAdminMode + ? isFetchingMatchedProjectRewards + : isFetchingIndividualReward, + key: 'currentRewards', + label: currentRewardsLabel, + value: isProjectAdminMode ? currentDonationsToDisplay : currentRewardsToDisplay, + }, + { + isLoadingValue: + isConnected && + (isProjectAdminMode + ? isFetchingMatchedProjectRewards + : isFetchingIndividualRewardAllEpochs || + isFetchingUserAllAllocations || + isFetchingEpochPatronsAllEpochs), + key: 'totalRewards', + label: totalRewardsLabel, + value: isProjectAdminMode ? currentMatchFundingToDisplay : totalRewardsToDisplay, + }, + { + isLoadingValue: + isProjectAdminMode || isPatronMode + ? isFetchingMatchedProjectRewards + : isFetchingRewardsRate, + key: 'rewardsRate', + label: rewardsRateLabel, + tooltipText: t('rewardsRateTooltip'), + value: + isProjectAdminMode || isPatronMode ? epochTotalMatchFundingToDisplay : `${rewardsRate} %`, + }, + ]; + + return ( +
+ {tiles.map(({ label, value, key, isLoadingValue, tooltipText }) => ( +
+
+ {label} + {!isProjectAdminMode && !isPatronMode && tooltipText && ( + + + + )} +
+
+ {isLoadingValue ? null : value} +
+
+ ))} +
+ ); +}; + +export default HomeRewards; diff --git a/client/src/components/Home/HomeRewards/index.tsx b/client/src/components/Home/HomeRewards/index.tsx new file mode 100644 index 0000000000..6a2978becf --- /dev/null +++ b/client/src/components/Home/HomeRewards/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './HomeRewards'; diff --git a/client/src/components/Metrics/MetricsDonationsProgressBar/index.tsx b/client/src/components/Metrics/MetricsDonationsProgressBar/index.tsx deleted file mode 100644 index 4a343a8fe9..0000000000 --- a/client/src/components/Metrics/MetricsDonationsProgressBar/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsDonationsProgressBar'; diff --git a/client/src/components/Metrics/MetricsDonationsProgressBar/types.ts b/client/src/components/Metrics/MetricsDonationsProgressBar/types.ts deleted file mode 100644 index 9ba1096633..0000000000 --- a/client/src/components/Metrics/MetricsDonationsProgressBar/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default interface MetricsDonationsProgressBarProps { - donationsValue: number; - isDisabled?: boolean; - isLoading: boolean; -} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.module.scss b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.module.scss index 5ae2ca6865..96c2c82403 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.module.scss +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.module.scss @@ -1,67 +1,78 @@ .root { width: 100%; + border-top: 0.1rem solid $color-octant-grey1; + border-bottom: 0.1rem solid $color-octant-grey1; + padding-bottom: 6.4rem; } .grid { - grid-template-rows: 16rem 16rem minmax(14.4rem, auto); + grid-template-rows: repeat(10, 15.6rem); + + @media #{$tablet-up} { + grid-template-rows: repeat(6, 15.6rem); + } @media #{$desktop-up} { - grid-template-rows: 14.4rem 14.4rem minmax(14.4rem, auto); + grid-template-rows: repeat(4, 15.6rem); + } +} + +.topProjects, +.fundsUsage { + @media #{$tablet-down} { + order: 0; } } -.topProjects { +.totalDonations, +.totalMatching { @media #{$tablet-down} { order: 1; } } -.totalDonationsAndPersonal { + +.donationsVsMatching { @media #{$tablet-down} { order: 2; } } -.donationsVsPersonal { + +.totalUsers { @media #{$tablet-down} { order: 3; } } -.currentDonors { +.patrons { @media #{$tablet-down} { order: 4; } } -.averageLeverage { +.currentDonors { @media #{$tablet-down} { order: 5; } } -.totalUsers { +.averageLeverage { @media #{$tablet-down} { order: 6; } } -.patrons { + +.rewardsUnused { @media #{$tablet-down} { order: 7; } } - -.fundsUsage { +.unallocatedValue { @media #{$tablet-down} { order: 8; } } -.unusedAndUnallocatedValue { +.donationsVsPersonal { @media #{$tablet-down} { order: 9; } } - -.belowThreshold { - @media #{$tablet-down} { - order: 10; - } -} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx index 1f3358473e..e2a3a7c926 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpoch.tsx @@ -1,20 +1,21 @@ import React, { ReactElement } from 'react'; import MetricsEpochGridAverageLeverage from 'components/Metrics/MetricsEpoch/MetricsEpochGridAverageLeverage'; -import MetricsEpochGridBelowThreshold from 'components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold'; import MetricsEpochGridCurrentDonors from 'components/Metrics/MetricsEpoch/MetricsEpochGridCurrentDonors'; +import MetricsEpochGridDonationsVsMatching from 'components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching'; import MetricsEpochGridDonationsVsPersonalAllocations from 'components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations'; import MetricsEpochGridFundsUsage from 'components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage'; import MetricsEpochGridPatrons from 'components/Metrics/MetricsEpoch/MetricsEpochGridPatrons'; -import MetricsEpochGridRewardsUnusedAndUnallocatedValue from 'components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue'; +import MetricsEpochGridRewardsUnused from 'components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused'; import MetricsEpochGridTopProjects from 'components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects'; -import MetricsEpochGridTotalDonationsAndPersonal from 'components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal'; +import MetricsEpochGridTotalDonations from 'components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations'; +import MetricsEpochGridTotalMatchingFund from 'components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund'; import MetricsEpochGridTotalUsers from 'components/Metrics/MetricsEpoch/MetricsEpochGridTotalUsers'; +import MetricsEpochGridUnallocatedValue from 'components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue'; import MetricsEpochHeader from 'components/Metrics/MetricsEpoch/MetricsEpochHeader'; import MetricsGrid from 'components/Metrics/MetricsGrid'; import { METRICS_EPOCH_ID } from 'constants/domElementsIds'; import useMetricsEpoch from 'hooks/helpers/useMetrcisEpoch'; -import useProjectsDonors from 'hooks/queries/donors/useProjectsDonors'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useCurrentEpochEnd from 'hooks/queries/useCurrentEpochEnd'; import useCurrentEpochProps from 'hooks/queries/useCurrentEpochProps'; @@ -26,7 +27,6 @@ import useEpochPatrons from 'hooks/queries/useEpochPatrons'; import useEpochUnusedRewards from 'hooks/queries/useEpochUnusedRewards'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useMatchedProjectRewards from 'hooks/queries/useMatchedProjectRewards'; -import useProjectRewardsThreshold from 'hooks/queries/useProjectRewardsThreshold'; import useProjectsIpfsWithRewards from 'hooks/queries/useProjectsIpfsWithRewards'; import useEpochsStartEndTime from 'hooks/subgraph/useEpochsStartEndTime'; import useLargestLockedAmount from 'hooks/subgraph/useLargestLockedAmount'; @@ -47,14 +47,9 @@ const MetricsEpoch = (): ReactElement => { const { isFetching: isFetchingMatchedProjectRewards } = useMatchedProjectRewards( isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch, ); - const { isFetching: isFetchingProjectsIpfsWithRewards } = useProjectsIpfsWithRewards( - isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch, - ); - const { data: projectsDonors, isFetching: isFetchingProjectsDonors } = useProjectsDonors( - isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch, - ); - const { data: projectRewardsThreshold, isFetching: isFetchingProjectRewardsThreshold } = - useProjectRewardsThreshold(isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch); + const { data: projectsIpfsWithRewards, isFetching: isFetchingProjectsIpfsWithRewards } = + useProjectsIpfsWithRewards(isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch); + const { isFetching: isFetchingEpochLeverage } = useEpochLeverage(epoch); const { data: epochAllocations, isFetching: isFetchingEpochAllocations } = useEpochAllocations(epoch); @@ -64,21 +59,6 @@ const MetricsEpoch = (): ReactElement => { const { data: epochUnusedRewards, isFetching: isFetchingEpochUnusedRewards } = useEpochUnusedRewards(epoch); - const ethBelowThreshold = - projectRewardsThreshold === undefined || projectsDonors === undefined - ? BigInt(0) - : Object.values(projectsDonors).reduce((acc, curr) => { - const projectSumOfDonations = curr.reduce((acc2, curr2) => { - return acc2 + curr2.amount; - }, BigInt(0)); - - if (projectSumOfDonations < projectRewardsThreshold) { - return acc + projectSumOfDonations; - } - - return acc; - }, BigInt(0)); - const patronsRewards = epochInfo?.patronsRewards || BigInt(0); const sumOfDonations = epochAllocations?.reduce((acc, curr) => acc + curr.amount, BigInt(0)) || BigInt(0); @@ -87,6 +67,10 @@ const MetricsEpoch = (): ReactElement => { const epochBudget = epochBudgets?.budgetsSum || BigInt(0); const totalPersonal = epochBudget - totalUserDonationsWithPatronRewards - unusedRewards; + const matchedRewards = projectsIpfsWithRewards.reduce( + (acc, curr) => acc + curr.matchedRewards, + 0n, + ); // All metrics should be visible in the same moment (design). Skeletons are visible to the end of fetching all needed data. const isLoading = @@ -104,8 +88,6 @@ const MetricsEpoch = (): ReactElement => { isFetchingEpochUnusedRewards || isFetchingEpochBudgets || isFetchingMatchedProjectRewards || - isFetchingProjectsDonors || - isFetchingProjectRewardsThreshold || isFetchingEpochPatrons; return ( @@ -113,40 +95,43 @@ const MetricsEpoch = (): ReactElement => { - - + + + + - + - {epoch < 4 && ( - - )}
); diff --git a/client/src/components/Metrics/MetricsDonationsProgressBar/MetricsDonationsProgressBar.module.scss b/client/src/components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar/MetricsEpochDonationsProgressBar.module.scss similarity index 100% rename from client/src/components/Metrics/MetricsDonationsProgressBar/MetricsDonationsProgressBar.module.scss rename to client/src/components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar/MetricsEpochDonationsProgressBar.module.scss diff --git a/client/src/components/Metrics/MetricsDonationsProgressBar/MetricsDonationsProgressBar.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar/MetricsEpochDonationsProgressBar.tsx similarity index 82% rename from client/src/components/Metrics/MetricsDonationsProgressBar/MetricsDonationsProgressBar.tsx rename to client/src/components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar/MetricsEpochDonationsProgressBar.tsx index 1d80f63fad..4b3c353cf6 100644 --- a/client/src/components/Metrics/MetricsDonationsProgressBar/MetricsDonationsProgressBar.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar/MetricsEpochDonationsProgressBar.tsx @@ -5,13 +5,14 @@ import { useTranslation } from 'react-i18next'; import ProgressBar from 'components/ui/ProgressBar'; import { dotAndZeroes } from 'utils/regExp'; -import styles from './MetricsDonationsProgressBar.module.scss'; -import MetricsDonationsProgressBarProps from './types'; +import styles from './MetricsEpochDonationsProgressBar.module.scss'; +import MetricsEpochDonationsProgressBarProps from './types'; -const MetricsDonationsProgressBar: FC = ({ +const MetricsEpochDonationsProgressBar: FC = ({ isDisabled, isLoading, donationsValue, + compareValueLabel, }) => { const { i18n } = useTranslation('translation'); const donationsPercentage = isDisabled ? 0 : donationsValue.toFixed(2).replace(dotAndZeroes, ''); @@ -50,7 +51,7 @@ const MetricsDonationsProgressBar: FC = ({ ) : ( <>
{personalPercentage}%
-
{i18n.t('common.personal')}
+
{compareValueLabel}
)}
@@ -59,4 +60,4 @@ const MetricsDonationsProgressBar: FC = ({ ); }; -export default memo(MetricsDonationsProgressBar); +export default memo(MetricsEpochDonationsProgressBar); diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar/index.tsx new file mode 100644 index 0000000000..bfd1267498 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './MetricsEpochDonationsProgressBar'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar/types.ts new file mode 100644 index 0000000000..c802f131da --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar/types.ts @@ -0,0 +1,6 @@ +export default interface MetricsEpochDonationsProgressBarProps { + compareValueLabel: string; + donationsValue: number; + isDisabled?: boolean; + isLoading: boolean; +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold/MetricsEpochGridBelowThreshold.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold/MetricsEpochGridBelowThreshold.tsx deleted file mode 100644 index cd7cc2c3e6..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold/MetricsEpochGridBelowThreshold.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; -import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue'; -import useMetricsEpoch from 'hooks/helpers/useMetrcisEpoch'; -import useCryptoValues from 'hooks/queries/useCryptoValues'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; -import useMatchedProjectRewards from 'hooks/queries/useMatchedProjectRewards'; -import useProjectsEpoch from 'hooks/queries/useProjectsEpoch'; -import i18n from 'i18n'; -import useSettingsStore from 'store/settings/store'; -import getValueCryptoToDisplay from 'utils/getValueCryptoToDisplay'; -import getValueFiatToDisplay from 'utils/getValueFiatToDisplay'; - -import MetricsEpochGridBelowThresholdProps from './types'; - -const MetricsEpochGridBelowThreshold: FC = ({ - isLoading, - className, - ethBelowThreshold, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - const { epoch, lastEpoch } = useMetricsEpoch(); - const { data: projectsEpoch } = useProjectsEpoch(epoch); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); - const { data: matchedProjectRewards } = useMatchedProjectRewards( - isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch, - ); - const { - data: { displayCurrency }, - } = useSettingsStore(({ data }) => ({ - data: { - displayCurrency: data.displayCurrency, - }, - })); - const { data: cryptoValues, error } = useCryptoValues(displayCurrency); - - const projectsBelowThreshold = - (projectsEpoch?.projectsAddresses.length || 0) - - (matchedProjectRewards?.filter(({ matched }) => matched !== 0n).length || 0); - - const ethBelowThresholdToDisplay = getValueCryptoToDisplay({ - cryptoCurrency: 'ethereum', - valueCrypto: ethBelowThreshold, - }); - - const ethBelowThresholdFiatToDisplay = getValueFiatToDisplay({ - cryptoCurrency: 'ethereum', - cryptoValues, - displayCurrency, - error, - valueCrypto: ethBelowThreshold, - }); - - return ( - - ), - title: t('belowThreshold'), - }, - { - children: ( - - ), - title: t('ethBelowThreshold'), - }, - ]} - size="M" - /> - ); -}; - -export default MetricsEpochGridBelowThreshold; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold/index.tsx deleted file mode 100644 index 9a98c4c677..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsEpochGridBelowThreshold'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold/types.ts deleted file mode 100644 index e4c7bf8724..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridBelowThreshold/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default interface MetricsEpochGridBelowThresholdProps { - className?: string; - ethBelowThreshold: bigint; - isLoading: boolean; -} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/MetricsEpochGridDonationsVsMatching.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/MetricsEpochGridDonationsVsMatching.tsx new file mode 100644 index 0000000000..b372d48cb7 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/MetricsEpochGridDonationsVsMatching.tsx @@ -0,0 +1,46 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import MetricsEpochDonationsProgressBar from 'components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar'; +import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; +import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; + +import MetricsEpochGridDonationsVsMatchingProps from './types'; + +const MetricsEpochGridDonationsVsMatching: FC = ({ + totalUserDonations, + isLoading, + matchingFund, + className, +}) => { + const { i18n, t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); + + const totalUserDonationsNumber = parseFloat(formatUnitsBigInt(totalUserDonations)); + const matchingFundNumber = parseFloat(formatUnitsBigInt(matchingFund)); + + const donationsValue = + totalUserDonationsNumber > 0 + ? (totalUserDonationsNumber / (matchingFundNumber + totalUserDonationsNumber)) * 100 + : 0; + + return ( + + ), + title: t('donationsVsMatchFunding'), + }, + ]} + /> + ); +}; + +export default MetricsEpochGridDonationsVsMatching; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/index.tsx new file mode 100644 index 0000000000..c50555aba1 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './MetricsEpochGridDonationsVsMatching'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/types.ts new file mode 100644 index 0000000000..0eba7fbf13 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsMatching/types.ts @@ -0,0 +1,6 @@ +export default interface MetricsEpochGridDonationsVsMatchingProps { + className?: string; + isLoading: boolean; + matchingFund: bigint; + totalUserDonations: bigint; +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/MetricsEpochGridDonationsVsPersonalAllocations.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/MetricsEpochGridDonationsVsPersonalAllocations.tsx index 1ea059bfc3..58e3ef2d4b 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/MetricsEpochGridDonationsVsPersonalAllocations.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/MetricsEpochGridDonationsVsPersonalAllocations.tsx @@ -1,7 +1,7 @@ import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; -import MetricsDonationsProgressBar from 'components/Metrics/MetricsDonationsProgressBar'; +import MetricsEpochDonationsProgressBar from 'components/Metrics/MetricsEpoch/MetricsEpochDonationsProgressBar'; import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; @@ -9,19 +9,15 @@ import MetricsEpochGridDonationsVsPersonalAllocationsProps from './types'; const MetricsEpochGridDonationsVsPersonalAllocations: FC< MetricsEpochGridDonationsVsPersonalAllocationsProps -> = ({ totalUserDonationsWithPatronRewards, isLoading, totalPersonal, className }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); +> = ({ totalUserDonations, isLoading, totalPersonal, className }) => { + const { i18n, t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - const totalUserDonationWithPatronRewardsNumber = parseFloat( - formatUnitsBigInt(totalUserDonationsWithPatronRewards), - ); + const totalUserDonationsNumber = parseFloat(formatUnitsBigInt(totalUserDonations)); const totalPersonalNumber = parseFloat(formatUnitsBigInt(totalPersonal)); const donationsValue = - totalUserDonationWithPatronRewardsNumber > 0 - ? (totalUserDonationWithPatronRewardsNumber / - (totalPersonalNumber + totalUserDonationWithPatronRewardsNumber)) * - 100 + totalUserDonationsNumber > 0 + ? (totalUserDonationsNumber / (totalPersonalNumber + totalUserDonationsNumber)) * 100 : 0; return ( @@ -31,9 +27,13 @@ const MetricsEpochGridDonationsVsPersonalAllocations: FC< groups={[ { children: ( - + ), - title: t('donationsVsPersonalAllocationValue'), + title: t('donationsVsPersonal'), }, ]} /> diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/types.ts index 0fd229fde9..7885c3098b 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/types.ts +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridDonationsVsPersonalAllocations/types.ts @@ -2,5 +2,5 @@ export default interface MetricsEpochGridDonationsVsPersonalAllocationsProps { className?: string; isLoading: boolean; totalPersonal: bigint; - totalUserDonationsWithPatronRewards: bigint; + totalUserDonations: bigint; } diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/MetricsEpochGridFundsUsage.module.scss b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/MetricsEpochGridFundsUsage.module.scss index ecd32715e2..20cf670465 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/MetricsEpochGridFundsUsage.module.scss +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/MetricsEpochGridFundsUsage.module.scss @@ -3,11 +3,15 @@ } .pieChartWrapper { + display: flex; + align-items: center; + justify-content: center; + flex: 1; margin: 0 auto 0; } .epochTotal { - height: 4rem; + height: 3.2rem; border-radius: $border-radius-08; background-color: $color-octant-grey6; color: $color-octant-grey5; @@ -16,8 +20,8 @@ display: flex; align-items: center; justify-content: space-between; - padding: 0 1.6rem; - margin: auto 2.4rem; + padding: 0 0.8rem; + margin: auto 2.4rem 2.4rem; .label, .value { @@ -25,6 +29,10 @@ min-height: 1.6rem; } + .label { + text-align: left; + } + .value { text-align: right; } diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/MetricsEpochGridFundsUsage.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/MetricsEpochGridFundsUsage.tsx index 35c4949b9a..70bd7299eb 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/MetricsEpochGridFundsUsage.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/MetricsEpochGridFundsUsage.tsx @@ -18,7 +18,6 @@ const MetricsEpochGridFundsUsage: FC = ({ className, totalUserDonationsWithPatronRewards, unusedRewards, - ethBelowThreshold, }) => { const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); const { epoch } = useMetricsEpoch(); @@ -30,16 +29,11 @@ const MetricsEpochGridFundsUsage: FC = ({ const leftover = epochInfo ? epochInfo.leftover : BigInt(0); const projectCosts = epochInfo ? epochInfo.operationalCost : BigInt(0); + const donatedToProjects = epochInfo ? epochInfo.donatedToProjects : BigInt(0); const staking = epochInfo ? epochInfo.staking : BigInt(0); const ppf = epochInfo ? epochInfo.ppf : BigInt(0); const communityFund = epochInfo ? epochInfo.communityFund : BigInt(0); - const donatedToProjects = epochInfo - ? epochInfo.matchedRewards + - (totalUserDonationsWithPatronRewards - epochInfo.patronsRewards) - - ethBelowThreshold - : BigInt(0); - const claimedByUsers = useMemo(() => { if (!epochInfo) { return BigInt(0); diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/types.ts index b52432b763..fe3f173335 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/types.ts +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridFundsUsage/types.ts @@ -1,6 +1,5 @@ export default interface MetricsEpochGridFundsUsageProps { className?: string; - ethBelowThreshold: bigint; isLoading: boolean; totalUserDonationsWithPatronRewards: bigint; unusedRewards: bigint; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/MetricsEpochGridRewardsUnused.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/MetricsEpochGridRewardsUnused.tsx new file mode 100644 index 0000000000..f978ad07e8 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/MetricsEpochGridRewardsUnused.tsx @@ -0,0 +1,44 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; +import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue'; +import useMetricsEpoch from 'hooks/helpers/useMetrcisEpoch'; +import useEpochUnusedRewards from 'hooks/queries/useEpochUnusedRewards'; + +import MetricsEpochGridRewardsUnusedProps from './types'; + +const MetricsEpochGridRewardsUnused: FC = ({ + isLoading, + className, +}) => { + const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); + + const { epoch } = useMetricsEpoch(); + const { data: epochUnusedRewards } = useEpochUnusedRewards(epoch); + + const users = `${epochUnusedRewards?.addresses.length || 0}`; + + return ( + + ), + title: t('rewardsUnused'), + }, + ]} + size="S" + /> + ); +}; + +export default MetricsEpochGridRewardsUnused; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/index.tsx new file mode 100644 index 0000000000..4cc5e7d676 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './MetricsEpochGridRewardsUnused'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/types.ts new file mode 100644 index 0000000000..f336df0205 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnused/types.ts @@ -0,0 +1,4 @@ +export default interface MetricsEpochGridRewardsUnusedProps { + className?: string; + isLoading: boolean; +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.module.scss b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.module.scss deleted file mode 100644 index 0e1300bc9e..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -.root { - @media #{$tablet-down} { - order: 9; - } -} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/index.tsx deleted file mode 100644 index 6f1fdab2ea..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsEpochGridRewardsUnusedAndUnallocatedValue'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/types.ts deleted file mode 100644 index 4f8935856e..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default interface MetricsEpochGridRewardsUnusedAndUnallocatedValueProps { - className?: string; - isLoading: boolean; -} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.module.scss b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.module.scss index fa09389653..14aa077378 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.module.scss +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.module.scss @@ -1,12 +1,17 @@ .root { - max-height: 62.4rem; grid-row: span 2; grid-column: span 2; - height: 33.6rem; + height: 32.8rem; - @media #{$desktop-up} { - height: 62.4rem; + @media #{$tablet-up} { + height: 67.2rem; grid-row: span 4; + grid-column: span 2; + } + + @media #{$large-desktop-up} { + grid-row: span 4; + grid-column: span 4; } } @@ -39,3 +44,17 @@ height: 100%; } } + +.headers { + color: $color-octant-grey5; + font-weight: $font-weight-bold; + display: grid; + grid-template-columns: 8rem 11.8rem 11.8rem 11.8rem; + margin-left: 10rem; + font-size: $font-size-12; + text-align: left; + + .label { + margin-left: 0.2rem; + } +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.tsx index 14d1e4eb6b..7c0ad425e1 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTopProjects/MetricsEpochGridTopProjects.tsx @@ -16,24 +16,19 @@ const MetricsEpochGridTopProjects: FC = ({ isLoading, className, }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); + const { i18n, t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); const { epoch, lastEpoch } = useMetricsEpoch(); - const { isDesktop } = useMediaQuery(); + const { isLargeDesktop } = useMediaQuery(); const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { data: projectsIpfsWithRewards } = useProjectsIpfsWithRewards( isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch, ); - const numberOfProjects = isDesktop ? 10 : 5; - const projects = - projectsIpfsWithRewards - .slice(0, numberOfProjects) - .map(({ totalValueOfAllocations, ...rest }) => ({ - epoch, - value: totalValueOfAllocations!, - ...rest, - })) || []; + projectsIpfsWithRewards.map(props => ({ + epoch, + ...props, + })) || []; return ( = ({ ), - title: t('topProjectsByEthRaised', { numberOfProjects }), + title: t('fundingLeaderboard'), + titleSuffix: isLargeDesktop ? ( +
+
{i18n.t('common.donors')}
+
{i18n.t('common.donations')}
+
{i18n.t('common.matchFunding')}
+
{i18n.t('common.total')}
+
+ ) : null, }, ]} size="custom" diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/MetricsEpochGridTotalDonations.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/MetricsEpochGridTotalDonations.tsx new file mode 100644 index 0000000000..e0eaeea362 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/MetricsEpochGridTotalDonations.tsx @@ -0,0 +1,48 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; +import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue'; +import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; + +import MetricsEpochGridTotalDonationsProps from './types'; + +const MetricsEpochGridTotalDonations: FC = ({ + isLoading, + totalUserDonations, + className, +}) => { + const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); + + const getValuesToDisplay = useGetValuesToDisplay(); + + const totalUserDonationsValues = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: totalUserDonations, + }); + + return ( + + ), + title: t('totalDonations'), + }, + ]} + size="S" + /> + ); +}; + +export default MetricsEpochGridTotalDonations; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/index.tsx new file mode 100644 index 0000000000..e13f894fcf --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './MetricsEpochGridTotalDonations'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/types.ts new file mode 100644 index 0000000000..8cc8cc1510 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonations/types.ts @@ -0,0 +1,5 @@ +export default interface MetricsEpochGridTotalDonationsProps { + className?: string; + isLoading: boolean; + totalUserDonations: bigint; +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.module.scss b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.module.scss deleted file mode 100644 index 79195eec67..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -.root { - @media #{$tablet-down} { - order: 2; - } -} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.tsx deleted file mode 100644 index 5c4363a6aa..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/MetricsEpochGridTotalDonationsAndPersonal.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; -import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue'; -import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; - -import MetricsEpochGridTotalDonationsAndPersonalProps from './types'; - -const MetricsEpochGridTotalDonationsAndPersonal: FC< - MetricsEpochGridTotalDonationsAndPersonalProps -> = ({ isLoading, totalUserDonationsWithPatronRewards, totalPersonal, className }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - - const getValuesToDisplay = useGetValuesToDisplay(); - - const totalUserDonationWithPatronRewardsValues = getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - valueCrypto: totalUserDonationsWithPatronRewards, - }); - - const totalPersonalValues = getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - valueCrypto: totalPersonal, - }); - - return ( - - ), - title: t('totalDonations'), - }, - { - children: ( - - ), - title: t('totalPersonal'), - }, - ]} - size="M" - /> - ); -}; - -export default MetricsEpochGridTotalDonationsAndPersonal; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/index.tsx deleted file mode 100644 index 3d73911729..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsEpochGridTotalDonationsAndPersonal'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/types.ts deleted file mode 100644 index 0b5125075c..0000000000 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalDonationsAndPersonal/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface MetricsEpochGridTotalDonationsAndPersonalProps { - className?: string; - isLoading: boolean; - totalPersonal: bigint; - totalUserDonationsWithPatronRewards: bigint; -} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/MetricsEpochGridTotalMatchingFund.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/MetricsEpochGridTotalMatchingFund.tsx new file mode 100644 index 0000000000..026cdf4bff --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/MetricsEpochGridTotalMatchingFund.tsx @@ -0,0 +1,47 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; + +import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; +import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue'; +import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; + +import MetricsEpochGridTotalMatchingFundProps from './types'; + +const MetricsEpochGridTotalMatchingFund: FC = ({ + isLoading, + matchingFund, + className, +}) => { + const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); + + const getValuesToDisplay = useGetValuesToDisplay(); + + const totalMatchingFundToDisplay = getValuesToDisplay({ + cryptoCurrency: 'ethereum', + showCryptoSuffix: true, + valueCrypto: matchingFund, + }); + + return ( + + ), + title: t('totalMatching'), + }, + ]} + size="S" + /> + ); +}; + +export default MetricsEpochGridTotalMatchingFund; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/index.tsx new file mode 100644 index 0000000000..1225ad454e --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './MetricsEpochGridTotalMatchingFund'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/types.ts new file mode 100644 index 0000000000..75a6cf2a10 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridTotalMatchingFund/types.ts @@ -0,0 +1,5 @@ +export default interface MetricsEpochGridTotalMatchingFundProps { + className?: string; + isLoading: boolean; + matchingFund: bigint; +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/MetricsEpochGridUnallocatedValue.tsx similarity index 60% rename from client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.tsx rename to client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/MetricsEpochGridUnallocatedValue.tsx index 67e354e70d..6828b7f40b 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridRewardsUnusedAndUnallocatedValue/MetricsEpochGridRewardsUnusedAndUnallocatedValue.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/MetricsEpochGridUnallocatedValue.tsx @@ -7,18 +7,18 @@ import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; import useMetricsEpoch from 'hooks/helpers/useMetrcisEpoch'; import useEpochUnusedRewards from 'hooks/queries/useEpochUnusedRewards'; -import MetricsEpochGridRewardsUnusedAndUnallocatedValueProps from './types'; +import MetricsEpochGridUnallocatedValueProps from './types'; -const MetricsEpochGridRewardsUnusedAndUnallocatedValue: FC< - MetricsEpochGridRewardsUnusedAndUnallocatedValueProps -> = ({ isLoading, className }) => { +const MetricsEpochGridUnallocatedValue: FC = ({ + isLoading, + className, +}) => { const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); const { epoch } = useMetricsEpoch(); const { data: epochUnusedRewards } = useEpochUnusedRewards(epoch); const getValuesToDisplay = useGetValuesToDisplay(); - const users = `${epochUnusedRewards?.addresses.length || 0}`; const unallocatedValue = getValuesToDisplay({ cryptoCurrency: 'ethereum', showCryptoSuffix: true, @@ -28,23 +28,12 @@ const MetricsEpochGridRewardsUnusedAndUnallocatedValue: FC< return ( - ), - title: t('rewardsUnused'), - }, - { - children: ( - ); }; -export default MetricsEpochGridRewardsUnusedAndUnallocatedValue; +export default MetricsEpochGridUnallocatedValue; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/index.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/index.tsx new file mode 100644 index 0000000000..450946b735 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './MetricsEpochGridUnallocatedValue'; diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/types.ts b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/types.ts new file mode 100644 index 0000000000..2863cb8882 --- /dev/null +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochGridUnallocatedValue/types.ts @@ -0,0 +1,4 @@ +export default interface MetricsEpochGridUnallocatedValueProps { + className?: string; + isLoading: boolean; +} diff --git a/client/src/components/Metrics/MetricsEpoch/MetricsEpochHeader/MetricsEpochHeader.tsx b/client/src/components/Metrics/MetricsEpoch/MetricsEpochHeader/MetricsEpochHeader.tsx index 0eb9ae040a..006ee3aed6 100644 --- a/client/src/components/Metrics/MetricsEpoch/MetricsEpochHeader/MetricsEpochHeader.tsx +++ b/client/src/components/Metrics/MetricsEpoch/MetricsEpochHeader/MetricsEpochHeader.tsx @@ -2,7 +2,7 @@ import cx from 'classnames'; import React, { ReactElement } from 'react'; import { useTranslation } from 'react-i18next'; -import MetricsHeader from 'components/Metrics/MetricsHeader'; +import MetricsSectionHeader from 'components/Metrics/MetricsSectionHeader'; import Svg from 'components/ui/Svg'; import useEpochDurationLabel from 'hooks/helpers/useEpochDurationLabel'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; @@ -25,7 +25,9 @@ const MetricsEpochHeader = (): ReactElement => { const isLeftArrowDisabled = epoch < 2; return ( - +
{isCurrentOpenEpoch ? (
{t('open')}
@@ -57,7 +59,7 @@ const MetricsEpochHeader = (): ReactElement => {
- + ); }; diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.module.scss b/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.module.scss index 5733eab620..22d1c0e201 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.module.scss +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.module.scss @@ -8,3 +8,15 @@ background-color: $color-octant-grey1; margin: 3.2rem 0; } + +.grid { + grid-template-rows: repeat(6, 15.6rem); + + @media #{$tablet-up} { + grid-template-rows: repeat(3, 15.6rem); + } + + @media #{$desktop-up} { + grid-template-rows: repeat(2, 15.6rem); + } +} diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.tsx b/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.tsx index 6db348e220..23328544ba 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.tsx +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneral.tsx @@ -7,7 +7,7 @@ import MetricsGeneralGridTotalGlmLockedAndTotalSupply from 'components/Metrics/M import MetricsGeneralGridTotalProjects from 'components/Metrics/MetricsGeneral/MetricsGeneralGridTotalProjects'; import MetricsGeneralGridWalletsWithGlmLocked from 'components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked'; import MetricsGrid from 'components/Metrics/MetricsGrid'; -import MetricsHeader from 'components/Metrics/MetricsHeader'; +import MetricsSectionHeader from 'components/Metrics/MetricsSectionHeader'; import { METRICS_GENERAL_ID } from 'constants/domElementsIds'; import useCryptoValues from 'hooks/queries/useCryptoValues'; import useProjectsEpoch from 'hooks/queries/useProjectsEpoch'; @@ -44,14 +44,13 @@ const MetricsGeneral = (): ReactElement => { return (
-
- - + + + + - -
); diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.module.scss b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.module.scss index 81808e7777..654d7e9d6f 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.module.scss +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.module.scss @@ -1,6 +1,9 @@ .root { - @media #{$tablet-down} { - order: 2; + grid-column: span 2; + grid-row: span 2; + + @media #{$large-desktop-up} { + grid-column: span 3; } } diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.tsx b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.tsx index dfcbea0bf1..e3b943f57a 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.tsx +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridCumulativeGlmLocked/MetricsGeneralGridCumulativeGlmLocked.tsx @@ -57,11 +57,12 @@ const MetricsGeneralGridCumulativeGlmLocked: FC = () => { />
), + hasTitleLargeBottomPadding: true, title: t('cumulativeGlmLocked'), titleSuffix: , }, ]} - size="L" + size="custom" /> ); }; diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.module.scss b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.module.scss index 5fc4f55671..654d7e9d6f 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.module.scss +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.module.scss @@ -1,6 +1,9 @@ .root { - @media #{$tablet-down} { - order: 4; + grid-column: span 2; + grid-row: span 2; + + @media #{$large-desktop-up} { + grid-column: span 3; } } diff --git a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.tsx b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.tsx index 93d000b0cb..1475f8863f 100644 --- a/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.tsx +++ b/client/src/components/Metrics/MetricsGeneral/MetricsGeneralGridWalletsWithGlmLocked/MetricsGeneralGridWalletsWithGlmLocked.tsx @@ -47,11 +47,12 @@ const MetricsGeneralGridWalletsWithGlmLocked: FC = () => { />
), + hasTitleLargeBottomPadding: true, title: t('walletsWithGlmLocked'), titleSuffix: , }, ]} - size="L" + size="custom" /> ); }; diff --git a/client/src/components/Metrics/MetricsGrid/MetricsGrid.module.scss b/client/src/components/Metrics/MetricsGrid/MetricsGrid.module.scss index 2246197510..db7f69583c 100644 --- a/client/src/components/Metrics/MetricsGrid/MetricsGrid.module.scss +++ b/client/src/components/Metrics/MetricsGrid/MetricsGrid.module.scss @@ -6,7 +6,15 @@ $gridGap: 1.6rem; gap: $gridGap; width: 100%; + @media #{$tablet-up} { + grid-template-columns: repeat(4, calc(calc(100% / 4) - calc(3 * $gridGap / 4))); + } + @media #{$desktop-up} { - grid-template-columns: 16.3rem 16.3rem 16.3rem 16.3rem; + grid-template-columns: repeat(6, calc(calc(100% / 6) - calc(5 * $gridGap / 6))); + } + + @media #{$large-desktop-up} { + grid-template-columns: repeat(8, 16.3rem); } } diff --git a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.module.scss b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.module.scss index 7975c6847a..867fae08a4 100644 --- a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.module.scss +++ b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.module.scss @@ -6,22 +6,21 @@ &.size { &--S { - height: 14.4rem; grid-column: span 1; grid-row: span 1; + height: 100%; } &--M { - height: 14.4rem; + height: 100%; grid-column: span 2; grid-row: span 1; } &--L { - min-height: 30.4rem; + height: 100%; grid-column: span 2; grid-row: span 2; - max-height: 50.4rem; } } } @@ -46,25 +45,27 @@ } .groupTitleWrapper { - padding: 1.2rem 1.6rem 0 2.4rem; + padding: 1.6rem; display: flex; align-items: center; margin: 0; - &.hasTitileBottomPadding { - padding-bottom: 1.2rem; + &.hasTitleLargeBottomPadding { + padding-bottom: 3.6rem; } .title { text-align: left; - font-size: $font-size-10; + font-size: $font-size-12; color: $color-octant-green; - text-transform: uppercase; font-weight: $font-weight-bold; letter-spacing: 0.03rem; - height: 3.2rem; + height: 2.4rem; align-items: center; display: flex; + border-radius: $border-radius-08; + background: $color-octant-grey6; + padding: 0 0.6rem; } } } diff --git a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.tsx b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.tsx index 52c15f835e..3c25d042d3 100644 --- a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.tsx +++ b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/MetricsGridTile.tsx @@ -17,7 +17,7 @@ const MetricsGridTile: FC = ({
diff --git a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/types.ts b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/types.ts index 69ba6c7a1d..5ebe6a9ced 100644 --- a/client/src/components/Metrics/MetricsGrid/MetricsGridTile/types.ts +++ b/client/src/components/Metrics/MetricsGrid/MetricsGridTile/types.ts @@ -5,7 +5,7 @@ export type MetricsGridTileSizes = (typeof METRICS_GRID_TILE_SZIES)[number]; type MetricsGridTileGroup = { children: ReactNode; - hasTitileBottomPadding?: boolean; + hasTitleLargeBottomPadding?: boolean; title: string; titleSuffix?: string | ReactNode; }; diff --git a/client/src/components/Metrics/MetricsGrid/MetricsGridTileTimeSlicer/MetricsGridTileTimeSlicer.module.scss b/client/src/components/Metrics/MetricsGrid/MetricsGridTileTimeSlicer/MetricsGridTileTimeSlicer.module.scss index d51b03f134..7f8bbaea88 100644 --- a/client/src/components/Metrics/MetricsGrid/MetricsGridTileTimeSlicer/MetricsGridTileTimeSlicer.module.scss +++ b/client/src/components/Metrics/MetricsGrid/MetricsGridTileTimeSlicer/MetricsGridTileTimeSlicer.module.scss @@ -8,6 +8,7 @@ padding: 0.4rem; justify-content: space-between; align-items: center; + margin-top: -0.4rem; .item { display: flex; diff --git a/client/src/components/Metrics/MetricsHeader/MetricsHeader.module.scss b/client/src/components/Metrics/MetricsHeader/MetricsHeader.module.scss deleted file mode 100644 index 9274ea8500..0000000000 --- a/client/src/components/Metrics/MetricsHeader/MetricsHeader.module.scss +++ /dev/null @@ -1,14 +0,0 @@ -.root { - height: 6.4rem; - margin: 1.6rem 0; -} - -.boxChildrenWrapper { - justify-content: left; -} - -.title { - font-size: $font-size-16; - color: $color-octant-dark; - font-weight: $font-weight-bold; -} diff --git a/client/src/components/Metrics/MetricsHeader/MetricsHeader.tsx b/client/src/components/Metrics/MetricsHeader/MetricsHeader.tsx deleted file mode 100644 index f77090f9a1..0000000000 --- a/client/src/components/Metrics/MetricsHeader/MetricsHeader.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { FC } from 'react'; - -import BoxRounded from 'components/ui/BoxRounded'; - -import styles from './MetricsHeader.module.scss'; -import MetricsHeaderProps from './types'; - -const MetricsHeader: FC = ({ title, dataTest = 'MetricsHeader', children }) => { - return ( - -
{title}
- {children} -
- ); -}; - -export default MetricsHeader; diff --git a/client/src/components/Metrics/MetricsNavigation/MetricsNavigation.module.scss b/client/src/components/Metrics/MetricsNavigation/MetricsNavigation.module.scss deleted file mode 100644 index 203c19555f..0000000000 --- a/client/src/components/Metrics/MetricsNavigation/MetricsNavigation.module.scss +++ /dev/null @@ -1,145 +0,0 @@ -.root { - height: 6.4rem; - width: 100%; - border-radius: $border-radius-16; - background-color: $color-octant-grey8; - padding: 0.8rem 0.4rem; - display: flex; - position: sticky; - top: 1.6rem; - margin-bottom: 1.6rem; - z-index: $z-index-5; - - .box { - width: 100%; - display: flex; - } - - .circleSvg { - position: absolute; - } - - .circle { - transition: all $transition-time-1; - stroke-width: 0.4rem; - fill: transparent; - stroke: $color-octant-grey2; - stroke-linejoin: 'round'; - } - - .smallDotsWrapper { - $smallDotsWrapperTop: 2.6rem; - - position: absolute; - display: flex; - flex-direction: column; - justify-content: space-between; - height: 7.6rem; - top: $smallDotsWrapperTop; - - &.transformDots { - top: calc(50% + $smallDotsWrapperTop); - transform: translate(0, calc(-50% + $smallDotsWrapperTop)); - } - } - - .smallDot { - width: 0.4rem; - height: 0.4rem; - border-radius: 100%; - background-color: $color-octant-grey1; - - &.isActive { - background-color: $color-octant-green; - } - } - - @media #{$large-desktop-up} { - padding: 0; - margin: 0; - position: fixed; - height: 23.2rem; - width: 8.8rem; - right: calc(50% + 48rem); - top: 13.4rem; - background-color: transparent; - - .box { - flex-direction: column; - height: 100%; - width: 2.4rem; - background-color: $color-white; - border-radius: $border-radius-16; - - .sectionStep { - margin: 0; - - &:first-child { - .label, - .circleSvg { - top: 0.4rem; - } - } - - &:last-child { - .label, - .circleSvg { - bottom: 0.4rem; - } - - .smallDotsWrapper { - top: auto; - bottom: 2.6rem; - } - } - - .label { - transition: all $transition-time-1; - position: absolute; - left: calc(100% + 0.8rem); - &:hover { - color: $color-octant-grey11; - } - } - - &.isActive { - .label { - color: $color-octant-green; - } - .circle { - stroke: $color-octant-green; - } - } - } - } - } - - .sectionStep { - position: relative; - background-color: transparent; - color: $color-octant-grey5; - font-size: $font-size-14; - font-weight: $font-weight-bold; - display: flex; - align-items: center; - justify-content: center; - flex: 1; - margin: 0 0.4rem; - cursor: pointer; - transition: all 0.3s; - - &.isActive { - color: $color-octant-dark; - } - } - - .isActiveOverlay { - position: absolute; - background-color: $color-white; - color: $color-octant-dark; - width: 100%; - height: 100%; - border-radius: $border-radius-12; - z-index: -1; - } -} diff --git a/client/src/components/Metrics/MetricsNavigation/MetricsNavigation.tsx b/client/src/components/Metrics/MetricsNavigation/MetricsNavigation.tsx deleted file mode 100644 index f0fe2432fe..0000000000 --- a/client/src/components/Metrics/MetricsNavigation/MetricsNavigation.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import cx from 'classnames'; -import { motion } from 'framer-motion'; -import { throttle } from 'lodash'; -import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -import { - LAYOUT_BODY_ID, - METRICS_EPOCH_ID, - METRICS_GENERAL_ID, - METRICS_PERSONAL_ID, -} from 'constants/domElementsIds'; -import useMediaQuery from 'hooks/helpers/useMediaQuery'; - -import styles from './MetricsNavigation.module.scss'; -import { ActiveSection } from './types'; - -const MetricsNavigation = (): ReactElement => { - const ref = useRef(null); - const { i18n, t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - const { isLargeDesktop } = useMediaQuery(); - const forcedSectionRef = useRef(null); - const [activeSection, setActiveSection] = useState('epoch'); - const [activeDot, setActiveDot] = useState(1); - const throttleCallback = useCallback(throttle, []); - const numberOfDots = 8; - - const steps = [ - { - label: t('epoch'), - section: 'epoch', - sectionId: METRICS_EPOCH_ID, - }, - { - label: t('overview'), - section: 'general', - sectionId: METRICS_GENERAL_ID, - }, - { - label: i18n.t('common.personal'), - section: 'personal', - sectionId: METRICS_PERSONAL_ID, - }, - ]; - - const scrollToSection = (sectionId: string, section: ActiveSection) => { - if (!ref.current) { - return; - } - - const { height, marginBottom } = getComputedStyle(ref.current); - - const navigationBoxHeight = parseInt(height, 10); - const navigationBoxMarginBottom = parseInt(marginBottom, 10); - const sectionDividerHeight = 48; - - const valueToAddToOffstetTop = - (section !== 'epoch' ? sectionDividerHeight : 0) - - (isLargeDesktop ? 0 : navigationBoxHeight + navigationBoxMarginBottom); - - const element = document.getElementById(sectionId)!; - - const scrollToTop = element!.offsetTop + valueToAddToOffstetTop; - setActiveSection(section); - setActiveDot(1); - const maxScroll = element!.parentElement!.scrollHeight - window.innerHeight; - if ( - scrollToTop === window.scrollY || - (scrollToTop > maxScroll && window.scrollY === maxScroll) - ) { - return; - } - - forcedSectionRef.current = section; - window.scrollTo({ - behavior: 'smooth', - top: scrollToTop, - }); - }; - - useEffect(() => { - const metricsEpochTarget = document.getElementById(METRICS_EPOCH_ID)!; - const metricsGeneralTarget = document.getElementById(METRICS_GENERAL_ID)!; - const layoutBodyTarget = document.getElementById(LAYOUT_BODY_ID)!; - - const layoutComputedStyle = getComputedStyle(layoutBodyTarget); - - const layoutBodyHeight = parseInt(layoutComputedStyle.height, 10); - const layoutBodyPaddingTop = parseInt(layoutComputedStyle.paddingTop, 10); - const layoutBodyPaddingBottom = parseInt(layoutComputedStyle.paddingBottom, 10); - - const layoutBodyWithoutVerticalPadding = - layoutBodyHeight - layoutBodyPaddingTop - layoutBodyPaddingBottom; - - const scrollHeightWithoutPaddingTop = - layoutBodyHeight - window.innerHeight - layoutBodyPaddingTop; - - const percentageScrollShareEpochSection = - metricsEpochTarget.clientHeight / layoutBodyWithoutVerticalPadding; - const percentageScrollShareGeneralSection = - metricsGeneralTarget.clientHeight / layoutBodyWithoutVerticalPadding; - - const scrollShareEpochSection = - percentageScrollShareEpochSection * scrollHeightWithoutPaddingTop; - const scrollShareGeneralSection = - percentageScrollShareGeneralSection * scrollHeightWithoutPaddingTop; - - const stepEpochSection = - (percentageScrollShareEpochSection * scrollHeightWithoutPaddingTop) / 8; - const stepGeneralSection = - (percentageScrollShareGeneralSection * scrollHeightWithoutPaddingTop) / 8; - - const scrollEndListener = () => { - if (!forcedSectionRef.current) { - return; - } - - setTimeout(() => { - forcedSectionRef.current = null; - }, 100); - }; - - const scrollListener = () => { - if (forcedSectionRef.current) { - return; - } - - const scrollYWithoutPaddingTop = window.scrollY - layoutBodyPaddingTop; - - if (scrollYWithoutPaddingTop <= scrollShareEpochSection) { - setActiveSection('epoch'); - setActiveDot( - scrollYWithoutPaddingTop < 0 ? 1 : Math.ceil(scrollYWithoutPaddingTop / stepEpochSection), - ); - return; - } - - if ( - scrollYWithoutPaddingTop > scrollShareEpochSection && - scrollYWithoutPaddingTop <= scrollShareEpochSection + scrollShareGeneralSection - ) { - setActiveSection('general'); - setActiveDot( - Math.ceil((scrollYWithoutPaddingTop - scrollShareEpochSection) / stepGeneralSection), - ); - return; - } - - setActiveSection('personal'); - setActiveDot(1); - }; - - const throttledScrollListener = throttleCallback(scrollListener, 100); - - document.addEventListener('scroll', throttledScrollListener); - document.addEventListener('scrollend', scrollEndListener); - - return () => { - document.removeEventListener('scroll', throttledScrollListener); - document.removeEventListener('scrollend', scrollEndListener); - }; - }, [throttleCallback]); - - return ( -
-
- {steps.map(({ section, sectionId, label }) => ( -
scrollToSection(sectionId, section as ActiveSection)} - > - {isLargeDesktop && ( - <> - - - - {sectionId !== METRICS_PERSONAL_ID && ( -
- {[...Array(numberOfDots - 1).keys()].map(i => ( -
- ))} -
- )} - - )} - {label} - {!isLargeDesktop && activeSection === section ? ( - - ) : null} -
- ))} -
-
- ); -}; - -export default MetricsNavigation; diff --git a/client/src/components/Metrics/MetricsNavigation/index.tsx b/client/src/components/Metrics/MetricsNavigation/index.tsx deleted file mode 100644 index 03b889b968..0000000000 --- a/client/src/components/Metrics/MetricsNavigation/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsNavigation'; diff --git a/client/src/components/Metrics/MetricsNavigation/types.ts b/client/src/components/Metrics/MetricsNavigation/types.ts deleted file mode 100644 index 96cc7892c7..0000000000 --- a/client/src/components/Metrics/MetricsNavigation/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type ActiveSection = 'epoch' | 'general' | 'personal'; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.module.scss b/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.module.scss deleted file mode 100644 index 366333883e..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -.root { - width: 100%; -} - -.grid { - grid-template-rows: 14.4rem minmax(14.4rem, auto); -} - -.divider { - width: 100%; - height: 0.1rem; - background-color: $color-octant-grey1; - margin: 3.2rem 0; -} - -.connectWalletImage { - @include tipTileConnectWalletImage(); -} diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx deleted file mode 100644 index e4729c1be4..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonal.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { ReactElement, useState } from 'react'; -import { Trans, useTranslation } from 'react-i18next'; -import { useAccount } from 'wagmi'; - -import MetricsGrid from 'components/Metrics/MetricsGrid'; -import MetricsHeader from 'components/Metrics/MetricsHeader'; -import TipTile from 'components/shared/TipTile'; -import { METRICS_PERSONAL_ID } from 'constants/domElementsIds'; -import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import useMetricsPersonalDataRewardsUsage from 'hooks/helpers/useMetricsPersonalDataRewardsUsage'; -import useTotalPatronDonations from 'hooks/helpers/useTotalPatronDonations'; -import useCryptoValues from 'hooks/queries/useCryptoValues'; -import useSettingsStore from 'store/settings/store'; - -import styles from './MetricsPersonal.module.scss'; -import MetricsPersonalGridAllocations from './MetricsPersonalGridAllocations'; -import MetricsPersonalGridDonationsProgressBar from './MetricsPersonalGridDonationsProgressBar'; -import MetricsPersonalGridPatronDonations from './MetricsPersonalGridPatronDonations'; -import MetricsPersonalGridTotalRewardsWithdrawals from './MetricsPersonalGridTotalRewardsWithdrawals'; - -const MetricsPersonal = (): ReactElement => { - const { isConnected } = useAccount(); - const { isDesktop } = useMediaQuery(); - const [isConnectWalletTipTileOpen, setIsConnectWalletTipTileOpen] = useState(!isConnected); - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - - const { - data: { displayCurrency }, - } = useSettingsStore(({ data }) => ({ - data: { - displayCurrency: data.displayCurrency, - }, - })); - const { isFetching: isFetchingCryptoValues } = useCryptoValues(displayCurrency); - const { isFetching: isFetchingMetricsPersonalDataRewardsUsage } = - useMetricsPersonalDataRewardsUsage(); - const { data: totalPatronDonations, isFetching: isFetchingTotalPatronDonations } = - useTotalPatronDonations(); - - const isLoading = - isFetchingCryptoValues || - isFetchingMetricsPersonalDataRewardsUsage || - isFetchingTotalPatronDonations; - - const wasUserEverAPatron = totalPatronDonations && totalPatronDonations.numberOfEpochs > 0; - - return ( -
-
- - {isConnected ? ( - - - - - {wasUserEverAPatron && ( - - )} - - ) : ( - setIsConnectWalletTipTileOpen(false)} - text={ - : <>]} - i18nKey="views.metrics.connectWalletTip.text" - /> - } - title={t('connectWalletTip.title')} - /> - )} -
- ); -}; - -export default MetricsPersonal; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.module.scss b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.module.scss deleted file mode 100644 index c5a1c2ea28..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.module.scss +++ /dev/null @@ -1,50 +0,0 @@ -.customSize { - min-height: 46.4rem; - grid-column: span 2; - grid-row: span 3; - max-height: 50.4rem; -} - -.numberOfAllocationsSuffix { - display: flex; - align-items: center; - justify-content: center; - height: 2.4rem; - width: 3.2rem; - font-size: $font-size-10; - font-weight: $font-weight-bold; - color: $color-octant-dark; - border-radius: $border-radius-08; - background-color: $color-octant-grey3; - margin-left: auto; -} - -.noAllocationsYet { - height: 100%; - width: 100%; - - .noAllocationsYetImage { - height: 8rem; - width: auto; - margin-top: 5.6rem; - } - - .noAllocationsYetLabel { - margin-top: 2.8rem; - font-size: $font-size-16; - font-weight: $font-weight-semibold; - color: $color-octant-grey5; - } -} - -.skeletonWrapper { - width: 100%; - height: 100%; - padding: 1rem 2.4rem; - - .skeleton { - @include skeleton(); - width: 100%; - height: 100%; - } -} diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.tsx deleted file mode 100644 index 78745856ce..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/MetricsPersonalGridAllocations.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { FC, memo, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; - -import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; -import MetricsProjectsList from 'components/Metrics/MetricsProjectsList'; -import Img from 'components/ui/Img'; -import useUserAllocationsAllEpochs from 'hooks/helpers/useUserAllocationsAllEpochs'; - -import styles from './MetricsPersonalGridAllocations.module.scss'; -import MetricsPersonalGridAllocationsProps from './types'; -import { getReducedUserAllocationsAllEpochs } from './utils'; - -const MetricsPersonalGridAllocations: FC = ({ - isLoading, - size, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - const { data: userAllocationsAllEpochs } = useUserAllocationsAllEpochs(); - const reducedUserAllocationsAllEpochs = - getReducedUserAllocationsAllEpochs(userAllocationsAllEpochs); - - const areAllocationsEmpty = !isLoading && reducedUserAllocationsAllEpochs?.length === 0; - - const children = useMemo(() => { - if (areAllocationsEmpty) { - return ( -
- -
{t('noAllocationsYet')}
-
- ); - } - - return ( - - ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isLoading, areAllocationsEmpty, reducedUserAllocationsAllEpochs?.length, t]); - - return ( - - {reducedUserAllocationsAllEpochs.length} -
- ), - }, - ]} - size={size} - /> - ); -}; - -export default memo(MetricsPersonalGridAllocations); diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/index.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/index.tsx deleted file mode 100644 index 2cfc27b371..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsPersonalGridAllocations'; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/types.ts b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/types.ts deleted file mode 100644 index dfe4d9b67e..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { MetricsGridTileSizes } from 'components/Metrics/MetricsGrid/MetricsGridTile/types'; - -export default interface MetricsPersonalGridAllocationsProps { - isLoading: boolean; - size: MetricsGridTileSizes; -} diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils.test.ts b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils.test.ts deleted file mode 100644 index a73583f3e7..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridAllocations/utils.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { parseUnitsBigInt } from 'utils/parseUnitsBigInt'; - -import { getReducedUserAllocationsAllEpochs } from './utils'; - -describe('getReducedUserAllocationsAllEpochs', () => { - it('properly reduces userAllocationsAllEpochs and squashes the duplicates, summarizing', () => { - expect( - getReducedUserAllocationsAllEpochs([ - { - elements: [ - { - address: '0x1', - epoch: 1, - value: parseUnitsBigInt('0.1'), - }, - { - address: '0x2', - epoch: 1, - value: parseUnitsBigInt('0.2'), - }, - { - address: '0x3', - epoch: 1, - value: parseUnitsBigInt('0.3'), - }, - { - address: '0x4', - epoch: 1, - value: parseUnitsBigInt('0.4'), - }, - ], - hasUserAlreadyDoneAllocation: true, - isManuallyEdited: false, - }, - { - elements: [ - { - address: '0x1', - epoch: 2, - value: parseUnitsBigInt('0.3'), - }, - { - address: '0x2', - epoch: 2, - value: parseUnitsBigInt('0.2'), - }, - { - address: '0x3', - epoch: 2, - value: parseUnitsBigInt('0.1'), - }, - { - address: '0x5', - epoch: 2, - value: parseUnitsBigInt('0.5'), - }, - ], - hasUserAlreadyDoneAllocation: true, - isManuallyEdited: false, - }, - ]), - ).toEqual([ - { - address: '0x1', - epoch: 2, - value: parseUnitsBigInt('0.4'), - }, - { - address: '0x2', - epoch: 2, - value: parseUnitsBigInt('0.4'), - }, - { - address: '0x3', - epoch: 2, - value: parseUnitsBigInt('0.4'), - }, - { - address: '0x4', - epoch: 1, - value: parseUnitsBigInt('0.4'), - }, - { - address: '0x5', - epoch: 2, - value: parseUnitsBigInt('0.5'), - }, - ]); - }); -}); diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/MetricsPersonalGridDonationsProgressBar.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/MetricsPersonalGridDonationsProgressBar.tsx deleted file mode 100644 index cf4315fcb3..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/MetricsPersonalGridDonationsProgressBar.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import MetricsDonationsProgressBar from 'components/Metrics/MetricsDonationsProgressBar'; -import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; -import useMetricsPersonalDataRewardsUsage from 'hooks/helpers/useMetricsPersonalDataRewardsUsage'; -import { formatUnitsBigInt } from 'utils/formatUnitsBigInt'; - -import MetricsPersonalGridDonationsProgressBarProps from './types'; - -const MetricsPersonalGridDonationsProgressBar: FC = ({ - isLoading, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - const { data: metricsPersonalDataRewardsUsage } = useMetricsPersonalDataRewardsUsage(); - - const totalDonationsNumber = parseFloat( - formatUnitsBigInt(metricsPersonalDataRewardsUsage?.totalDonations || BigInt(0)), - ); - const totalRewardsUsedNumber = parseFloat( - formatUnitsBigInt(metricsPersonalDataRewardsUsage?.totalRewardsUsed || BigInt(0)), - ); - - const donationsValue = - totalRewardsUsedNumber > 0 ? (totalDonationsNumber / totalRewardsUsedNumber) * 100 : 0; - - return ( - - ), - title: t('donationsVsPersonalAllocationValue'), - }, - ]} - /> - ); -}; - -export default MetricsPersonalGridDonationsProgressBar; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/index.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/index.tsx deleted file mode 100644 index 38b93bbd38..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsPersonalGridDonationsProgressBar'; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/types.ts b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/types.ts deleted file mode 100644 index d9eb4e2fc2..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridDonationsProgressBar/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface MetricsPersonalGridDonationsProgressBarProps { - isLoading: boolean; -} diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/MetricsPersonalGridPatronDonations.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/MetricsPersonalGridPatronDonations.tsx deleted file mode 100644 index 15d76c8318..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/MetricsPersonalGridPatronDonations.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { FC, memo } from 'react'; -import { useTranslation } from 'react-i18next'; - -import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; -import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue'; -import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; - -import MetricsPersonalGridPatronDonationsProps from './types'; - -const MetricsPersonalGridPatronDonations: FC = ({ - isLoading, - numberOfEpochs, - value, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - - const getValuesToDisplay = useGetValuesToDisplay(); - - const totalPatronDonationsValues = getValuesToDisplay({ - cryptoCurrency: 'ethereum', - valueCrypto: value, - }); - - return ( - - ), - title: t('patronModeActive'), - }, - { - children: ( - - ), - title: t('donatedAsPatron'), - }, - ]} - size="M" - /> - ); -}; - -export default memo(MetricsPersonalGridPatronDonations); diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/index.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/index.tsx deleted file mode 100644 index 9ad78dbe70..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsPersonalGridPatronDonations'; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/types.ts b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/types.ts deleted file mode 100644 index 801fc4e10f..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridPatronDonations/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default interface MetricsPersonalGridPatronDonationsProps { - isLoading: boolean; - numberOfEpochs: number; - value: bigint; -} diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/MetricsPersonalGridTotalRewardsWithdrawals.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/MetricsPersonalGridTotalRewardsWithdrawals.tsx deleted file mode 100644 index fa407d9c85..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/MetricsPersonalGridTotalRewardsWithdrawals.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { FC } from 'react'; -import { useTranslation } from 'react-i18next'; - -import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile'; -import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue'; -import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; -import useMetricsPersonalDataRewardsUsage from 'hooks/helpers/useMetricsPersonalDataRewardsUsage'; - -import MetricsPersonalGridTotalRewardsWithdrawalsProps from './types'; - -const MetricsPersonalGridTotalRewardsWithdrawals: FC< - MetricsPersonalGridTotalRewardsWithdrawalsProps -> = ({ isLoading }) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' }); - const { data: metricsPersonalDataRewardsUsage } = useMetricsPersonalDataRewardsUsage(); - const getValuesToDisplay = useGetValuesToDisplay(); - - const totalRewardsUsedValues = getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - valueCrypto: metricsPersonalDataRewardsUsage?.totalRewardsUsed, - }); - - const totalWithdrawalsValues = getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - valueCrypto: metricsPersonalDataRewardsUsage?.totalWithdrawals, - }); - - return ( - - ), - title: t('totalRewards'), - }, - { - children: ( - - ), - title: t('totalWithdrawals'), - }, - ]} - size="M" - /> - ); -}; - -export default MetricsPersonalGridTotalRewardsWithdrawals; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/index.tsx b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/index.tsx deleted file mode 100644 index 99c48ddf27..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsPersonalGridTotalRewardsWithdrawals'; diff --git a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/types.ts b/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/types.ts deleted file mode 100644 index 1913fde7b2..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/MetricsPersonalGridTotalRewardsWithdrawals/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface MetricsPersonalGridTotalRewardsWithdrawalsProps { - isLoading: boolean; -} diff --git a/client/src/components/Metrics/MetricsPersonal/index.tsx b/client/src/components/Metrics/MetricsPersonal/index.tsx deleted file mode 100644 index 824e21fcd5..0000000000 --- a/client/src/components/Metrics/MetricsPersonal/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './MetricsPersonal'; diff --git a/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.module.scss b/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.module.scss index c61bbfdc2e..07be68c62e 100644 --- a/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.module.scss +++ b/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.module.scss @@ -14,9 +14,10 @@ } .projectsList { - padding: 0 3.8rem 0rem 2.4rem; + display: grid; + padding: 0 2.4rem; max-height: 100%; - overflow: auto; - margin-right: 0.8rem; + overflow-y: auto; + overflow-x: hidden; } } diff --git a/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx b/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx index efa9c36262..2a5efbe3f3 100644 --- a/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx +++ b/client/src/components/Metrics/MetricsProjectsList/MetricsProjectsList.tsx @@ -26,17 +26,42 @@ const MetricsProjectsList: FC = ({ : projects.map(project => ( diff --git a/client/src/components/Metrics/MetricsProjectsList/types.ts b/client/src/components/Metrics/MetricsProjectsList/types.ts index 2e228aaf03..a3e842195e 100644 --- a/client/src/components/Metrics/MetricsProjectsList/types.ts +++ b/client/src/components/Metrics/MetricsProjectsList/types.ts @@ -1,8 +1,8 @@ -import { ResponseItem } from 'hooks/helpers/useUserAllocationsAllEpochs'; +import { ProjectIpfsWithRewards } from 'hooks/queries/useProjectsIpfsWithRewards'; export default interface MetricsProjectsListProps { dataTest?: string; isLoading: boolean; numberOfSkeletons: number; - projects: ResponseItem['elements']; + projects: (ProjectIpfsWithRewards & { epoch: number })[]; } diff --git a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.module.scss b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.module.scss index dbc6f9f717..e3c6f2ce6c 100644 --- a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.module.scss +++ b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.module.scss @@ -1,13 +1,24 @@ .root { - display: flex; + display: grid; align-items: center; justify-content: space-between; height: 5.6rem; + grid-template-columns: auto auto; + + @media #{$large-desktop-up} { + grid-template-columns: 21.2rem 8rem 11.8rem 11.8rem 11.8rem; + } &:not(:last-child) { border-bottom: 0.1rem solid $color-octant-grey3; } + .logoNameGroup { + display: flex; + align-items: center; + overflow: hidden; + } + .image { height: 2.4rem; width: 2.4rem; @@ -19,19 +30,31 @@ .value { font-size: $font-size-12; font-weight: $font-weight-bold; - color: $color-octant-dark; } .name { width: 100%; - text-align: left; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + color: $color-octant-dark; + margin-right: 0.8rem; + text-align: left; } .value { - margin-left: 1rem; + margin-left: 0.2rem; flex-shrink: 0; + color: $color-octant-grey5; + text-align: right; + white-space: nowrap; + + @media #{$large-desktop-up} { + text-align: left; + } + + &.total { + color: $color-octant-dark; + } } } diff --git a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx index 85da1968b5..cbfdeade20 100644 --- a/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx +++ b/client/src/components/Metrics/MetricsProjectsListItem/MetricsProjectsListItem.tsx @@ -1,36 +1,52 @@ +import cx from 'classnames'; import React, { FC, memo } from 'react'; import Img from 'components/ui/Img/Img'; import env from 'env'; -import useProjectsIpfs from 'hooks/queries/useProjectsIpfs'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; import styles from './MetricsProjectsListItem.module.scss'; import MetricsProjectsListItemProps from './types'; const MetricsProjectsListItem: FC = ({ - address, - epoch, - value, + numberOfDonors, + donations, + matchFunding, + total, dataTest = 'MetricsProjectsListItem', + image, + name, }) => { const { ipfsGateways } = env; - const { data: projectsIpfs } = useProjectsIpfs([address], epoch); - - const image = projectsIpfs.at(0)?.profileImageSmall; - const name = projectsIpfs.at(0)?.name; + const { isLargeDesktop } = useMediaQuery(); return (
- project logo `${element}${image}`)} - /> -
- {name} +
+ project logo `${element}${image}`)} + /> +
+ {name} +
-
- {value} + {isLargeDesktop && ( + <> +
+ {numberOfDonors} +
+
+ {donations} +
+
+ {matchFunding} +
+ + )} +
+ {total}
); diff --git a/client/src/components/Metrics/MetricsProjectsListItem/types.ts b/client/src/components/Metrics/MetricsProjectsListItem/types.ts index c87fc4fac1..e84aec2ab1 100644 --- a/client/src/components/Metrics/MetricsProjectsListItem/types.ts +++ b/client/src/components/Metrics/MetricsProjectsListItem/types.ts @@ -1,6 +1,9 @@ export default interface MetricsProjectsListItemProps { - address: string; dataTest?: string; - epoch?: number; - value: string; + donations: string; + image: string; + matchFunding: string; + name: string; + numberOfDonors: number; + total: string; } diff --git a/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.module.scss b/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.module.scss index b11934a8e5..6921abf7d5 100644 --- a/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.module.scss +++ b/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.module.scss @@ -1,34 +1,47 @@ .root { - display: flex; + display: grid; align-items: center; justify-content: space-between; height: 5.6rem; + grid-template-columns: 16rem 8rem; + + @media #{$large-desktop-up} { + grid-template-columns: 21.2rem 8rem 11.8rem 11.8rem 11.8rem; + } &:not(:last-child) { border-bottom: 0.1rem solid $color-octant-grey3; } - .image { - @include skeleton(); - height: 2.4rem; - width: 2.4rem; - border-radius: 100%; - margin-right: 1.6rem; + .imageNameGroup { + display: flex; + align-items: center; + + .image { + @include skeleton(); + height: 2.4rem; + width: 2.4rem; + border-radius: 100%; + margin-right: 1.6rem; + min-width: 2.4rem; + } + + .name { + @include skeleton(); + height: 1.5rem; + width: 16.4rem; + } } - .name { + .numberOfDonors { @include skeleton(); height: 1.5rem; - width: 18rem; + width: calc(100% - 1rem); } .value { @include skeleton(); height: 1.5rem; - width: 4rem; - } - - .value { - margin-left: auto; + width: calc(100% - 1rem); } } diff --git a/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.tsx b/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.tsx index 1b7197f451..6cf2df6a45 100644 --- a/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.tsx +++ b/client/src/components/Metrics/MetricsProjectsListSkeletonItem/MetricsProjectsListSkeletonItem.tsx @@ -1,12 +1,24 @@ import React, { ReactElement, memo } from 'react'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; + import styles from './MetricsProjectsListSkeletonItem.module.scss'; const MetricsProjectsListSkeletonItem = (): ReactElement => { + const { isLargeDesktop } = useMediaQuery(); return (
-
-
+
+
+
+
+ {isLargeDesktop && ( + <> +
+
+
+ + )}
); diff --git a/client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.module.scss b/client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.module.scss new file mode 100644 index 0000000000..8e59eaa836 --- /dev/null +++ b/client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.module.scss @@ -0,0 +1,13 @@ +.root { + display: flex; + justify-content: space-between; + align-items: center; + height: 6.4rem; + margin: 1.6rem 0; + + .title { + font-size: $font-size-16; + color: $color-octant-dark; + font-weight: $font-weight-bold; + } +} diff --git a/client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.tsx b/client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.tsx new file mode 100644 index 0000000000..241f7e5dd2 --- /dev/null +++ b/client/src/components/Metrics/MetricsSectionHeader/MetricsSectionHeader.tsx @@ -0,0 +1,19 @@ +import React, { FC } from 'react'; + +import styles from './MetricsSectionHeader.module.scss'; +import MetricsSectionHeaderProps from './types'; + +const MetricsSectionHeader: FC = ({ + title, + dataTest = 'MetricsSectionHeader', + children, +}) => { + return ( +
+
{title}
+ {children} +
+ ); +}; + +export default MetricsSectionHeader; diff --git a/client/src/components/Metrics/MetricsHeader/index.ts b/client/src/components/Metrics/MetricsSectionHeader/index.ts similarity index 50% rename from client/src/components/Metrics/MetricsHeader/index.ts rename to client/src/components/Metrics/MetricsSectionHeader/index.ts index 989bf16060..85421ea425 100644 --- a/client/src/components/Metrics/MetricsHeader/index.ts +++ b/client/src/components/Metrics/MetricsSectionHeader/index.ts @@ -1,2 +1,2 @@ // eslint-disable-next-line no-restricted-exports -export { default } from './MetricsHeader'; +export { default } from './MetricsSectionHeader'; diff --git a/client/src/components/Metrics/MetricsHeader/types.ts b/client/src/components/Metrics/MetricsSectionHeader/types.ts similarity index 65% rename from client/src/components/Metrics/MetricsHeader/types.ts rename to client/src/components/Metrics/MetricsSectionHeader/types.ts index 9efa6993e1..061142524c 100644 --- a/client/src/components/Metrics/MetricsHeader/types.ts +++ b/client/src/components/Metrics/MetricsSectionHeader/types.ts @@ -1,6 +1,6 @@ import { ReactNode } from 'react'; -export default interface MetricsHeaderProps { +export default interface MetricsSectionHeaderProps { children?: ReactNode; dataTest?: string; title: string; diff --git a/client/src/components/Project/ProjectListItem/ProjectListItem.module.scss b/client/src/components/Project/ProjectListItem/ProjectListItem.module.scss index 809b9d850a..f2fd94b17d 100644 --- a/client/src/components/Project/ProjectListItem/ProjectListItem.module.scss +++ b/client/src/components/Project/ProjectListItem/ProjectListItem.module.scss @@ -1,32 +1,57 @@ .root { display: flex; flex-direction: column; - padding-bottom: 4rem; background: $color-white; - padding-left: 5.6rem; - padding-right: 5.6rem; + padding: 4.8rem 5.6rem 4rem; - &:first-child { - padding-top: 10.4rem; + @media #{$tablet-up} { + padding: 9.6rem 13.2rem 4rem; } @media #{$desktop-up} { - &:first-child { - padding-top: 0; - border-radius: $border-radius-16 $border-radius-16 0 0; + padding: 9.6rem 17.8rem 4rem; + } + + @media #{$large-desktop-up} { + padding: 9.6rem 24.6rem 4rem; + } + + .projectRewards { + margin: 4.6rem 0 4.2rem; + border-bottom: 0.2rem solid $color-octant-grey3; + padding-bottom: 1.2rem; + + @media #{$tablet-up} { + margin: 6.4rem 0 5.6rem; + } + + @media #{$desktop-up} { + margin: 8.8rem 0; } - padding-left: 11rem; - padding-right: 11rem; + @media #{$large-desktop-up} { + margin: 8rem 0 8.8rem; + } } -} -.projectRewards { - margin: 6.4rem 0 5rem; - border-bottom: 0.2rem solid $color-octant-grey3; - padding-bottom: 1.2rem; + .description { + font-weight: $font-weight-medium; + font-size: $font-size-12; + line-height: 2rem; + + @media #{$tablet-up} { + font-size: $font-size-16; + line-height: 2.6rem; + } - @media #{$tablet-down} { - margin: 3.4rem 0 5rem; + @media #{$desktop-up} { + font-size: $font-size-20; + line-height: 3.4rem; + } + + @media #{$large-desktop-up} { + font-size: $font-size-22; + line-height: 4rem; + } } } diff --git a/client/src/components/Project/ProjectListItem/ProjectListItem.tsx b/client/src/components/Project/ProjectListItem/ProjectListItem.tsx index 45da930b03..3335e3e128 100644 --- a/client/src/components/Project/ProjectListItem/ProjectListItem.tsx +++ b/client/src/components/Project/ProjectListItem/ProjectListItem.tsx @@ -5,6 +5,7 @@ import ProjectListItemHeader from 'components/Project/ProjectListItemHeader'; import RewardsWithoutThreshold from 'components/shared/RewardsWithoutThreshold'; import RewardsWithThreshold from 'components/shared/RewardsWithThreshold'; import Description from 'components/ui/Description'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useProjectsIpfsWithRewards from 'hooks/queries/useProjectsIpfsWithRewards'; import decodeBase64ToUtf8 from 'utils/decodeBase64ToUtf8'; @@ -21,11 +22,14 @@ const ProjectListItem: FC = ({ index, epoch, }) => { + const { isMobile } = useMediaQuery(); const { data: projectsIpfsWithRewards } = useProjectsIpfsWithRewards(epoch); const projectIpfsWithRewards = projectsIpfsWithRewards.find(p => p.address === address); // loadedProjects (ProjectView) aren't updated during changes in open AW // to provide live updates, the following values are taken directly from projectsIpfsWithRewards const numberOfDonors = projectIpfsWithRewards?.numberOfDonors || 0; + const matchedRewards = projectIpfsWithRewards?.matchedRewards || 0n; + const donations = projectIpfsWithRewards?.donations || 0n; const totalValueOfAllocations = projectIpfsWithRewards?.totalValueOfAllocations || 0n; const { data: currentEpoch } = useCurrentEpoch(); const isEpoch1 = currentEpoch === 1; @@ -55,12 +59,16 @@ const ProjectListItem: FC = ({ {!isEpoch1 && (!epoch || epoch >= 4) && ( )} = ({ epoch, }) => { const { ipfsGateways } = env; - const { i18n } = useTranslation('translation', { keyPrefix: 'views.project' }); + const { t, i18n } = useTranslation('translation', { keyPrefix: 'views.project' }); const [isLinkCopied, setIsLinkCopied] = useState(false); const { epoch: epochUrl } = useParams(); const { data: currentEpoch } = useCurrentEpoch(); @@ -50,9 +49,20 @@ const ProjectListItemHeader: FC = ({ const epochUrlInt = parseInt(epochUrl!, 10); const isArchivedProject = epochUrlInt < (isDecisionWindowOpen ? currentEpoch! - 1 : currentEpoch!); - const isAllocatedTo = !!userAllocations?.elements.find( - ({ address: userAllocationAddress }) => userAllocationAddress === address, - ); + + const isAllocatedTo = useMemo(() => { + const isInUserAllocations = !!userAllocations?.elements.find( + ({ address: userAllocationAddress }) => userAllocationAddress === address, + ); + const isInAllocations = allocations.includes(address); + if (epoch !== undefined) { + return isInUserAllocations; + } + if (isDecisionWindowOpen) { + return isInUserAllocations && isInAllocations; + } + return false; + }, [address, allocations, userAllocations, epoch, isDecisionWindowOpen]); const onShareClick = (): boolean | Promise => { const { origin } = window.location; @@ -81,49 +91,52 @@ const ProjectListItemHeader: FC = ({ return (
-
- `${element}${profileImageSmall}`)} - /> -
+ `${element}${profileImageSmall}`)} + /> +
+ {name} +
+
+
+ + {website!.label || website!.url} + + - + - {((isAllocatedTo && isArchivedProject) || !isArchivedProject) && ( - onAddRemoveFromAllocate(address)} - /> - )}
+ onAddRemoveFromAllocate(address)} + variant="cta" + />
- - {name} - -
); }; diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss index 62009b0300..8101c16dd5 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.module.scss +++ b/client/src/components/Projects/ProjectsList/ProjectsList.module.scss @@ -1,19 +1,3 @@ -$elementMargin: 1.6rem; - -.noSearchResults { - display: flex; - flex-direction: column; - align-items: center; - flex: 1; - color: $color-octant-grey5; - margin-bottom: 1.6rem; - - .image { - width: 28rem; - margin-bottom: 3.2rem; - } -} - .list { display: flex; flex-wrap: wrap; @@ -21,18 +5,6 @@ $elementMargin: 1.6rem; justify-content: space-between; } -.inputSearch { - margin-bottom: $elementMargin; -} - -.element { - margin-bottom: $elementMargin; - - @media #{$desktop-up} { - @include flexBasisGutter(2, $elementMargin); - } -} - .epochArchive { width: 100%; background: $color-white; @@ -45,7 +17,7 @@ $elementMargin: 1.6rem; font-size: $font-size-16; font-weight: $font-weight-bold; border-radius: $border-radius-16; - margin: 0 0 1.6rem; + margin: 0 0 1.6rem; .epochDurationLabel { color: $color-octant-grey5; @@ -65,5 +37,5 @@ $elementMargin: 1.6rem; width: 100%; height: 0.1rem; background-color: $color-octant-grey1; - margin: 0 0 1.6rem; + margin: 0 0 1.6rem; } diff --git a/client/src/components/Projects/ProjectsList/ProjectsList.tsx b/client/src/components/Projects/ProjectsList/ProjectsList.tsx index 7174b465a0..ed20c36b2f 100644 --- a/client/src/components/Projects/ProjectsList/ProjectsList.tsx +++ b/client/src/components/Projects/ProjectsList/ProjectsList.tsx @@ -1,19 +1,14 @@ import cx from 'classnames'; -import React, { ChangeEvent, FC, memo, useState } from 'react'; +import React, { FC, memo } from 'react'; import { useTranslation } from 'react-i18next'; import ProjectsListItem from 'components/Projects/ProjectsListItem'; import ProjectsListSkeletonItem from 'components/Projects/ProjectsListSkeletonItem'; -import Img from 'components/ui/Img'; -import InputText from 'components/ui/InputText/InputText'; -import Svg from 'components/ui/Svg'; -import { PROJECTS_ADDRESSES_RANDOMIZED_ORDER } from 'constants/localStorageKeys'; +import Grid from 'components/shared/Grid'; import useEpochDurationLabel from 'hooks/helpers/useEpochDurationLabel'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import useSortedProjects from 'hooks/helpers/useIdsInAllocation/useSortedProjects'; import useProjectsEpoch from 'hooks/queries/useProjectsEpoch'; import useProjectsIpfsWithRewards from 'hooks/queries/useProjectsIpfsWithRewards'; -import { magnifyingGlass } from 'svg/misc'; -import { ProjectsAddressesRandomizedOrder } from 'types/localStorage'; import styles from './ProjectsList.module.scss'; import ProjectsListProps from './types'; @@ -22,47 +17,20 @@ const ProjectsList: FC = ({ areCurrentEpochsProjectsHiddenOutsideAllocationWindow, epoch, isFirstArchive, + orderOption, }) => { const { t } = useTranslation('translation', { keyPrefix: 'components.dedicated.projectsList', }); - const [searchQuery, setSearchQuery] = useState(''); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); const { data: projectsEpoch, isFetching: isFetchingProjectsEpoch } = useProjectsEpoch(epoch); - const { - data: projectsIpfsWithRewards, - isFetching: isFetchingProjectsWithRewards, - isAnyIpfsError, - } = useProjectsIpfsWithRewards(epoch); + const { data: projectsIpfsWithRewards, isFetching: isFetchingProjectsWithRewards } = + useProjectsIpfsWithRewards(epoch); const epochDurationLabel = useEpochDurationLabel(epoch); const isLoading = isFetchingProjectsEpoch || isFetchingProjectsWithRewards; - const isLatestEpochAndDecisionWindowOpen = epoch === undefined && !!isDecisionWindowOpen; - - const onChangeSearchQuery = (e: ChangeEvent): void => { - setSearchQuery(e.target.value); - }; - - const areProjectsIpfsWithRewardsAvailable = - projectsIpfsWithRewards.length > 0 && !isFetchingProjectsWithRewards && !isAnyIpfsError; - const projectsIpfsWithRewardsFiltered = areProjectsIpfsWithRewardsAvailable - ? projectsIpfsWithRewards.filter(projectIpfsWithRewards => { - return ( - projectIpfsWithRewards.name!.toLowerCase().includes(searchQuery.toLowerCase()) || - projectIpfsWithRewards.address!.toLowerCase().includes(searchQuery.toLowerCase()) - ); - }) - : []; - - const projectsAddressesRandomizedOrder = JSON.parse( - localStorage.getItem(PROJECTS_ADDRESSES_RANDOMIZED_ORDER)!, - ) as ProjectsAddressesRandomizedOrder; - - const projectsAddressesToIterate = isLatestEpochAndDecisionWindowOpen - ? projectsAddressesRandomizedOrder.addressesRandomizedOrder - : projectsIpfsWithRewards.map(({ address }) => address); + const projectsIpfsWithRewardsSorted = useSortedProjects(projectsIpfsWithRewards, orderOption); return (
= ({
)} - {isLatestEpochAndDecisionWindowOpen && ( - } - onChange={onChangeSearchQuery} - onClear={() => setSearchQuery('')} - placeholder={t('searchInputPlaceholder')} - value={searchQuery} - variant="search" - /> - )} - {isLatestEpochAndDecisionWindowOpen && - !isFetchingProjectsWithRewards && - projectsIpfsWithRewardsFiltered.length === 0 && ( -
- - {t('noSearchResults')} -
- )} - {areProjectsIpfsWithRewardsAvailable - ? projectsAddressesToIterate.map((address, index) => { - const projectIpfsWithRewards = projectsIpfsWithRewardsFiltered.find( - element => element.address === address, - ); - - if (!projectIpfsWithRewards) { - return null; - } - - return ( + + {projectsIpfsWithRewards.length > 0 && !isFetchingProjectsWithRewards + ? projectsIpfsWithRewardsSorted.map((projectIpfsWithRewards, index) => ( = ({ epoch={epoch} projectIpfsWithRewards={projectIpfsWithRewards} /> - ); - }) - : projectsEpoch?.projectsAddresses?.map((_, index) => ( - // eslint-disable-next-line react/no-array-index-key - - ))} + )) + : projectsEpoch?.projectsAddresses?.map((_, index) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} +
); }; diff --git a/client/src/components/Projects/ProjectsList/types.ts b/client/src/components/Projects/ProjectsList/types.ts index f2cce0c410..6fdd91674a 100644 --- a/client/src/components/Projects/ProjectsList/types.ts +++ b/client/src/components/Projects/ProjectsList/types.ts @@ -1,5 +1,8 @@ +import { OrderOption } from 'views/ProjectsView/types'; + export default interface ProjectsListProps { areCurrentEpochsProjectsHiddenOutsideAllocationWindow?: boolean; epoch?: number; isFirstArchive?: boolean; + orderOption: OrderOption; } diff --git a/client/src/components/Projects/ProjectsListItem/ProjectsListItem.module.scss b/client/src/components/Projects/ProjectsListItem/ProjectsListItem.module.scss index b2d91ab761..105f16bf44 100644 --- a/client/src/components/Projects/ProjectsListItem/ProjectsListItem.module.scss +++ b/client/src/components/Projects/ProjectsListItem/ProjectsListItem.module.scss @@ -34,12 +34,26 @@ right: 0.8rem; } - .imageProfile { - border-radius: 50%; - width: 4rem; - height: 4rem; + .imageProfileWrapper { + position: relative; + + .imageProfile { + border-radius: 50%; + width: 4rem; + height: 4rem; + } + + .tinyLabel { + position: absolute; + top: 0; + right: 0; + white-space: nowrap; + background: $color-white; + text-transform: none; + } } + .body { padding: 0 $projectItemPadding; text-align: left; diff --git a/client/src/components/Projects/ProjectsListItem/ProjectsListItem.tsx b/client/src/components/Projects/ProjectsListItem/ProjectsListItem.tsx index 4db53cd30e..e2b1652765 100644 --- a/client/src/components/Projects/ProjectsListItem/ProjectsListItem.tsx +++ b/client/src/components/Projects/ProjectsListItem/ProjectsListItem.tsx @@ -8,9 +8,11 @@ import RewardsWithoutThreshold from 'components/shared/RewardsWithoutThreshold'; import RewardsWithThreshold from 'components/shared/RewardsWithThreshold'; import Description from 'components/ui/Description'; import Img from 'components/ui/Img'; +import TinyLabel from 'components/ui/TinyLabel'; import { WINDOW_PROJECTS_SCROLL_Y } from 'constants/window'; import env from 'env'; import useIdsInAllocation from 'hooks/helpers/useIdsInAllocation'; +import useIsAddToAllocateButtonVisible from 'hooks/helpers/useIsAddToAllocateButtonVisible'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; import useUserAllocations from 'hooks/queries/useUserAllocations'; @@ -25,6 +27,7 @@ const ProjectsListItem: FC = ({ dataTest, epoch, projectIpfsWithRewards, + searchResultsLabel, }) => { const { ipfsGateways } = env; const { address, isLoadingError, profileImageSmall, name, introDescription } = @@ -64,6 +67,11 @@ const ProjectsListItem: FC = ({ const isEpoch1 = currentEpoch === 1; const isArchivedProject = epoch !== undefined; + const isAddToAllocateButtonVisible = useIsAddToAllocateButtonVisible({ + isAllocatedTo, + isArchivedProject, + }); + return (
= ({ ) : (
- `${element}${profileImageSmall}`)} - /> - {((isAllocatedTo && isArchivedProject) || !isArchivedProject) && ( +
+ `${element}${profileImageSmall}`)} + /> + {searchResultsLabel && ( + + )} +
+ {isAddToAllocateButtonVisible && ( = ({ + orderOption, + projectsIpfsWithRewardsAndEpochs, + isLoading, +}) => { + const { t } = useTranslation('translation', { + keyPrefix: 'components.dedicated.projectsSearchResults', + }); + + const { data: currentEpoch } = useCurrentEpoch(); + const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + + const projectsIpfsWithRewardsSorted = useSortedProjects( + projectsIpfsWithRewardsAndEpochs, + orderOption, + ); + + return ( +
+ {projectsIpfsWithRewardsAndEpochs.length === 0 && !isLoading && ( +
+ + {t('noSearchResults')} +
+ )} + + {projectsIpfsWithRewardsAndEpochs.length > 0 && + !isLoading && + projectsIpfsWithRewardsSorted.map((projectIpfsWithRewards, index) => ( + + ))} + {projectsIpfsWithRewardsAndEpochs.length === 0 && + isLoading && + [...Array(5).keys()].map((_, index) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} + +
+ ); +}; + +export default memo(ProjectsSearchResults); diff --git a/client/src/components/Projects/ProjectsSearchResults/index.tsx b/client/src/components/Projects/ProjectsSearchResults/index.tsx new file mode 100644 index 0000000000..de0a42181c --- /dev/null +++ b/client/src/components/Projects/ProjectsSearchResults/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ProjectsSearchResults'; diff --git a/client/src/components/Projects/ProjectsSearchResults/types.ts b/client/src/components/Projects/ProjectsSearchResults/types.ts new file mode 100644 index 0000000000..ad449f839a --- /dev/null +++ b/client/src/components/Projects/ProjectsSearchResults/types.ts @@ -0,0 +1,8 @@ +import { ProjectsIpfsWithRewardsAndEpochs } from 'hooks/queries/useSearchedProjectsDetails'; +import { OrderOption } from 'views/ProjectsView/types'; + +export default interface ProjectsSearchResultsProps { + isLoading: boolean; + orderOption: OrderOption; + projectsIpfsWithRewardsAndEpochs: ProjectsIpfsWithRewardsAndEpochs[]; +} diff --git a/client/src/components/Projects/ProjectsTimelineWidget/ProjectsTimelineWidget.module.scss b/client/src/components/Projects/ProjectsTimelineWidget/ProjectsTimelineWidget.module.scss deleted file mode 100644 index adc7fcc619..0000000000 --- a/client/src/components/Projects/ProjectsTimelineWidget/ProjectsTimelineWidget.module.scss +++ /dev/null @@ -1,18 +0,0 @@ -.root { - width: 100%; - padding: 1.6rem; - border-radius: $border-radius-16; - background: $color-white; - display: flex; - overflow: hidden; - margin-bottom: 1.6rem; -} - -.milestonesWrapper { - display: flex; -} - -.constraintsWrapper { - width: 100%; - display: flex; -} diff --git a/client/src/components/Projects/ProjectsTimelineWidget/index.tsx b/client/src/components/Projects/ProjectsTimelineWidget/index.tsx deleted file mode 100644 index 680c37162a..0000000000 --- a/client/src/components/Projects/ProjectsTimelineWidget/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ProjectsTimelineWidget'; diff --git a/client/src/components/Projects/ProjectsTimelineWidgetItem/ProjectsTimelineWidgetItem.tsx b/client/src/components/Projects/ProjectsTimelineWidgetItem/ProjectsTimelineWidgetItem.tsx deleted file mode 100644 index 9435fa9bb6..0000000000 --- a/client/src/components/Projects/ProjectsTimelineWidgetItem/ProjectsTimelineWidgetItem.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import cx from 'classnames'; -import { format } from 'date-fns'; -import { useInView } from 'framer-motion'; -import React, { FC, useRef, useState } from 'react'; - -import Svg from 'components/ui/Svg'; -import { arrowRight, arrowTopRight } from 'svg/misc'; - -import styles from './ProjectsTimelineWidgetItem.module.scss'; -import ProjectsTimelineWidgetItemProps from './types'; - -const ProjectsTimelineWidgetItem: FC = ({ - id, - label, - from, - to, - isActive, - href, -}) => { - const ref = useRef(null); - const isInView = useInView(ref, { amount: 'all' }); - const [initialClientX, setInitialClientX] = useState(null); - - return ( -
{ - if (!href) { - return; - } - setInitialClientX(e.clientX); - }} - onMouseUp={e => { - if (!href) { - return; - } - if (initialClientX === e.clientX) { - // workaround for cypress test - window.open(href, window.Cypress ? '_self' : '_blank'); - } - - setInitialClientX(null); - }} - > -
- {label} - {href && ( - - )} -
-
-
{format(from, 'dd MMM yyyy')}
- {to && ( -
- - {format(to, 'dd MMM yyyy')} -
- )} -
-
- ); -}; - -export default ProjectsTimelineWidgetItem; diff --git a/client/src/components/Projects/ProjectsTimelineWidgetItem/index.tsx b/client/src/components/Projects/ProjectsTimelineWidgetItem/index.tsx deleted file mode 100644 index 8abd30e701..0000000000 --- a/client/src/components/Projects/ProjectsTimelineWidgetItem/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ProjectsTimelineWidgetItem'; diff --git a/client/src/components/Settings/ModalSettingsCalculatingUQScore/index.tsx b/client/src/components/Settings/ModalSettingsCalculatingUQScore/index.tsx deleted file mode 100644 index 4074153816..0000000000 --- a/client/src/components/Settings/ModalSettingsCalculatingUQScore/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalSettingsCalculatingUQScore'; diff --git a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/index.tsx b/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/index.tsx deleted file mode 100644 index 886be24fa6..0000000000 --- a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalSettingsCalculatingYourUniqueness'; diff --git a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/types.ts b/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/types.ts deleted file mode 100644 index 04c67e7364..0000000000 --- a/client/src/components/Settings/ModalSettingsCalculatingYourUniqueness/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import ModalProps from 'components/ui/Modal/types'; - -export default interface ModalSettingsCalculatingYourUniquenessProps { - modalProps: Omit; -} diff --git a/client/src/components/Settings/ModalSettingsRecalculatingScore/ModalSettingsRecalculatingScore.tsx b/client/src/components/Settings/ModalSettingsRecalculatingScore/ModalSettingsRecalculatingScore.tsx deleted file mode 100644 index c060abf828..0000000000 --- a/client/src/components/Settings/ModalSettingsRecalculatingScore/ModalSettingsRecalculatingScore.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { FC, memo } from 'react'; -import { useTranslation } from 'react-i18next'; - -import SettingsRecalculatingScore from 'components/Settings/SettingsRecalculatingScore'; -import Modal from 'components/ui/Modal'; - -import styles from './ModalSettingsRecalculatingScore.module.scss'; -import ModalSettingsRecalculatingScoreProps from './types'; - -const ModalSettingsRecalculatingScore: FC = ({ - modalProps, -}) => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); - - return ( - - - - ); -}; - -export default memo(ModalSettingsRecalculatingScore); diff --git a/client/src/components/Settings/ModalSettingsRecalculatingScore/index.tsx b/client/src/components/Settings/ModalSettingsRecalculatingScore/index.tsx deleted file mode 100644 index 4d7a5e86f3..0000000000 --- a/client/src/components/Settings/ModalSettingsRecalculatingScore/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './ModalSettingsRecalculatingScore'; diff --git a/client/src/components/Settings/Settings.module.scss b/client/src/components/Settings/Settings.module.scss new file mode 100644 index 0000000000..bd59bd8235 --- /dev/null +++ b/client/src/components/Settings/Settings.module.scss @@ -0,0 +1,56 @@ +.root { + padding-bottom: 8.8rem; + width: 100%; + display: flex; + flex-direction: column; + + @media #{$tablet-up} { + padding-bottom: 14.4rem; + } + + @media #{$desktop-up} { + padding-bottom: 0; + width: 42.4rem; + min-height: 0; + } + + .title { + @include fontBig($font-size-24); + padding: 0; + display: flex; + align-items: center; + min-height: 8.8rem; + width: 100%; + color: $color-octant-dark; + line-height: 1.4rem; + + @media #{$tablet-up} { + @include fontBig($font-size-32); + min-height: 14.4rem; + } + + @media #{$desktop-up} { + padding: 0 4rem; + } + } + + .mainInfoBoxWrapper { + flex-direction: column; + display: flex; + gap: 1.6rem; + width: 100%; + + @media #{$desktop-up} { + flex-direction: row; + } + } + + .boxesWrapper { + width: 100%; + + @media #{$desktop-up} { + padding: 0 4rem 4rem; + overflow: auto; + } + } +} diff --git a/client/src/components/Settings/Settings.tsx b/client/src/components/Settings/Settings.tsx new file mode 100644 index 0000000000..7918c7d313 --- /dev/null +++ b/client/src/components/Settings/Settings.tsx @@ -0,0 +1,40 @@ +import React, { ReactElement } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useAccount } from 'wagmi'; + +import SettingsCryptoMainValueBox from 'components/Settings/SettingsCryptoMainValueBox'; +import SettingsCurrencyBox from 'components/Settings/SettingsCurrencyBox'; +import SettingsMainInfoBox from 'components/Settings/SettingsMainInfoBox'; +import SettingsPatronModeBox from 'components/Settings/SettingsPatronModeBox'; +import SettingsShowOnboardingBox from 'components/Settings/SettingsShowOnboardingBox'; +import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; +import useIsPatronMode from 'hooks/queries/useIsPatronMode'; + +import styles from './Settings.module.scss'; + +const Settings = (): ReactElement => { + const { t } = useTranslation('translation', { keyPrefix: 'components.settings' }); + const { isConnected } = useAccount(); + + const { data: isPatronMode } = useIsPatronMode(); + const isProjectAdminMode = useIsProjectAdminMode(); + + return ( +
+
{t('title')}
+
+ {!isProjectAdminMode && ( +
+ +
+ )} + + + {isConnected && !isProjectAdminMode && } + {!isProjectAdminMode && !isPatronMode && } +
+
+ ); +}; + +export default Settings; diff --git a/client/src/components/Settings/SettingsAddressScore/index.tsx b/client/src/components/Settings/SettingsAddressScore/index.tsx deleted file mode 100644 index ab04a4525c..0000000000 --- a/client/src/components/Settings/SettingsAddressScore/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './SettingsAddressScore'; diff --git a/client/src/components/Settings/SettingsCalculatingUQScore/index.tsx b/client/src/components/Settings/SettingsCalculatingUQScore/index.tsx deleted file mode 100644 index e96f88b63f..0000000000 --- a/client/src/components/Settings/SettingsCalculatingUQScore/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './SettingsCalculatingUQScore'; diff --git a/client/src/components/Settings/SettingsCurrencyBox/SettingsCurrencyBox.module.scss b/client/src/components/Settings/SettingsCurrencyBox/SettingsCurrencyBox.module.scss index 011be12bf2..0fef6d5528 100644 --- a/client/src/components/Settings/SettingsCurrencyBox/SettingsCurrencyBox.module.scss +++ b/client/src/components/Settings/SettingsCurrencyBox/SettingsCurrencyBox.module.scss @@ -3,10 +3,6 @@ font-size: $font-size-12; margin: 1.6rem auto 0; - @media #{$desktop-up} { - font-size: $font-size-14; - } - .spacer { height: 2.4rem; width: 0.1rem; diff --git a/client/src/components/Settings/SettingsCurrencyBox/SettingsCurrencyBox.tsx b/client/src/components/Settings/SettingsCurrencyBox/SettingsCurrencyBox.tsx index 777112f9f4..9b93740481 100644 --- a/client/src/components/Settings/SettingsCurrencyBox/SettingsCurrencyBox.tsx +++ b/client/src/components/Settings/SettingsCurrencyBox/SettingsCurrencyBox.tsx @@ -5,9 +5,9 @@ import BoxRounded from 'components/ui/BoxRounded'; import InputSelect from 'components/ui/InputSelect'; import useSettingsStore from 'store/settings/store'; import { SettingsData } from 'store/settings/types'; -import { Options } from 'views/SettingsView/types'; import styles from './SettingsCurrencyBox.module.scss'; +import { Options } from './types'; const options: Options = [ { label: 'USD', value: 'usd' }, diff --git a/client/src/views/SettingsView/types.ts b/client/src/components/Settings/SettingsCurrencyBox/types.ts similarity index 100% rename from client/src/views/SettingsView/types.ts rename to client/src/components/Settings/SettingsCurrencyBox/types.ts diff --git a/client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.module.scss b/client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.module.scss deleted file mode 100644 index 7b2a53f78d..0000000000 --- a/client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.module.scss +++ /dev/null @@ -1,44 +0,0 @@ -.root { - width: 100%; - display: flex; - gap: 1.5rem; - margin-top: 1.6rem; - - .boxChildrenWrapper { - height: 100%; - } - - .box { - font-size: $font-size-12; - line-height: 2rem; - font-weight: $font-weight-bold; - display: flex; - align-items: center; - justify-content: center; - height: 6.4rem; - } - - .buttonLink { - display: block; - font-size: $font-size-12; - line-height: 2rem; - font-weight: $font-weight-semibold; - min-height: 2rem; - width: 100%; - height: 100%; - display: flex; - align-items: center; - - .buttonLinkText { - margin-left: 0.4rem; - } - - .buttonLinkArrowSvg { - margin-left: 0.4rem; - } - - @media #{$desktop-up} { - font-size: $font-size-14; - } - } -} diff --git a/client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.tsx b/client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.tsx deleted file mode 100644 index 6e1e3b63c5..0000000000 --- a/client/src/components/Settings/SettingsLinkBoxes/SettingsLinkBoxes.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { ReactNode, memo } from 'react'; -import { useTranslation } from 'react-i18next'; - -import BoxRounded from 'components/ui/BoxRounded'; -import Button from 'components/ui/Button'; -import Svg from 'components/ui/Svg'; -import { DISCORD_LINK, OCTANT_BUILD_LINK, OCTANT_DOCS } from 'constants/urls'; -import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import { arrowTopRight } from 'svg/misc'; - -import styles from './SettingsLinkBoxes.module.scss'; - -const SettingsLinkBoxes = (): ReactNode => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); - const { isDesktop } = useMediaQuery(); - - const mobileLinks = [ - { - href: OCTANT_BUILD_LINK, - label: isDesktop ? t('visitWebsite') : t('website'), // 'Website', - }, - { - href: OCTANT_DOCS, - label: isDesktop ? t('checkOutDocs') : t('docs'), // 'Docs', - }, - { - href: DISCORD_LINK, - label: isDesktop ? t('joinOurDiscord') : t('discord'), - }, - ]; - - return ( -
- {mobileLinks.map(({ href, label }) => ( - - - - ))} -
- ); -}; - -export default memo(SettingsLinkBoxes); diff --git a/client/src/components/Settings/SettingsLinkBoxes/index.tsx b/client/src/components/Settings/SettingsLinkBoxes/index.tsx deleted file mode 100644 index 957a823fa9..0000000000 --- a/client/src/components/Settings/SettingsLinkBoxes/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './SettingsLinkBoxes'; diff --git a/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.module.scss b/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.module.scss index a04dc60b9d..bb22949417 100644 --- a/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.module.scss +++ b/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.module.scss @@ -1,8 +1,8 @@ .root { padding: 4.4rem 2.2rem 0.5rem; - @media #{$desktop-up} { - padding: 4.6rem 2.2rem 4.3rem; + @media #{$tablet-up} { + padding: 4.6rem 2.2rem 1rem; } .infoTitle { @@ -17,7 +17,7 @@ font-weight: $font-weight-bold; line-height: 1.6rem; - @media #{$desktop-up} { + @media #{$tablet-up} { font-size: $font-size-14; } } @@ -33,10 +33,9 @@ color: $color-octant-grey5; } - @media #{$desktop-up} { - justify-content: flex-end; - min-height: 8rem; + @media #{$tablet-up} { margin-top: 3.6rem; + min-height: 8rem; .info { line-height: 2rem; @@ -48,17 +47,5 @@ color: $color-octant-green; } } - - .buttonLink { - display: block; - font-size: $font-size-12; - line-height: 2rem; - font-weight: $font-weight-semibold; - min-height: 2rem; - - @media #{$desktop-up} { - font-size: $font-size-14; - } - } } } diff --git a/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.tsx b/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.tsx index b1df74421b..52c1444814 100644 --- a/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.tsx +++ b/client/src/components/Settings/SettingsMainInfoBox/SettingsMainInfoBox.tsx @@ -3,9 +3,7 @@ import React, { ReactNode, memo } from 'react'; import { useTranslation } from 'react-i18next'; import BoxRounded from 'components/ui/BoxRounded'; -import Button from 'components/ui/Button'; import Svg from 'components/ui/Svg'; -import { TERMS_OF_USE } from 'constants/urls'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; import { octantWordmark } from 'svg/logo'; @@ -35,15 +33,7 @@ const SettingsMainInfoBox = (): ReactNode => {
{t('golemFoundationProject')}
-
{t('poweredByCoinGeckoApi')}
- +
{t('poweredByCoinGeckoApi')}
); diff --git a/client/src/components/Settings/SettingsPatronModeBox/SettingsPatronModeBox.module.scss b/client/src/components/Settings/SettingsPatronModeBox/SettingsPatronModeBox.module.scss index 1d4cf31367..2913271213 100644 --- a/client/src/components/Settings/SettingsPatronModeBox/SettingsPatronModeBox.module.scss +++ b/client/src/components/Settings/SettingsPatronModeBox/SettingsPatronModeBox.module.scss @@ -9,3 +9,11 @@ justify-content: center; margin-left: 1.2rem; } + +.tooltipBox { + .tooltip { + @media #{$desktop-up} { + left: -13.6rem; + } + } +} diff --git a/client/src/components/Settings/SettingsPatronModeBox/SettingsPatronModeBox.tsx b/client/src/components/Settings/SettingsPatronModeBox/SettingsPatronModeBox.tsx index e121a68ea8..f7b28278f6 100644 --- a/client/src/components/Settings/SettingsPatronModeBox/SettingsPatronModeBox.tsx +++ b/client/src/components/Settings/SettingsPatronModeBox/SettingsPatronModeBox.tsx @@ -25,9 +25,11 @@ const SettingsPatronModeBox = (): ReactElement => {
{t('enablePatronMode')} void; -} diff --git a/client/src/components/Settings/SettingsShowTipsBox/SettingsShowTipsBox.tsx b/client/src/components/Settings/SettingsShowTipsBox/SettingsShowTipsBox.tsx deleted file mode 100644 index 9d906d8c92..0000000000 --- a/client/src/components/Settings/SettingsShowTipsBox/SettingsShowTipsBox.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { ReactElement } from 'react'; -import { useTranslation } from 'react-i18next'; - -import SettingsToggleBox from 'components/Settings/SettingsToggleBox'; -import useSettingsStore from 'store/settings/store'; - -const SettingsShowTipsBox = (): ReactElement => { - const { t } = useTranslation('translation', { keyPrefix: 'views.settings' }); - const { setAreOctantTipsAlwaysVisible, areOctantTipsAlwaysVisible } = useSettingsStore(state => ({ - areOctantTipsAlwaysVisible: state.data.areOctantTipsAlwaysVisible, - setAreOctantTipsAlwaysVisible: state.setAreOctantTipsAlwaysVisible, - })); - - return ( - setAreOctantTipsAlwaysVisible(event.target.checked)} - > - {t('alwaysShowOctantTips')} - - ); -}; - -export default SettingsShowTipsBox; diff --git a/client/src/components/Settings/SettingsShowTipsBox/index.tsx b/client/src/components/Settings/SettingsShowTipsBox/index.tsx deleted file mode 100644 index 55c60a1397..0000000000 --- a/client/src/components/Settings/SettingsShowTipsBox/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './SettingsShowTipsBox'; diff --git a/client/src/components/Settings/SettingsToggleBox/SettingsToggleBox.module.scss b/client/src/components/Settings/SettingsToggleBox/SettingsToggleBox.module.scss index ae242dbfc7..9ce12e3427 100644 --- a/client/src/components/Settings/SettingsToggleBox/SettingsToggleBox.module.scss +++ b/client/src/components/Settings/SettingsToggleBox/SettingsToggleBox.module.scss @@ -5,10 +5,6 @@ &:not(:first-child) { margin: 1.6rem auto 0; } - - @media #{$desktop-up} { - font-size: $font-size-14; - } } .inputToggle { diff --git a/client/src/components/Settings/SettingsUniquenessScoreAddresses/index.tsx b/client/src/components/Settings/SettingsUniquenessScoreAddresses/index.tsx deleted file mode 100644 index 750c5b4fbf..0000000000 --- a/client/src/components/Settings/SettingsUniquenessScoreAddresses/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './SettingsUniquenessScoreAddresses'; diff --git a/client/src/components/Settings/SettingsUniquenessScoreAddresses/types.ts b/client/src/components/Settings/SettingsUniquenessScoreAddresses/types.ts deleted file mode 100644 index 0fc57c88bc..0000000000 --- a/client/src/components/Settings/SettingsUniquenessScoreAddresses/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface SettingsUniquenessScoreAddressesProps { - isFetchingScore?: boolean; -} diff --git a/client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.module.scss b/client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.module.scss deleted file mode 100644 index c5e2a130d8..0000000000 --- a/client/src/components/Settings/SettingsUniquenessScoreBox/SettingsUniquenessScoreBox.module.scss +++ /dev/null @@ -1,38 +0,0 @@ -.root { - padding: 4.4rem 2.2rem 0.5rem; - height: 26.4rem; - - @media #{$desktop-up} { - padding: 4.5rem 2.2rem 2.5rem; - } - - .titleSuffix { - cursor: pointer; - color: $color-octant-green; - font-size: $font-size-10; - font-weight: $font-weight-bold; - } - - .visitDashboard { - font-size: 1rem; - padding: 0 0 0.8rem 0; - margin: 0 0 0.8rem 0; - border-bottom: 0.1rem solid $color-octant-grey3; - } - - .buttonsWrapper { - margin-bottom: 1.6rem; - width: 100%; - display: flex; - justify-content: space-between; - - .button { - cursor: pointer; - flex: 1; - - &:first-child { - margin-right: 1.2rem; - } - } - } -} diff --git a/client/src/components/Settings/SettingsUniquenessScoreBox/index.tsx b/client/src/components/Settings/SettingsUniquenessScoreBox/index.tsx deleted file mode 100644 index 26449d2f5a..0000000000 --- a/client/src/components/Settings/SettingsUniquenessScoreBox/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line no-restricted-exports -export { default } from './SettingsUniquenessScoreBox'; diff --git a/client/src/components/Settings/index.tsx b/client/src/components/Settings/index.tsx new file mode 100644 index 0000000000..6b65b09ebe --- /dev/null +++ b/client/src/components/Settings/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './Settings'; diff --git a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.module.scss b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.module.scss index dc3ce36d05..878d38722b 100644 --- a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.module.scss +++ b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.module.scss @@ -1,41 +1,33 @@ .root { z-index: $z-index-4; + padding: 0; - &.isAllocatedTo { - svg path { - stroke: $color-white; - // fill: $color-white; -- no fill here, it makes the stroke bigger. - } - } + &.variant--cta { + min-width: 15rem; + font-size: $font-size-12; + padding: 0 1rem 0 1.6rem; + justify-content: right; + background-color: $color-octant-dark; + cursor: pointer; - &:not(.isAllocatedTo) { - &.isAddedToAllocate { - svg path { - stroke: $color-octant-orange; - fill: $color-octant-orange; - } + &:hover { + background-color: $color-octant-grey10; } - } - .svgWrapper { - display: flex; - } - - &.isArchivedProject { - &:hover { - background: transparent; - cursor: default; + &:active { + background-color: $color-octant-dark; } - svg path { - stroke: $color-octant-grey1; + &.isDisabled { + cursor: default; + color: $color-octant-grey1; + background-color: $color-octant-grey2; } - &.isAllocatedTo { - svg circle { - stroke: $color-octant-grey5; - fill: $color-octant-grey5; - } + .wrapper { + display: flex; + align-items: center; + margin: 0 auto; } } } diff --git a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.tsx b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.tsx index a4ca3ab154..e35de42bbe 100644 --- a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.tsx +++ b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocate.tsx @@ -3,11 +3,10 @@ import { useAnimate } from 'framer-motion'; import React, { FC, useMemo, useState, memo } from 'react'; import { useTranslation } from 'react-i18next'; +import ButtonAddToAllocateIcon from 'components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon'; import Button from 'components/ui/Button'; -import Svg from 'components/ui/Svg'; import Tooltip from 'components/ui/Tooltip'; import useIsPatronMode from 'hooks/queries/useIsPatronMode'; -import { checkMark, heart } from 'svg/misc'; import styles from './ButtonAddToAllocate.module.scss'; import ButtonAddToAllocateProps from './types'; @@ -19,17 +18,21 @@ const ButtonAddToAllocate: FC = ({ isAddedToAllocate, isAllocatedTo, isArchivedProject, + variant = 'iconOnly', }) => { - const { t } = useTranslation('translation', { + const { i18n, t } = useTranslation('translation', { keyPrefix: 'components.dedicated.buttonAddToAllocate', }); const [scope, animate] = useAnimate(); const { data: isPatronMode } = useIsPatronMode(); const [isTooltipClicked, setIsTooltipClicked] = useState(false); const [isTooltipVisible, setIsTooltipVisible] = useState(false); + + const isDisabled = isArchivedProject || isPatronMode; + const tooltipText = useMemo(() => { if (isArchivedProject && isAllocatedTo) { - return t('donated'); + return i18n.t('common.donated'); } if (isAddedToAllocate && isTooltipClicked) { return t('saved'); @@ -44,6 +47,17 @@ const ButtonAddToAllocate: FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [isAddedToAllocate, isTooltipClicked, isArchivedProject, isAllocatedTo]); + const ctaButtonText = useMemo(() => { + if (isAllocatedTo) { + return i18n.t('common.donated'); + } + if (isAddedToAllocate && !isArchivedProject) { + return t('savedProject'); + } + return t('saveProject'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isAddedToAllocate, isAllocatedTo, isArchivedProject]); + const handleTooltipVisibilityChange = (isVisible: boolean) => { setIsTooltipVisible(isVisible); if (!isVisible) { @@ -51,41 +65,69 @@ const ButtonAddToAllocate: FC = ({ } }; + const animateHeartIcon = () => { + animate(scope?.current, { scale: [1.2, 1] }, { duration: 0.25, ease: 'easeIn' }); + }; + return (
- -
-
+ variant === 'iconOnly' ? ( + { + if (isTooltipVisible) { + setIsTooltipClicked(true); + } + animateHeartIcon(); + }} + onVisibilityChange={handleTooltipVisibilityChange} + position="top" + showDelay={1000} + text={tooltipText} + variant="small" + > + + + ) : null } - isDisabled={isArchivedProject || isPatronMode} - onClick={onClick} - variant="iconOnly" - /> + isDisabled={isDisabled} + onClick={() => { + onClick(); + animateHeartIcon(); + }} + variant={variant} + > + {variant === 'cta' && ( +
+ {ctaButtonText} + +
+ )} + ); }; diff --git a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/ButtonAddToAllocateIcon.module.scss b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/ButtonAddToAllocateIcon.module.scss new file mode 100644 index 0000000000..fc32498eb0 --- /dev/null +++ b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/ButtonAddToAllocateIcon.module.scss @@ -0,0 +1,43 @@ +.root { + display: flex; + + &.isDisabled { + stroke: $color-octant-grey1; + } + + &.isAllocatedTo { + svg path { + stroke: $color-white; + // fill: $color-white; -- no fill here, it makes the stroke bigger. + } + } + + &:not(.isAllocatedTo) { + &:not(.isArchivedProject) { + &.isAddedToAllocate { + svg path { + stroke: $color-octant-orange; + fill: $color-octant-orange; + } + } + } + } + + &.isArchivedProject { + &:hover { + background: transparent; + cursor: default; + } + + svg path { + stroke: $color-octant-grey1; + } + + &.isAllocatedTo { + svg circle { + stroke: $color-octant-grey5; + fill: $color-octant-grey5; + } + } + } +} diff --git a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/ButtonAddToAllocateIcon.tsx b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/ButtonAddToAllocateIcon.tsx new file mode 100644 index 0000000000..3a90866c84 --- /dev/null +++ b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/ButtonAddToAllocateIcon.tsx @@ -0,0 +1,28 @@ +import cx from 'classnames'; +import React, { forwardRef } from 'react'; + +import Svg from 'components/ui/Svg'; +import { checkMark, heart } from 'svg/misc'; + +import styles from './ButtonAddToAllocateIcon.module.scss'; +import ButtonAddToAllocateIconProps from './types'; + +const ButtonAddToAllocateIcon = ( + { isDisabled, isAllocatedTo, isArchivedProject, isAddedToAllocate }, + ref, +) => ( +
+ +
+); + +export default forwardRef(ButtonAddToAllocateIcon); diff --git a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/index.tsx b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/index.tsx new file mode 100644 index 0000000000..5a0b3d9c03 --- /dev/null +++ b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './ButtonAddToAllocateIcon'; diff --git a/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/types.ts b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/types.ts new file mode 100644 index 0000000000..3aefccd1b8 --- /dev/null +++ b/client/src/components/shared/ButtonAddToAllocate/ButtonAddToAllocateIcon/types.ts @@ -0,0 +1,6 @@ +export default interface ButtonAddToAllocateIconProps { + isAddedToAllocate: boolean; + isAllocatedTo: boolean; + isArchivedProject: boolean; + isDisabled: boolean; +} diff --git a/client/src/components/shared/ButtonAddToAllocate/types.ts b/client/src/components/shared/ButtonAddToAllocate/types.ts index 13c512bcc6..9262a7f52b 100644 --- a/client/src/components/shared/ButtonAddToAllocate/types.ts +++ b/client/src/components/shared/ButtonAddToAllocate/types.ts @@ -5,4 +5,5 @@ export default interface ButtonAddToAllocateProps { isAllocatedTo: boolean; isArchivedProject?: boolean; onClick: () => void; + variant?: 'cta' | 'iconOnly'; } diff --git a/client/src/components/shared/Grid/Grid.module.scss b/client/src/components/shared/Grid/Grid.module.scss new file mode 100644 index 0000000000..fbbc61e90d --- /dev/null +++ b/client/src/components/shared/Grid/Grid.module.scss @@ -0,0 +1,20 @@ +$gridGap: 1.6rem; + +.root { + display: grid; + grid-template-columns: 100%; + gap: $gridGap; + width: 100%; + + @media #{$tablet-up} { + grid-template-columns: repeat(2, calc(50% - calc($gridGap / 2))); + } + + @media #{$desktop-up} { + grid-template-columns: repeat(3, calc(calc(100% / 3) - calc(2 * $gridGap / 3))); + } + + @media #{$large-desktop-up} { + grid-template-columns: repeat(4, calc(25% - calc(3 * $gridGap / 4))); + } +} diff --git a/client/src/components/shared/Grid/Grid.tsx b/client/src/components/shared/Grid/Grid.tsx new file mode 100644 index 0000000000..ed667f0dc9 --- /dev/null +++ b/client/src/components/shared/Grid/Grid.tsx @@ -0,0 +1,15 @@ +import cx from 'classnames'; +import React, { FC } from 'react'; + +import styles from './Grid.module.scss'; +import GridProps from './types'; + +const Grid: FC = ({ children, dataTest = 'Grid', className }) => { + return ( +
+ {children} +
+ ); +}; + +export default Grid; diff --git a/client/src/components/shared/Grid/GridTile/GridTile.module.scss b/client/src/components/shared/Grid/GridTile/GridTile.module.scss new file mode 100644 index 0000000000..28636532cc --- /dev/null +++ b/client/src/components/shared/Grid/GridTile/GridTile.module.scss @@ -0,0 +1,35 @@ +.root { + padding: 0; + display: flex; + background: $color-white; + border-radius: $border-radius-16; + width: 100%; + flex-direction: column; + display: flex; + max-height: 100%; +} + +.titleWrapper { + padding: 2.4rem; + display: flex; + align-items: center; + margin: 0; + + &.showTitleDivider { + border-bottom: 0.1rem solid $color-octant-grey3; + } + + .title { + padding: 0 1rem; + height: 3.2rem; + display: flex; + align-items: center; + justify-content: start; + font-size: $font-size-14; + color: $color-octant-green; + font-weight: $font-weight-bold; + color: $color-octant-dark; + background: $color-octant-grey8; + border-radius: $border-radius-10; + } +} diff --git a/client/src/components/shared/Grid/GridTile/GridTile.tsx b/client/src/components/shared/Grid/GridTile/GridTile.tsx new file mode 100644 index 0000000000..1b366c528d --- /dev/null +++ b/client/src/components/shared/Grid/GridTile/GridTile.tsx @@ -0,0 +1,26 @@ +import cx from 'classnames'; +import React, { FC } from 'react'; + +import styles from './GridTile.module.scss'; +import GridTileProps from './types'; + +const GridTile: FC = ({ + title, + titleSuffix, + children, + className, + dataTest = 'GridTile', + showTitleDivider, +}) => ( +
+
+
+ {title} +
+ {titleSuffix} +
+ {children} +
+); + +export default GridTile; diff --git a/client/src/components/shared/Grid/GridTile/index.tsx b/client/src/components/shared/Grid/GridTile/index.tsx new file mode 100644 index 0000000000..af5c86e658 --- /dev/null +++ b/client/src/components/shared/Grid/GridTile/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './GridTile'; diff --git a/client/src/components/shared/Grid/GridTile/types.ts b/client/src/components/shared/Grid/GridTile/types.ts new file mode 100644 index 0000000000..eaadb3f48b --- /dev/null +++ b/client/src/components/shared/Grid/GridTile/types.ts @@ -0,0 +1,12 @@ +import { ReactNode } from 'react'; + +type GridTileProps = { + children: ReactNode; + className?: string; + dataTest?: string; + showTitleDivider?: boolean; + title: string | ReactNode; + titleSuffix?: ReactNode; +}; + +export default GridTileProps; diff --git a/client/src/components/shared/Grid/index.tsx b/client/src/components/shared/Grid/index.tsx new file mode 100644 index 0000000000..0139e192a1 --- /dev/null +++ b/client/src/components/shared/Grid/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './Grid'; diff --git a/client/src/components/shared/Grid/types.ts b/client/src/components/shared/Grid/types.ts new file mode 100644 index 0000000000..d098b5fb5b --- /dev/null +++ b/client/src/components/shared/Grid/types.ts @@ -0,0 +1,7 @@ +import { ReactNode } from 'react'; + +export default interface GridProps { + children: ReactNode; + className?: string; + dataTest?: string; +} diff --git a/client/src/components/shared/Layout/Layout.module.scss b/client/src/components/shared/Layout/Layout.module.scss index 52e20f9a0d..7353fd40dc 100644 --- a/client/src/components/shared/Layout/Layout.module.scss +++ b/client/src/components/shared/Layout/Layout.module.scss @@ -1,202 +1,109 @@ -$headerNavigationBorderRadius: $border-radius-32; -$headerMarginTop: 1.6rem; -$bodyPaddingTop: calc(1.6rem + $headerMarginTop + $headerHeight); - .root { display: flex; flex-direction: column; height: 100%; min-height: 100%; -} - -.root, -.headerWrapper { - @include layoutFloatingElementWidth(); - margin: 0 auto; -} - -.headerWrapper { - position: fixed; - top: 0; - width: 100%; - z-index: $z-index-5; - - &.isAbsoluteHeaderPosition { - position: absolute; - } -} - -.header { - display: flex; - justify-content: space-between; + padding: 0 $layout-horizontal-padding-small; align-items: center; - height: $headerHeight; - border-radius: $headerNavigationBorderRadius; - background: $color-white; - padding: 1.6rem; - margin: $headerMarginTop $layoutMarginHorizontal 0; - box-shadow: $box-shadow-1; - z-index: $z-index-3; -} + margin: 8rem auto 0; -.headerBlur { - @include layoutOverflowBlurCommonProperties(); - height: $bodyPaddingTop; - top: 0; - -webkit-mask-image: linear-gradient( - to bottom, - rgba(0, 0, 0, 1), - rgba(0, 0, 0, 0.9), - rgba(0, 0, 0, 0.8), - rgba(0, 0, 0, 0.05) - ); - mask-image: linear-gradient( - to bottom, - rgba(0, 0, 0, 1), - rgba(0, 0, 0, 0.9), - rgba(0, 0, 0, 0.8), - rgba(0, 0, 0, 0.05) - ); - - &.isAbsoluteHeaderPosition { - position: absolute; + @media #{$large-desktop-up} { + padding: 0 $layout-horizontal-padding-large; } -} - -.logo { - position: relative; - height: 4rem; - cursor: pointer; -} - -.testnetIndicatorWrapper { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - top: -1rem; - right: -0.2rem; - transform: translate(75%, 0); - background: $color-white; - height: 1.6rem; - box-sizing: content-box; - border-radius: 1.2rem; - padding: 0.4rem 0.5rem; -} -.testnetIndicator { - display: flex; - align-items: center; - font-size: $font-size-10; - font-weight: $font-weight-bold; - text-transform: uppercase; - color: $color-white; - letter-spacing: 0.1rem; - background: $color-octant-orange; - border-radius: 0.8rem; - height: 1.6rem; - padding: 0 0.8rem; - user-select: none; -} + &.isProjectView { + padding: 0; -.buttons { - display: flex; - align-items: center; -} - -.buttonWallet { - margin-left: 1.6rem; - cursor: pointer; - - &.isWalletModalOpen { - transform: rotate(180deg); + @media #{$desktop-up} { + padding: 0 $layout-horizontal-padding-large; + } } -} - -.profileInfo { - display: flex; - align-items: center; - font-size: $font-size-14; - cursor: pointer; - padding: $layoutMarginHorizontal; - margin: (-$layoutMarginHorizontal); -} -.walletInfo { - display: flex; - flex-direction: column; - align-items: flex-end; -} + .section { + width: 100%; -.addressWrapper { - display: flex; + @media #{$large-desktop-up} { + width: $layout-section-width-large-desktop; + } + } - .badge { - display: flex; - align-items: center; - justify-content: center; - font-size: $font-size-10; - text-transform: uppercase; - letter-spacing: 0.05rem; - font-weight: $font-weight-bold; - color: $color-white; - border-radius: $border-radius-04; - height: 1.6rem; - margin-right: 0.8rem; - background: $color-octant-purple; - width: 5.6rem; + .topBarWrapper { + position: absolute; + top: 0; + background: $color-octant-grey3; + width: 100%; + padding: 0 $layout-horizontal-padding-small; + z-index: $z-index-5; + backdrop-filter: blur(2.5rem); + border-bottom: 0.1rem solid $color-octant-grey1; + + &.isPatronMode { + background: $color-octant-purple3-20; + } &.isProjectAdminMode { - background: $color-octant-green; - width: 4.4rem; + background: $color-octant-green5-60; } - } -} -.address { - @include ethereumAddress(); - font-weight: $font-weight-bold; + @media #{$tablet-up} { + position: fixed; + } - &.isProjectAdminMode { - color: $color-octant-green; - } + @media #{$large-desktop-up} { + padding: 0 $layout-horizontal-padding-large; + } - &.isPatronMode { - color: $color-octant-purple; + .section { + @media #{$large-desktop-up} { + margin: 0 auto; + } + } } -} -.budget, -.allocationPeriod { - font-size: $font-size-14; -} + .body { + @include layoutMaxWidth(); + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + flex-grow: 1; + flex-shrink: 0; + // 15.3 rem -> height of navbar with AllocationNavigation on mobile + min-height: calc(100% - $layout-top-bar-height - 15.3rem - $layout-navbar-bottom); + + @media #{$tablet-up} { + // 20.1 rem -> height of navbar with AllocationNavigation on tablet + min-height: calc(100% - $layout-top-bar-height - 20.1rem - $layout-navbar-bottom); + } -.highlighted { - color: $color-octant-orange; - font-weight: $font-weight-bold; -} + &.isNavigationBottomSuffix { + padding-bottom: 20rem; -.body { - display: flex; - flex-direction: column; - align-items: center; - justify-content: flex-start; - flex: 1; - padding: $bodyPaddingTop $layoutMarginHorizontal 12rem; + @media #{$desktop-up} { + padding-bottom: 24.8rem; + } + } - @media #{$desktop-up} { - padding-bottom: 15.2rem; + &.isLoading { + justify-content: center; + } } - &.isNavigationBottomSuffix { - padding-bottom: 20rem; + .footerWrapper { + width: calc(100% + 2 * $layout-horizontal-padding-small); + border-top: 0.1rem solid $color-octant-grey1; + margin-top: 8rem; + padding: 0 $layout-horizontal-padding-small; + display: flex; + justify-content: center; @media #{$desktop-up} { - padding-bottom: 24.8rem; + margin-top: 4rem; } - } - &.isLoading { - justify-content: center; + @media #{$large-desktop-up} { + width: calc(100% + 2 * $layout-horizontal-padding-large); + padding: 0 $layout-horizontal-padding-large; + } } } diff --git a/client/src/components/shared/Layout/Layout.tsx b/client/src/components/shared/Layout/Layout.tsx index f4dd3a3119..1b274c4d14 100644 --- a/client/src/components/shared/Layout/Layout.tsx +++ b/client/src/components/shared/Layout/Layout.tsx @@ -1,40 +1,20 @@ import cx from 'classnames'; -import React, { FC, useState, Fragment, useMemo, useEffect } from 'react'; -import { Trans, useTranslation } from 'react-i18next'; -import { useLocation, useMatch, useNavigate } from 'react-router-dom'; -import { useAccount } from 'wagmi'; +import React, { FC, Fragment, useEffect, useRef } from 'react'; +import { useLocation } from 'react-router-dom'; +import LayoutFooter from 'components/shared/Layout/LayoutFooter'; import LayoutNavbar from 'components/shared/Layout/LayoutNavbar'; +import LayoutTopBar from 'components/shared/Layout/LayoutTopBar'; import ModalLayoutConnectWallet from 'components/shared/Layout/ModalLayoutConnectWallet'; import ModalLayoutWallet from 'components/shared/Layout/ModalLayoutWallet'; -import Button from 'components/ui/Button'; import Loader from 'components/ui/Loader'; -import Svg from 'components/ui/Svg'; -import { ELEMENT_POSITION_FIXED_CLASSNAME } from 'constants/css'; import { LAYOUT_BODY_ID } from 'constants/domElementsIds'; -import { - adminNavigationTabs, - navigationTabs as navigationTabsDefault, - patronNavigationTabs, -} from 'constants/navigationTabs/navigationTabs'; -import networkConfig from 'constants/networkConfig'; -import useEpochAndAllocationTimestamps from 'hooks/helpers/useEpochAndAllocationTimestamps'; -import useGetValuesToDisplay from 'hooks/helpers/useGetValuesToDisplay'; import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; -import useCurrentEpoch from 'hooks/queries/useCurrentEpoch'; -import useIndividualReward from 'hooks/queries/useIndividualReward'; -import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; import useIsPatronMode from 'hooks/queries/useIsPatronMode'; -import useUserTOS from 'hooks/queries/useUserTOS'; import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; -import useSettingsStore from 'store/settings/store'; -import { octant } from 'svg/logo'; -import { chevronBottom } from 'svg/misc'; -import { chevronLeft } from 'svg/navigation'; -import getDifferenceInWeeks from 'utils/getDifferenceInWeeks'; -import getIsPreLaunch from 'utils/getIsPreLaunch'; -import getTimeDistance from 'utils/getTimeDistance'; -import truncateEthAddress from 'utils/truncateEthAddress'; +import useLayoutStore from 'store/layout/store'; +import SyncView from 'views/SyncView'; import styles from './Layout.module.scss'; import LayoutProps from './types'; @@ -43,256 +23,134 @@ const Layout: FC = ({ children, dataTest, navigationBottomSuffix, - isHeaderVisible = true, isLoading, isNavigationVisible = true, classNameBody, - isAbsoluteHeaderPosition = false, - showHeaderBlur = true, + isSyncingInProgress, }) => { + const { isMobile, isDesktop } = useMediaQuery(); + const isProjectAdminMode = useIsProjectAdminMode(); const { data: isPatronMode } = useIsPatronMode(); - const { i18n, t } = useTranslation('translation', { keyPrefix: 'layouts.main' }); - const [isModalConnectWalletOpen, setIsModalConnectWalletOpen] = useState(false); - const [isWalletModalOpen, setIsWalletModalOpen] = useState(false); - const { address, isConnected } = useAccount(); - const { data: individualReward } = useIndividualReward(); - const { data: currentEpoch } = useCurrentEpoch(); - const { timeCurrentAllocationEnd, timeCurrentEpochEnd } = useEpochAndAllocationTimestamps(); - const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen(); + + const ref = useRef(null); + const topBarWrapperRef = useRef(null); + const scrollRef = useRef(window.scrollY); + const lastScrollYUpRef = useRef(0); const { pathname } = useLocation(); - const navigate = useNavigate(); - const { data: isUserTOSAccepted } = useUserTOS(); - const isProjectAdminMode = useIsProjectAdminMode(); + + const isProjectView = pathname.includes(`${ROOT_ROUTES.project.absolute}/`); + const { - data: { isCryptoMainValueDisplay }, - } = useSettingsStore(({ data }) => ({ + setIsWalletModalOpen, + setIsConnectWalletModalOpen, + data: { isWalletModalOpen, isConnectWalletModalOpen }, + } = useLayoutStore(state => ({ data: { - isCryptoMainValueDisplay: data.isCryptoMainValueDisplay, + isConnectWalletModalOpen: state.data.isConnectWalletModalOpen, + isWalletModalOpen: state.data.isWalletModalOpen, }, + setIsConnectWalletModalOpen: state.setIsConnectWalletModalOpen, + setIsWalletModalOpen: state.setIsWalletModalOpen, })); - const isPreLaunch = getIsPreLaunch(currentEpoch); - const isAllocationRoot = !!useMatch(ROOT_ROUTES.allocation.absolute); - const isUseMatchProject = !!useMatch(ROOT_ROUTES.projectWithAddress.absolute); - const isUseMatchProjectWithAddress = !!useMatch(ROOT_ROUTES.projectWithAddress.absolute); - const isProjectRoot = isUseMatchProject || isUseMatchProjectWithAddress; - const isProjectsRoot = !!useMatch(ROOT_ROUTES.projects.absolute); - const getValuesToDisplay = useGetValuesToDisplay(); - - const showAllocationPeriod = isAllocationRoot || isProjectRoot || isProjectsRoot; - - const getCurrentPeriod = () => { - if (isDecisionWindowOpen && timeCurrentAllocationEnd) { - return getTimeDistance(Date.now(), new Date(timeCurrentAllocationEnd).getTime()); - } - if (!isDecisionWindowOpen && timeCurrentEpochEnd) { - return getTimeDistance(Date.now(), new Date(timeCurrentEpochEnd).getTime()); - } - return ''; - }; - const [currentPeriod, setCurrentPeriod] = useState(() => getCurrentPeriod()); - - const truncatedEthAddress = useMemo(() => address && truncateEthAddress(address), [address]); - - const tabsWithIsActive = useMemo(() => { - let tabs = navigationTabsDefault; - - if (isPatronMode) { - tabs = patronNavigationTabs; - } - if (isProjectAdminMode) { - tabs = adminNavigationTabs; - } - - return tabs.map(tab => { - const isProjectView = - pathname.includes(`${ROOT_ROUTES.project.absolute}/`) && - tab.to === ROOT_ROUTES.projects.absolute; - return { - ...tab, - icon: isProjectView ? chevronLeft : tab.icon, - isActive: tab.isActive || pathname === tab.to || isProjectView, - isDisabled: isPreLaunch && tab.to !== ROOT_ROUTES.earn.absolute, - }; - }); - }, [isPatronMode, isProjectAdminMode, isPreLaunch, pathname]); - - const isAllocationPeriodIsHighlighted = useMemo(() => { - if (isDecisionWindowOpen && timeCurrentAllocationEnd) { - return getDifferenceInWeeks(Date.now(), new Date(timeCurrentAllocationEnd).getTime()) < 1; - } - if (!isDecisionWindowOpen && timeCurrentEpochEnd) { - return getDifferenceInWeeks(Date.now(), new Date(timeCurrentEpochEnd).getTime()) < 1; - } - return false; - }, [isDecisionWindowOpen, timeCurrentAllocationEnd, timeCurrentEpochEnd]); - - const individualRewardText = useMemo(() => { - if (currentEpoch === 1 || individualReward === 0n || !isDecisionWindowOpen) { - return i18n.t('layouts.main.noRewardsYet'); - } - if (currentEpoch === undefined || individualReward === undefined) { - return i18n.t('layouts.main.loadingRewardBudget'); + // Logic that hides TopBar when scrolling down and shows when scrolling up (only on mobile devices) + useEffect(() => { + if (!topBarWrapperRef?.current) { + return; } - return i18n.t('common.rewards', { - rewards: getValuesToDisplay({ - cryptoCurrency: 'ethereum', - showCryptoSuffix: true, - valueCrypto: individualReward, - }).primary, - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [individualReward, currentEpoch, isDecisionWindowOpen, isCryptoMainValueDisplay]); - - const onLogoClick = () => { - if (pathname === ROOT_ROUTES.projects.absolute) { - window.scrollTo({ behavior: 'smooth', top: 0 }); + const topBarWrapperEl = topBarWrapperRef.current; + + const listener = e => { + if (e.target.body.className === 'bodyFixed' || window.scrollY < 0) { + return; + } + const { offsetTop, clientHeight } = topBarWrapperEl; + + if (window.scrollY > scrollRef.current) { + topBarWrapperEl.style.position = 'absolute'; + if (window.scrollY < lastScrollYUpRef.current + clientHeight) { + topBarWrapperEl.style.top = `${lastScrollYUpRef.current}px`; + } else if (window.scrollY >= clientHeight) { + topBarWrapperEl.style.top = `${window.scrollY - clientHeight}px`; + } + } else { + lastScrollYUpRef.current = window.scrollY; + if (window.scrollY <= offsetTop) { + topBarWrapperEl.style.top = '0px'; + topBarWrapperEl.style.position = 'fixed'; + } + } + + scrollRef.current = window.scrollY; + }; + + if (!isMobile) { return; } - navigate(ROOT_ROUTES.projects.absolute); - }; + lastScrollYUpRef.current = window.scrollY; + document.addEventListener('scroll', listener); - useEffect(() => { - const intervalId = setInterval(() => { - setCurrentPeriod(getCurrentPeriod()); - }, 1000); + return () => { + topBarWrapperEl.style.position = 'fixed'; + topBarWrapperEl.style.top = '0px'; + document.removeEventListener('scroll', listener); + }; + }, [isMobile]); - return () => clearInterval(intervalId); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isDecisionWindowOpen, timeCurrentAllocationEnd, timeCurrentEpochEnd]); + if (isSyncingInProgress) { + return ; + } return ( - setIsWalletModalOpen(false), - }} - /> - setIsModalConnectWalletOpen(false), - }} - /> -
- {isHeaderVisible && ( - - {showHeaderBlur &&
} -
-
-
- - {networkConfig.isTestnet && ( -
-
{networkConfig.name}
-
- )} -
-
- {isConnected && address ? ( -
isUserTOSAccepted && setIsWalletModalOpen(true)} - > -
-
- {(isProjectAdminMode || isPatronMode) && ( -
- {isProjectAdminMode ? t('admin') : t('patron')} -
- )} - -
- {truncatedEthAddress} -
-
- {!!currentEpoch && - currentEpoch > 1 && - (showAllocationPeriod ? ( -
- , - ]} - i18nKey={ - isDecisionWindowOpen - ? 'layouts.main.allocationEndsIn' - : 'layouts.main.allocationStartsIn' - } - values={{ currentPeriod }} - /> -
- ) : ( -
{individualRewardText}
- ))} -
-
- ) : ( -
-
-
- - )} +
+
+ +
- {isLoading ? : children} + {isLoading ? : children}
- {isNavigationVisible && ( - + {!isDesktop && isNavigationVisible && ( + )} +
+ +
+ setIsWalletModalOpen(false), + }} + /> + setIsConnectWalletModalOpen(false), + }} + /> ); }; diff --git a/client/src/components/shared/Layout/LayoutConnectWallet/LayoutConnectWallet.tsx b/client/src/components/shared/Layout/LayoutConnectWallet/LayoutConnectWallet.tsx index 0d64324525..92250331f5 100644 --- a/client/src/components/shared/Layout/LayoutConnectWallet/LayoutConnectWallet.tsx +++ b/client/src/components/shared/Layout/LayoutConnectWallet/LayoutConnectWallet.tsx @@ -8,7 +8,7 @@ import BoxRounded from 'components/ui/BoxRounded'; import Loader from 'components/ui/Loader'; import Svg from 'components/ui/Svg'; import networkConfig from 'constants/networkConfig'; -import useSettingsStore from 'store/settings/store'; +import useDelegationStore from 'store/delegation/store'; import { browserWallet, walletConnect, ledgerConnect } from 'svg/wallet'; import styles from './LayoutConnectWallet.module.scss'; @@ -18,7 +18,7 @@ const LayoutConnectWallet: FC = () => { keyPrefix: 'components.dedicated.connectWallet', }); - const { isDelegationInProgress, setIsDelegationConnectModalOpen } = useSettingsStore(state => ({ + const { isDelegationInProgress, setIsDelegationConnectModalOpen } = useDelegationStore(state => ({ isDelegationInProgress: state.data.isDelegationInProgress, setIsDelegationConnectModalOpen: state.setIsDelegationConnectModalOpen, })); diff --git a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss new file mode 100644 index 0000000000..27e4605eef --- /dev/null +++ b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.module.scss @@ -0,0 +1,64 @@ +.root { + position: relative; + display: flex; + align-items: center; + width: 100%; + padding: 4.8rem 0 21rem; + flex-direction: column-reverse; + @include layoutMaxWidth(); + + @media #{$tablet-up} { + flex-direction: row; + padding: 4.8rem 0 26rem; + } + + @media #{$desktop-up} { + padding: 4.8rem 0 5.6rem; + margin-bottom: 0; + } + + .wrapper { + display: flex; + text-align: start; + align-items: center; + margin-right: 2.4rem; + + .octantText { + font-size: $font-size-12; + min-width: 23.2rem; + margin-left: 1.6rem; + font-weight: $font-weight-semibold; + line-height: 2rem; + color: $color-octant-grey5; + } + } + + .links { + display: grid; + grid-template-columns: repeat(3, 11.2rem [col-start]); + grid-template-rows: 2.4rem 2.4rem; + grid-auto-flow: column; + margin-bottom: 3.2rem; + + @media #{$tablet-up} { + margin: 0; + } + + .link { + cursor: pointer; + display: flex; + font-size: $font-size-14; + font-weight: $font-weight-semibold; + color: $color-octant-grey5; + + &:hover { + color: $color-octant-grey13; + } + } + } + + .golemFoundationLink { + color: $color-octant-green; + text-decoration: underline; + } +} diff --git a/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx new file mode 100644 index 0000000000..10e62e83db --- /dev/null +++ b/client/src/components/shared/Layout/LayoutFooter/LayoutFooter.tsx @@ -0,0 +1,77 @@ +import cx from 'classnames'; +import React, { FC, memo } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import Svg from 'components/ui/Svg'; +import { + BLOG_POST, + BRAND_ASSETS_FIGMA_LINK, + DISCORD_LINK, + FARCASTER_LINK, + GOLEM_FOUNDATION_LINK, + OCTANT_BUILD_LINK, + OCTANT_DOCS, + TERMS_OF_USE, + TWITTER_LINK, +} from 'constants/urls'; +import useMediaQuery from 'hooks/helpers/useMediaQuery'; +import { octantSemiTransparent } from 'svg/logo'; + +import styles from './LayoutFooter.module.scss'; +import LayoutFooterProps from './types'; + +const LayoutFooter: FC = ({ className }) => { + const { t } = useTranslation('translation', { keyPrefix: 'layout.footer' }); + const { isDesktop } = useMediaQuery(); + + const links = isDesktop + ? [ + { label: t('links.website'), link: OCTANT_BUILD_LINK }, + { label: t('links.docs'), link: OCTANT_DOCS }, + { label: t('links.blog'), link: BLOG_POST }, + { label: t('links.discord'), link: DISCORD_LINK }, + { label: t('links.farcaster'), link: FARCASTER_LINK }, + { label: t('links.twitterX'), link: TWITTER_LINK }, + { label: t('links.brandAssets'), link: BRAND_ASSETS_FIGMA_LINK }, + { label: t('links.termsOfUse'), link: TERMS_OF_USE }, + ] + : [ + { label: t('links.website'), link: OCTANT_BUILD_LINK }, + { label: t('links.docs'), link: OCTANT_DOCS }, + { label: t('links.farcaster'), link: FARCASTER_LINK }, + { label: t('links.twitterX'), link: TWITTER_LINK }, + { label: t('links.discord'), link: DISCORD_LINK }, + { label: t('links.termsOfUse'), link: TERMS_OF_USE }, + ]; + + return ( +
+
+ +
+ , + ]} + i18nKey="layout.footer.octantText" + /> +
+
+
+ {links.map(({ link, label }) => ( + + {`→ ${label}`} + + ))} +
+
+ ); +}; + +export default memo(LayoutFooter); diff --git a/client/src/components/shared/Layout/LayoutFooter/index.tsx b/client/src/components/shared/Layout/LayoutFooter/index.tsx new file mode 100644 index 0000000000..a98eab3131 --- /dev/null +++ b/client/src/components/shared/Layout/LayoutFooter/index.tsx @@ -0,0 +1,2 @@ +// eslint-disable-next-line no-restricted-exports +export { default } from './LayoutFooter'; diff --git a/client/src/components/shared/Layout/LayoutFooter/types.ts b/client/src/components/shared/Layout/LayoutFooter/types.ts new file mode 100644 index 0000000000..b974a9de3c --- /dev/null +++ b/client/src/components/shared/Layout/LayoutFooter/types.ts @@ -0,0 +1,3 @@ +export default interface LayoutFooterProps { + className?: string; +} diff --git a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss index 4c606cf73b..2e2aa010e6 100644 --- a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss +++ b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.module.scss @@ -4,37 +4,32 @@ } .navigationWrapper { - @include layoutFloatingElementWidth(); + width: 100%; + min-width: 39rem; display: flex; justify-content: center; position: fixed; - bottom: $layoutMarginHorizontal; + bottom: $layout-navbar-bottom; z-index: $z-index-5; pointer-events: none; } .navigation { + display: flex; + flex-direction: column; border-radius: $border-radius-32; background: $color-white; box-shadow: $box-shadow-1; pointer-events: initial; width: 34.2rem; - @media #{$desktop-up} { + @media #{$tablet-up} { border-radius: $border-radius-40; width: 43.2rem; } - .navigationBottomSuffix { - padding: 1.6rem; - border-bottom: 0.1rem solid $color-octant-grey3; - - @media #{$desktop-up} { - padding: 2.4rem; - } - } - .buttons { + order: 1; display: flex; justify-content: space-between; align-items: center; @@ -45,7 +40,7 @@ justify-content: center; } - @media #{$desktop-up} { + @media #{$tablet-up} { margin: 1.2rem 2.4rem 2rem 2.4rem; } } @@ -59,15 +54,29 @@ flex: 0; font-size: $font-size-10; + &.isActive { + svg.octantLogo { + path:nth-child(2) { + fill: $color-octant-dark; + } + } + } + &:not(.isActive):hover { color: $color-octant-grey5; svg path { stroke: $color-octant-grey5; } + + svg.octantLogo { + path:nth-child(2) { + fill: $color-octant-grey5; + } + } } - @media #{$desktop-up} { + @media #{$tablet-up} { font-size: $font-size-12; &:not(:last-child) { @@ -96,7 +105,7 @@ border-radius: 50%; transform: translateZ(0); // On webkit solves the flickering of text during the scale. - @media #{$desktop-up} { + @media #{$tablet-up} { height: 2rem; width: 2rem; font-size: $font-size-10; @@ -126,14 +135,14 @@ rgba(0, 0, 0, 1) ); - @media #{$desktop-up} { + @media #{$tablet-up} { height: 15.2rem; } &.hasNavigationBottomSuffix { height: 20rem; - @media #{$desktop-up} { + @media #{$tablet-up} { height: 24.8rem; } } diff --git a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx index 5c6f629610..e8bddda460 100644 --- a/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx +++ b/client/src/components/shared/Layout/LayoutNavbar/LayoutNavbar.tsx @@ -2,42 +2,39 @@ import cx from 'classnames'; import { useAnimate } from 'framer-motion'; import React, { FC, Fragment, useEffect, useRef } from 'react'; import { useLocation } from 'react-router-dom'; -import { useAccount } from 'wagmi'; import Button from 'components/ui/Button'; import Svg from 'components/ui/Svg'; import { ELEMENT_POSITION_FIXED_CLASSNAME } from 'constants/css'; +import { LAYOUT_NAVBAR_ID } from 'constants/domElementsIds'; import { WINDOW_PROJECTS_SCROLL_Y } from 'constants/window'; import useIsProjectAdminMode from 'hooks/helpers/useIsProjectAdminMode'; import useMediaQuery from 'hooks/helpers/useMediaQuery'; -import useUserTOS from 'hooks/queries/useUserTOS'; +import useNavigationTabs from 'hooks/helpers/useNavigationTabs'; import { ROOT_ROUTES } from 'routes/RootRoutes/routes'; import useAllocationsStore from 'store/allocations/store'; import styles from './LayoutNavbar.module.scss'; import LayoutNavbarProps from './types'; -const LayoutNavbar: FC = ({ navigationBottomSuffix, tabs }) => { - const { isConnected } = useAccount(); - const { data: isUserTOSAccepted } = useUserTOS(); +const LayoutNavbar: FC = ({ navigationBottomSuffix }) => { const { allocations } = useAllocationsStore(state => ({ allocations: state.data.allocations, })); const allocationsPrevRef = useRef(allocations); - const { isDesktop } = useMediaQuery(); + const { isTablet } = useMediaQuery(); const location = useLocation(); const [scope, animate] = useAnimate(); const isProjectAdminMode = useIsProjectAdminMode(); - - const areTabsDisabled = isConnected && !isUserTOSAccepted; + const tabs = useNavigationTabs(); useEffect(() => { if (!scope?.current || allocations.length === allocationsPrevRef.current.length) { return; } animate([ - [scope?.current, { scale: [isDesktop ? 1.4 : 1.5] }, { duration: 0.15, ease: 'easeOut' }], + [scope?.current, { scale: [isTablet ? 1.4 : 1.5] }, { duration: 0.15, ease: 'easeOut' }], [scope?.current, { scale: 1 }, { duration: 0.15, ease: 'easeOut' }], ]); allocationsPrevRef.current = allocations; @@ -50,23 +47,31 @@ const LayoutNavbar: FC = ({ navigationBottomSuffix, tabs }) = className={cx(styles.navigationWrapper, ELEMENT_POSITION_FIXED_CLASSNAME)} data-test="Navbar" > -