From d093acaab53f73cf8fe2e3b3dfd4eac933ef1889 Mon Sep 17 00:00:00 2001 From: Dima Date: Fri, 26 Apr 2024 18:29:01 +0200 Subject: [PATCH 1/3] claim flt page --- web/README.md | 1 + web/src/components/App/App.js | 3 + web/src/constants/addresses.js | 4 + web/src/constants/chains.js | 18 ++ web/src/constants/routes.js | 1 + .../pages/claim-flt-page/claim-flt-page.js | 155 +++++++++++++++ .../claim-flt-page/claim-flt-page.module.css | 182 ++++++++++++++++++ web/src/pages/claim-flt-page/helpers.js | 27 +++ 8 files changed, 391 insertions(+) create mode 100644 web/src/pages/claim-flt-page/claim-flt-page.js create mode 100644 web/src/pages/claim-flt-page/claim-flt-page.module.css create mode 100644 web/src/pages/claim-flt-page/helpers.js diff --git a/web/README.md b/web/README.md index 476a575..226b02a 100644 --- a/web/README.md +++ b/web/README.md @@ -7,6 +7,7 @@ - Claiming flow 10 - Route "/finish" - Claiming flow 11 - Route "/not-found" - Claiming flow 12 - Route "/claimed" +- Claiming FLT from FLT-DROP - Route "/claim-flt" # diff --git a/web/src/components/App/App.js b/web/src/components/App/App.js index 5e75c17..dd4a59a 100644 --- a/web/src/components/App/App.js +++ b/web/src/components/App/App.js @@ -24,11 +24,13 @@ import { ROUTE_INDEX, ROUTE_NOT_FOUND, ROUTE_PROOF, + ROUTE_CLAIM_FLT, ROUTE_WALLET } from "../../constants/routes"; import { catchError } from "../../utils"; import { fetchCurrentAward, fetchMerkleRoot, fetchNextHalvePeriod } from "../../store/actions/distributor"; import { useVh } from "../../hooks/useVh"; +import { ClaimFltPage } from "../../pages/claim-flt-page/claim-flt-page"; function App() { const { network, address, provider } = useWeb3Connection(); @@ -71,6 +73,7 @@ function App() { } /> } /> } /> + } /> } /> diff --git a/web/src/constants/addresses.js b/web/src/constants/addresses.js index 1a3b5b8..e2030a0 100644 --- a/web/src/constants/addresses.js +++ b/web/src/constants/addresses.js @@ -3,4 +3,8 @@ export const governanceContracts = { token: "0x236501327e701692a281934230AF0b6BE8Df3353", devRewardDistributor: "0x6081d7F04a8c31e929f25152d4ad37c83638C62b", }, + // fuji: { + // token: "0x236501327e701692a281934230AF0b6BE8Df3353", + // devRewardDistributor: "0x9Fe90893E9F8Bb5F7772B7f422d04709020A9BFE", + // }, }; diff --git a/web/src/constants/chains.js b/web/src/constants/chains.js index 7b37b65..af64439 100644 --- a/web/src/constants/chains.js +++ b/web/src/constants/chains.js @@ -16,6 +16,24 @@ const supportedChains = [ balance: "", }, }, + // NEXT CHAIN FOR TESTING ONLY + // { + // name: "Fuji C-Chain", + // short_name: "fuji", + // chain: "C-CHAIN", + // network: "fuji", + // chain_id: 43113, + // network_id: 43113, + // explorer_url: "https://explorer.cchain.dev", + // rpc_url: "https://api.avax-test.network/ext/bc/C/rpc", + // native_currency: { + // symbol: "AVAX", + // name: "C-Chain", + // decimals: "18", + // contractAddress: "", + // balance: "", + // }, + // } ]; export default supportedChains; diff --git a/web/src/constants/routes.js b/web/src/constants/routes.js index a23ff99..03940fa 100644 --- a/web/src/constants/routes.js +++ b/web/src/constants/routes.js @@ -6,3 +6,4 @@ export const ROUTE_DONE = "/done"; export const ROUTE_FINISH = "/finish"; export const ROUTE_NOT_FOUND = "/not-found"; export const ROUTE_CLAIMED = "/claimed"; +export const ROUTE_CLAIM_FLT = "/claim-flt"; diff --git a/web/src/pages/claim-flt-page/claim-flt-page.js b/web/src/pages/claim-flt-page/claim-flt-page.js new file mode 100644 index 0000000..3cfd976 --- /dev/null +++ b/web/src/pages/claim-flt-page/claim-flt-page.js @@ -0,0 +1,155 @@ +import { memo, useEffect, useState } from "react"; +import { useWeb3Connection } from "../../hooks/useWeb3Connection"; +import { ethers } from "ethers"; +import ConnectWallet from "../../components/ConnectWallet/ConnectWallet"; +import styles from "./claim-flt-page.module.css"; +import Header from "../../components/Header/Header"; +import Title from "../../components/Title/Title"; +import Dashboard from "../../components/Dashboard/Dashboard"; +import DefinitionList from "../../components/DefinitionList/DefinitionList"; +import Text from "../../components/Text/Text"; +import Footer from "../../components/Footer/Footer"; +import { governanceContracts } from "../../constants"; +import abis from "../../contracts"; +import { formatSeconds } from "./helpers"; +import Button from "../../components/Button/Button"; +import supportedChains from "../../constants/chains"; + + +export const ClaimFltPage = memo(() => { + const { address, provider, network } = useWeb3Connection(); + const [amountAndDate, setAmountAndDate] = useState(null); + const [secondsLeft, setSecondsLeft] = useState(null); + + useEffect(() => { + if (!amountAndDate || !address) { + return; + } + + const updateSecondsLeft = () => { + const now = Date.now(); + const unlockTime = amountAndDate.unlockTime.getTime() / 1000; + const newSecondsLeft = Math.floor((unlockTime - now / 1000)); + setSecondsLeft(newSecondsLeft); + }; + + updateSecondsLeft(); + + let intervalId; + if (!secondsLeft || secondsLeft > 0) { + intervalId = setInterval(updateSecondsLeft, 500); + } + return () => clearInterval(intervalId); + }, [amountAndDate, address, network]); + + useEffect(() => { + if (!provider) return; + (async () => { + const contract = new ethers.Contract( + governanceContracts[network.name].devRewardDistributor, + abis.DevRewardDistributor.abi, + provider + ); + if (address) { + const data = await contract.functions.lockedBalances(address); + const { amount, unlockTime } = data; + const _amount = +ethers.utils.formatEther(amount); + const _unlockTime = new Date(unlockTime.toNumber() * 1000); + setAmountAndDate({ amount: _amount, unlockTime: _unlockTime }); + } + })(); + }, [address, provider, network]); + + const [waitForSigning, setWaitForSigning] = useState(false); + const [waitForReceipt, setWaitForReceipt] = useState(false); + const [confirmedTxHash, setConfirmedTxHash] = useState(null); + + const handleClaim = async () => { + const contract = new ethers.Contract( + governanceContracts[network.name].devRewardDistributor, + abis.DevRewardDistributor.abi, + provider.getSigner(), + ); + setWaitForSigning(true); + const response = await contract.functions.transfer(address, amountAndDate.amount, { from: address }); + setWaitForSigning(false); + setWaitForReceipt(true); + const receipt = await response.wait(); + setWaitForReceipt(false); + setConfirmedTxHash(receipt.transactionHash); + } + + const dateInFuture = amountAndDate?.unlockTime > new Date(); + + return ( + <> +
+
+
+
+
+ + </div> + <div className={styles.dashboard}> + <Dashboard> + <div className={styles["dashboard__flex-container"]}> + <div className={styles.dashboard__logo} /> + <div className={styles.definition}> + {amountAndDate?.unlockTime ? <> + {amountAndDate.amount !== 0 && <DefinitionList + dd={`${amountAndDate.amount} FLT`} + dt={dateInFuture ? formatSeconds(secondsLeft) : "ready to be claimed"} + colorD="orange" + colorT="black" + />} + {amountAndDate.amount === 0 && "No FLT to claim"} + </> : ( + <> + {(!address || !network) && "Connect your wallet"} + {address && network && "Loading data..."} + </> + )} + </div> + </div> + <div className={styles.dashboard__text}> + <Text color="black" type="large"> + Claiming is a transfer of tokens to yourself or another address + </Text> + </div> + <ol className={styles.dashboard__list}> + <li className={styles.dashboard__item}> + Connect an Ethereum wallet + </li> + <li className={styles.dashboard__item}> + Press claim and sign a transaction + </li> + <li className={styles.dashboard__item}> + Or just use metamask or another wallet – transfer tokens to yourself + </li> + {secondsLeft > 0 && <li className={styles.dashboard__item}> + You will be able to claim at {amountAndDate.unlockTime.toLocaleString()} + </li>} + </ol> + <div className={styles.dashboard__caption}> + <Text color="grey" type="small"></Text> + </div> + <div className={styles["dashboard__flex-container-flat"]}> + <div className={styles.dashboard__button}> + <ConnectWallet /> + </div> + {!dateInFuture && Boolean(amountAndDate?.amount) && <div className={styles.dashboard__button}> + {waitForSigning && "Please sign tx in your wallet"} + {waitForReceipt && "Confirming..."} + {confirmedTxHash && <a href={supportedChains[0].explorer_url + "/tx/" + confirmedTxHash} target="_blank" rel="noreferrer">Transaction confirmed</a>} + {!waitForSigning && !waitForReceipt && !confirmedTxHash && <Button callback={handleClaim} text={`Claim FLT`} />} + </div>} + </div> + </Dashboard> + </div> + </main> + </div> + <Footer /> + </div> + </> + ); +}); \ No newline at end of file diff --git a/web/src/pages/claim-flt-page/claim-flt-page.module.css b/web/src/pages/claim-flt-page/claim-flt-page.module.css new file mode 100644 index 0000000..0e4cd52 --- /dev/null +++ b/web/src/pages/claim-flt-page/claim-flt-page.module.css @@ -0,0 +1,182 @@ +.background { + padding-top: 54px; + + background: radial-gradient( + 50% 45.85% at 50% 54.15%, + #c51103 0%, + #dc9e61 100% + ), + #c4c4c4; + background-image: url("../../images/Rectangle 76.21553159.webp"); + background-repeat: no-repeat; + background-size: 100% 100%; +} + +.progress { + margin-bottom: 30px; +} + +.title { + align-items: flex-end; + display: flex; + margin-bottom: 82px; +} + +.icon { + width: 60px; + height: 80px; + + background-image: url("https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/160/apple/285/fire_1f525.png"); + background-position: center; + background-repeat: no-repeat; +} + +.definition { + width: 234px; +} + +.dashboard { + margin-bottom: 161px; +} + +.dashboard__flex-container { + display: flex; + align-items: center; + margin-bottom: 53px; +} + +.dashboard__flex-container-flat { + display: flex; + align-items: center; +} + +.dashboard__timer { + margin-left: 40px; +} + +.dashboard__logo { + width: 80px; + height: 80px; + margin-right: 19px; + + background-color: rgb(239, 127, 36); + background-image: url("../../images/Ф.svg"); + background-repeat: no-repeat; + background-position: center; + + border-radius: 50%; +} + +.dashboard__text { + margin-bottom: 10px; + max-width: 785px; +} + +.icon-fire { + font-size: 80px; + line-height: 88px; + margin-left: 10px; +} + +.dashboard__list { + margin: 0 0 30px; +} + +.dashboard__item { + margin-bottom: 16px; + + font-family: "Hauora_Regular", sans-serif; + font-size: 18px; + line-height: 32px; + color: rgb(0, 0, 0); +} + +.dashboard__caption { + margin-bottom: 50px; +} + +.dashboard__button { + margin-bottom: 32px; +} + +.dashboard__button + .dashboard__button { + margin-left: 10px; +} + +.dashboard__paragraph { + margin: 0; + + font-family: "Hauora_Bold", sans-serif; + font-size: 10px; + line-height: 16px; + color: rgba(0, 0, 0, 0.5); + + text-transform: uppercase; + letter-spacing: 1px; +} + +.dashboard__link { + color: rgb(0, 0, 0); +} + +.dashboard__link:hover { + color: rgba(0, 0, 0, 0.5); + cursor: pointer; +} + +@media screen and (max-width: 1366px) { + .icon-fire { + font-size: 60px; + line-height: 70px; + } +} + +@media screen and (max-width: 600px) { + .icon-fire { + font-size: 42px; + line-height: 50px; + } +} + +@media screen and (max-width: 1275px) { + .main { + width: 100%; + } + + .dashboard { + max-width: 100%; + } +} + +@media screen and (max-width: 951px) { + .dashboard__item { + width: 100%; + } +} + +@media screen and (max-width: 900px) { + .main { + padding-right: 15px; + padding-left: 15px; + } +} + +@media screen and (max-width: 500px) { + .dashboard__text { + margin-bottom: 30px; + } + .main { + padding-right: 0; + padding-left: 0; + } + + .wallet, + .title, + .progress { + margin-left: 15px; + } + + .dashboard__button { + margin-bottom: 56px; + } +} diff --git a/web/src/pages/claim-flt-page/helpers.js b/web/src/pages/claim-flt-page/helpers.js new file mode 100644 index 0000000..dd4aaea --- /dev/null +++ b/web/src/pages/claim-flt-page/helpers.js @@ -0,0 +1,27 @@ +export const TOKENS = { + "1": "0x6081d7F04a8c31e929f25152d4ad37c83638C62b", + "31337": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "11155111": "0x35a0a84E0DDA0587794E1FB19543C65926040E03", + "43113": "0x44f9a4d2441efBf23646928CB6F27219f2b79A1a", +}; + +export function formatSeconds(seconds) { + // Calculate days, hours, minutes, and remaining seconds + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const remainingSeconds = seconds % 60; + + // Format hours, minutes, and seconds to be always two digits + const formattedHours = hours.toString().padStart(2, '0'); + const formattedMinutes = minutes.toString().padStart(2, '0'); + const formattedSeconds = remainingSeconds.toString().padStart(2, '0'); + + // Construct the time string based on the number of days + let timeString = `${formattedHours}:${formattedMinutes}:${formattedSeconds}`; + if (days > 0) { + timeString = `${days} day${days > 1 ? 's' : ''}, ${timeString}`; + } + + return timeString; +}; From 9e15129e837e2443a7075ce5804c2d6f8c439054 Mon Sep 17 00:00:00 2001 From: Dima <crystalbit@mail.ru> Date: Fri, 26 Apr 2024 18:31:05 +0200 Subject: [PATCH 2/3] remove unused --- web/src/pages/claim-flt-page/helpers.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/web/src/pages/claim-flt-page/helpers.js b/web/src/pages/claim-flt-page/helpers.js index dd4aaea..a0c6058 100644 --- a/web/src/pages/claim-flt-page/helpers.js +++ b/web/src/pages/claim-flt-page/helpers.js @@ -1,10 +1,3 @@ -export const TOKENS = { - "1": "0x6081d7F04a8c31e929f25152d4ad37c83638C62b", - "31337": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "11155111": "0x35a0a84E0DDA0587794E1FB19543C65926040E03", - "43113": "0x44f9a4d2441efBf23646928CB6F27219f2b79A1a", -}; - export function formatSeconds(seconds) { // Calculate days, hours, minutes, and remaining seconds const days = Math.floor(seconds / 86400); From 59d88ebccaf402ebc8d1a070de7d2ee31db7d177 Mon Sep 17 00:00:00 2001 From: Dima <crystalbit@mail.ru> Date: Fri, 26 Apr 2024 21:42:15 +0200 Subject: [PATCH 3/3] fixes, link from main --- web/src/pages/begin-page/begin-page.js | 13 ++++++++++++- web/src/pages/claim-flt-page/claim-flt-page.js | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/web/src/pages/begin-page/begin-page.js b/web/src/pages/begin-page/begin-page.js index 2ed7d78..e01d468 100644 --- a/web/src/pages/begin-page/begin-page.js +++ b/web/src/pages/begin-page/begin-page.js @@ -13,7 +13,7 @@ import Footer from "../../components/Footer/Footer"; import TimeUntilReduce from "../../components/TimeUntilReduce/TimeUntilReduce"; import styles from "./begin-page.module.css"; -import { ROUTE_NOT_FOUND, ROUTE_WALLET } from "../../constants/routes"; +import { ROUTE_CLAIM_FLT, ROUTE_NOT_FOUND, ROUTE_WALLET } from "../../constants/routes"; const PageBegin = memo(() => { const navigate = useNavigate(); @@ -44,6 +44,10 @@ const PageBegin = memo(() => { } }; + const onAlreadyHaveButtonClick = () => { + navigate(ROUTE_CLAIM_FLT); + }; + const handleChangeUsername = (e) => { const value = e.target.value.toLowerCase(); value !== "" ? setInputPressed(true) : setInputPressed(false); @@ -110,6 +114,13 @@ const PageBegin = memo(() => { callback={onEligibilityCheckButtonClick} /> </li> + <li className={styles.button}> + <Button + type="large" + text="I already have FLT-DROP" + callback={onAlreadyHaveButtonClick} + /> + </li> </ul> </div> <div className={styles["flex-container__part-right"]}> diff --git a/web/src/pages/claim-flt-page/claim-flt-page.js b/web/src/pages/claim-flt-page/claim-flt-page.js index 3cfd976..b16467c 100644 --- a/web/src/pages/claim-flt-page/claim-flt-page.js +++ b/web/src/pages/claim-flt-page/claim-flt-page.js @@ -88,7 +88,7 @@ export const ClaimFltPage = memo(() => { <div className="container"> <main className={`main ${styles.main}`}> <div className={styles.title}> - <Title type="h1" size="large" text="FLT-DROP Claim" icon="" /> + <Title type="h1" size="large" text="Convert FLT-DROP to FLT" icon="" /> </div> <div className={styles.dashboard}> <Dashboard>