From 05573cdbf2b114a1c355a89f1392ef95ac514ec8 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Mon, 26 Aug 2024 13:20:54 +0200 Subject: [PATCH 01/49] added project title to notification --- .../recurringTab/ModifyStreamModal/UpdateStreamInnerModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/UpdateStreamInnerModal.tsx b/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/UpdateStreamInnerModal.tsx index d8d83f5f43..0305bcde03 100644 --- a/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/UpdateStreamInnerModal.tsx +++ b/src/components/views/userProfile/donationsTab/recurringTab/ModifyStreamModal/UpdateStreamInnerModal.tsx @@ -204,7 +204,7 @@ export const UpdateStreamInnerModal: FC = ({ <> {tx && } = ({ <> {tx && } Date: Mon, 26 Aug 2024 15:00:52 +0200 Subject: [PATCH 02/49] Feature: added recurring tab as default for OP address --- src/components/views/donate/DonationCard.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/views/donate/DonationCard.tsx b/src/components/views/donate/DonationCard.tsx index 0e706af8f0..d64b3ecece 100644 --- a/src/components/views/donate/DonationCard.tsx +++ b/src/components/views/donate/DonationCard.tsx @@ -90,6 +90,15 @@ export const DonationCard: FC = ({ }); }, []); + // Check if the 'tab' query parameter is not present in the URL and project 'hasOpAddress' is true. + // If both conditions are met, set the active tab to 'RECURRING' using the setTab function. + // This ensures that the 'RECURRING' tab is active by default if rpoject has Op Address. + useEffect(() => { + if (!router.query.tab && hasOpAddress) { + setTab(ETabs.RECURRING); + } + }, [router.query.tab, hasOpAddress]); + return ( {!isQRDonation ? ( From 1cb4c02484fd5567ccd3dc5144a21c85e0a07152 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Mon, 26 Aug 2024 16:49:39 +0200 Subject: [PATCH 03/49] update useEffect dependency --- src/components/views/donate/DonationCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/donate/DonationCard.tsx b/src/components/views/donate/DonationCard.tsx index d64b3ecece..5c4042bcac 100644 --- a/src/components/views/donate/DonationCard.tsx +++ b/src/components/views/donate/DonationCard.tsx @@ -97,7 +97,7 @@ export const DonationCard: FC = ({ if (!router.query.tab && hasOpAddress) { setTab(ETabs.RECURRING); } - }, [router.query.tab, hasOpAddress]); + }, [router.query, hasOpAddress]); return ( From 5447b2d3d462f63ca017f7ba3a43597459e55131 Mon Sep 17 00:00:00 2001 From: Hrithik Sampson Date: Tue, 27 Aug 2024 10:44:56 +0530 Subject: [PATCH 04/49] bug: Add Modal on Donate Page so user can't donate to his/her own project --- .../modals/DonationByProjectOwner.tsx | 137 ++++++++++++++++++ src/components/views/donate/DonateIndex.tsx | 22 ++- 2 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 src/components/modals/DonationByProjectOwner.tsx diff --git a/src/components/modals/DonationByProjectOwner.tsx b/src/components/modals/DonationByProjectOwner.tsx new file mode 100644 index 0000000000..9ee7bd9925 --- /dev/null +++ b/src/components/modals/DonationByProjectOwner.tsx @@ -0,0 +1,137 @@ +import React from 'react'; +import { + brandColors, + Button, + IconAlertTriangleFilled32, + Lead, + neutralColors, +} from '@giveth/ui-design-system'; +import styled from 'styled-components'; +import { useIntl } from 'react-intl'; +import { useRouter } from 'next/router'; +import { Modal } from '@/components/modals/Modal'; +import Routes from '@/lib/constants/Routes'; +import { mediaQueries } from '@/lib/constants/constants'; +import { useModalAnimation } from '@/hooks/useModalAnimation'; + +// Define the props interface +interface DonationByProjectOwnerProps { + setShowDonationByProjectOwner: ( + showDonationByProjectOwner: boolean, + ) => void; +} + +export const DonationByProjectOwner: React.FC = ({ + setShowDonationByProjectOwner, +}) => { + const { formatMessage } = useIntl(); + const router = useRouter(); + const { closeModal } = useModalAnimation(setShowDonationByProjectOwner); + + const navigateToAllProjects = () => { + router.push(Routes.AllProjects); + closeModal(); + }; + + return ( + } + doNotCloseOnClickOutside + > + + + + You cannot donate to a project you are the owner of. + There are thousands of projects on Giveth looking for + your support! Please choose another project to donate + to. + + + + + + + + ); +}; + +const ModalContainer = styled.div` + background: white; + color: black; + padding: 24px 24px 38px; + margin: 0; + width: 100%; + + ${mediaQueries.tablet} { + width: 494px; + } +`; + +const ModalBox = styled.div` + color: ${brandColors.deep[900]}; + + > :first-child { + margin-bottom: 8px; + } + + h3 { + margin-top: -5px; + } + + h6 { + color: ${neutralColors.gray[700]}; + margin-top: -5px; + } + + > :last-child { + margin: 12px 0 32px 0; + + > span { + font-weight: 500; + } + } +`; + +const ModalButton = styled(Button)` + background: ${props => + props.disabled ? brandColors.giv[200] : brandColors.giv[500]}; + + &:hover:enabled { + background: ${brandColors.giv[700]}; + } + + :disabled { + cursor: not-allowed; + } + + > :first-child > div { + border-top: 3px solid ${brandColors.giv[200]}; + animation-timing-function: linear; + } + + text-transform: uppercase; +`; + +const Buttons = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + + > :first-child { + margin: 15px 0; + } +`; + +export default DonationByProjectOwner; diff --git a/src/components/views/donate/DonateIndex.tsx b/src/components/views/donate/DonateIndex.tsx index d5950c7142..e6e8b96318 100644 --- a/src/components/views/donate/DonateIndex.tsx +++ b/src/components/views/donate/DonateIndex.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import styled from 'styled-components'; import { Col, @@ -24,7 +24,7 @@ import { EContentType } from '@/lib/constants/shareContent'; import { PassportBanner } from '@/components/PassportBanner'; import { useAlreadyDonatedToProject } from '@/hooks/useAlreadyDonatedToProject'; import { Shadow } from '@/components/styled-components/Shadow'; -import { useAppDispatch } from '@/features/hooks'; +import { useAppDispatch, useAppSelector } from '@/features/hooks'; import { setShowHeader } from '@/features/general/general.slice'; import { DonateHeader } from './DonateHeader'; import { DonationCard, ETabs } from './DonationCard'; @@ -44,6 +44,7 @@ import { ChainType } from '@/types/config'; import { useQRCodeDonation } from '@/hooks/useQRCodeDonation'; import EndaomentProjectsInfo from '@/components/views/project/EndaomentProjectsInfo'; import { IDraftDonation } from '@/apollo/types/gqlTypes'; +import DonationByProjectOwner from '@/components/modals/DonationByProjectOwner'; const DonateIndex: FC = () => { const { formatMessage } = useIntl(); @@ -62,12 +63,14 @@ const DonateIndex: FC = () => { const { renewExpirationDate } = useQRCodeDonation(); const alreadyDonated = useAlreadyDonatedToProject(project); + const { userData } = useAppSelector(state => state.user); + const [showDonationByProjectOwner, setShowDonationByProjectOwner] = + useState(false); const dispatch = useAppDispatch(); const isSafeEnv = useIsSafeEnvironment(); const { isOnSolana } = useGeneralWallet(); const router = useRouter(); const { chainId } = useAccount(); - const [showQRCode, setShowQRCode] = React.useState( !!router.query.draft_donation, ); @@ -80,6 +83,12 @@ const DonateIndex: FC = () => { }; }, [dispatch]); + useEffect(() => { + setShowDonationByProjectOwner( + userData?.id !== undefined && userData?.id === project.adminUser.id, + ); + }, [userData?.id, project.adminUser]); + useEffect(() => { const fetchDonation = async () => { if (qrDonationStatus === 'success') { @@ -154,6 +163,13 @@ const DonateIndex: FC = () => { <> + {showDonationByProjectOwner && ( + + )} {alreadyDonated && ( From b71d951508b3c3c1b5ac7edcc72f168d7cd58254 Mon Sep 17 00:00:00 2001 From: Hrithik Sampson Date: Tue, 27 Aug 2024 11:18:30 +0530 Subject: [PATCH 05/49] chore: add all languages to the DonationByProjectOwner Modal --- lang/ca.json | 2 ++ lang/en.json | 2 ++ lang/es.json | 2 ++ src/components/modals/DonationByProjectOwner.tsx | 11 ++++++----- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lang/ca.json b/lang/ca.json index 791e811348..c188c94a34 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -1296,6 +1296,8 @@ "label.we_need_a_bit_more_info": "Necessitem una mica més d'informació!", "label.passport_connected": "Passaport connectat", "label.increase_passport_score": "Augmenta la puntuació del passaport", + "label.project_owner_address_detected": "Adreça del propietari del projecte detectada", + "label.project_owner_cant_donate_to_own_project": "No pots donar a un projecte del qual ets propietari. Hi ha milers de projectes a Giveth que busquen el teu suport! Si us plau, tria un altre projecte per donar.", "label.qf_donor_eligibility.banner.check_eligibility": "Fes que les teves donacions es igualin! Verifica la teva unicitat amb un clic.", "label.qf_donor_eligibility.banner.recheck_eligibility": "Fes que les teves donacions es igualin! Augmenta la teva puntuació de Gitcoin Passport abans de", "label.qf_donor_eligibility.banner.more_info_needed": "Necessitem una mica més d'informació per verificar la teva elegibilitat per QF!", diff --git a/lang/en.json b/lang/en.json index ee0d5f2299..e1bc3be208 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1300,6 +1300,8 @@ "label.we_need_a_bit_more_info": "We need a bit more info!", "label.passport_connected": "Passport connected", "label.increase_passport_score": "Increase Passport score", + "label.project_owner_address_detected": "Project Owner Address Detected", + "label.project_owner_cant_donate_to_own_project": "You cannot donate to a project you are the owner of. There are thousands of projects on Giveth looking for your support! Please choose another project to donate to.", "label.qf_donor_eligibility.banner.check_eligibility": "Get your donations matched! Verify your uniqueness with one click.", "label.qf_donor_eligibility.banner.recheck_eligibility": "Get your donations matched! Increase your Gitcoin Passport score before", "label.qf_donor_eligibility.banner.more_info_needed": "We need a bit more information to verify your QF Eligibility!", diff --git a/lang/es.json b/lang/es.json index 22caedb7e9..9fd8885fd0 100644 --- a/lang/es.json +++ b/lang/es.json @@ -1296,6 +1296,8 @@ "label.we_need_a_bit_more_info": "¡Necesitamos un poco más de información!", "label.passport_connected": "Pasaporte conectado", "label.increase_passport_score": "Aumentar la puntuación del pasaporte", + "label.project_owner_address_detected": "Dirección del propietario del proyecto detectada", + "label.project_owner_cant_donate_to_own_project": "No puedes donar a un proyecto del que eres propietario. ¡Hay miles de proyectos en Giveth que buscan tu apoyo! Por favor, elige otro proyecto para donar.", "label.qf_donor_eligibility.banner.check_eligibility": "¡Haz que tus donaciones sean igualadas! Verifica tu unicidad con un clic.", "label.qf_donor_eligibility.banner.recheck_eligibility": "¡Haz que tus donaciones sean igualadas! Aumenta tu puntuación de Gitcoin Passport antes de", "label.qf_donor_eligibility.banner.more_info_needed": "¡Necesitamos un poco más de información para verificar tu elegibilidad para QF!", diff --git a/src/components/modals/DonationByProjectOwner.tsx b/src/components/modals/DonationByProjectOwner.tsx index 9ee7bd9925..62978d202b 100644 --- a/src/components/modals/DonationByProjectOwner.tsx +++ b/src/components/modals/DonationByProjectOwner.tsx @@ -37,7 +37,9 @@ export const DonationByProjectOwner: React.FC = ({ } @@ -46,10 +48,9 @@ export const DonationByProjectOwner: React.FC = ({ - You cannot donate to a project you are the owner of. - There are thousands of projects on Giveth looking for - your support! Please choose another project to donate - to. + {formatMessage({ + id: 'label.project_owner_cant_donate_to_own_project', + })} From 2474052b9473d6fc8755ede9863d0bb84fd13ff7 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Tue, 27 Aug 2024 14:22:38 +0200 Subject: [PATCH 06/49] fixed typo --- src/components/views/donate/DonationCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/donate/DonationCard.tsx b/src/components/views/donate/DonationCard.tsx index 5c4042bcac..1f8eb01e89 100644 --- a/src/components/views/donate/DonationCard.tsx +++ b/src/components/views/donate/DonationCard.tsx @@ -92,7 +92,7 @@ export const DonationCard: FC = ({ // Check if the 'tab' query parameter is not present in the URL and project 'hasOpAddress' is true. // If both conditions are met, set the active tab to 'RECURRING' using the setTab function. - // This ensures that the 'RECURRING' tab is active by default if rpoject has Op Address. + // This ensures that the 'RECURRING' tab is active by default if project has Op Address. useEffect(() => { if (!router.query.tab && hasOpAddress) { setTab(ETabs.RECURRING); From 57c3b2c2af16912de8d2f49c3d9a6fe43e8b540e Mon Sep 17 00:00:00 2001 From: Hrithik Sampson Date: Mon, 19 Aug 2024 19:29:51 +0530 Subject: [PATCH 07/49] fix: trying to resolve order of execution consistency of hooks in react but falling into queueing issues in react --- .../SelectTokenModal/SelectTokenModal.tsx | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx b/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx index 60f16e7557..45a23d99a6 100644 --- a/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx +++ b/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx @@ -8,9 +8,9 @@ import { IconGIVBack24, IconSearch16, } from '@giveth/ui-design-system'; -import { useState, type FC, useEffect } from 'react'; +import { useState, type FC, useEffect, useCallback, use } from 'react'; import { useIntl } from 'react-intl'; -import { erc20Abi, isAddress } from 'viem'; // Assuming `isAddress` is a function from the `viem` library to validate Ethereum addresses +import { Address, erc20Abi, isAddress } from 'viem'; // Assuming `isAddress` is a function from the `viem` library to validate Ethereum addresses import { useAccount } from 'wagmi'; import { readContracts } from 'wagmi/actions'; import { IModal } from '@/types/common'; @@ -58,6 +58,30 @@ export const SelectTokenModal: FC = props => { ); }; +const useTokenBalances = (tokens: IProjectAcceptedToken[] | undefined) => { + const [allTokenBalances, setAllTokenBalances] = useState>(new Map()); + useEffect(() => { + if(tokens) { + const fetchBalance = useFetchBalance; + const fetchAllTokenBalances = async () => { + const balances = new Map(new Map()); + for (let token of tokens) { + const fetchTokenBalance = () => { + return new Promise((resolve)=>{ + balances.set(token.address,fetchBalance(token)); + resolve(null); + }) + } + await fetchTokenBalance(); + }; + return balances; + } + fetchAllTokenBalances().then((balances)=>setAllTokenBalances(balances)); + } + },[tokens]); + return allTokenBalances; +} + const SelectTokenInnerModal: FC = ({ tokens, acceptCustomToken, @@ -72,6 +96,8 @@ const SelectTokenInnerModal: FC = ({ const { setSelectedOneTimeToken } = useDonateData(); const { isOnEVM } = useGeneralWallet(); const { chain: evmChain } = useAccount(); + const allTokenBalances = useTokenBalances(tokens); + const fetchBalance = useFetchBalance; useEffect(() => { if (tokens) { @@ -149,12 +175,10 @@ const SelectTokenInnerModal: FC = ({ } }, [searchQuery, tokens]); - // Fetch balances for filtered tokens - const fetchBalance = useFetchBalance; const tokenBalances = filteredTokens.map(token => ({ token, - balance: fetchBalance(token), + balance: allTokenBalances?.get(token.address), })); const customTokenBalance = fetchBalance(customToken); From 18a53433bb058c3000f8388551156903c48eefc2 Mon Sep 17 00:00:00 2001 From: Hrithik Sampson Date: Mon, 19 Aug 2024 19:30:01 +0530 Subject: [PATCH 08/49] fix: trying to resolve order of execution consistency of hooks in react but falling into queueing issues in react --- .../SelectTokenModal/SelectTokenModal.tsx | 40 ++++++------------- .../SelectTokenModal/useFetchBalanceHook.tsx | 1 + 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx b/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx index 45a23d99a6..c2f7a9c138 100644 --- a/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx +++ b/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx @@ -8,7 +8,7 @@ import { IconGIVBack24, IconSearch16, } from '@giveth/ui-design-system'; -import { useState, type FC, useEffect, useCallback, use } from 'react'; +import { useState, type FC, useEffect, useCallback, use, useMemo, useRef } from 'react'; import { useIntl } from 'react-intl'; import { Address, erc20Abi, isAddress } from 'viem'; // Assuming `isAddress` is a function from the `viem` library to validate Ethereum addresses import { useAccount } from 'wagmi'; @@ -58,35 +58,14 @@ export const SelectTokenModal: FC = props => { ); }; -const useTokenBalances = (tokens: IProjectAcceptedToken[] | undefined) => { - const [allTokenBalances, setAllTokenBalances] = useState>(new Map()); - useEffect(() => { - if(tokens) { - const fetchBalance = useFetchBalance; - const fetchAllTokenBalances = async () => { - const balances = new Map(new Map()); - for (let token of tokens) { - const fetchTokenBalance = () => { - return new Promise((resolve)=>{ - balances.set(token.address,fetchBalance(token)); - resolve(null); - }) - } - await fetchTokenBalance(); - }; - return balances; - } - fetchAllTokenBalances().then((balances)=>setAllTokenBalances(balances)); - } - },[tokens]); - return allTokenBalances; -} + const SelectTokenInnerModal: FC = ({ tokens, acceptCustomToken, setShowModal, }) => { + const [hideZeroBalance, setHideZeroBalance] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [filteredTokens, setFilteredTokens] = useState(tokens || []); @@ -96,13 +75,18 @@ const SelectTokenInnerModal: FC = ({ const { setSelectedOneTimeToken } = useDonateData(); const { isOnEVM } = useGeneralWallet(); const { chain: evmChain } = useAccount(); - const allTokenBalances = useTokenBalances(tokens); const fetchBalance = useFetchBalance; + const tokenBalancesMap = useRef<{ [key: string]: bigint | undefined }>({}) + tokens?.map((token) => { + const balance = useFetchBalance(token); + tokenBalancesMap.current[token.address] = balance; + }); + useEffect(() => { if (tokens) { if (isAddress(searchQuery)) { - const existingToken = tokens.find( + const existingToken = tokens?.find( token => token.address.toLowerCase() === searchQuery.toLowerCase(), @@ -161,7 +145,7 @@ const SelectTokenInnerModal: FC = ({ } } else { setCustomToken(undefined); - const filtered = tokens.filter( + const filtered = tokens?.filter( token => token.symbol .toLowerCase() @@ -178,7 +162,7 @@ const SelectTokenInnerModal: FC = ({ const tokenBalances = filteredTokens.map(token => ({ token, - balance: allTokenBalances?.get(token.address), + balance: tokenBalancesMap.current[token.address], })); const customTokenBalance = fetchBalance(customToken); diff --git a/src/components/views/donate/OnTime/SelectTokenModal/useFetchBalanceHook.tsx b/src/components/views/donate/OnTime/SelectTokenModal/useFetchBalanceHook.tsx index c8f6d8574d..0ba6ce8cb5 100644 --- a/src/components/views/donate/OnTime/SelectTokenModal/useFetchBalanceHook.tsx +++ b/src/components/views/donate/OnTime/SelectTokenModal/useFetchBalanceHook.tsx @@ -7,6 +7,7 @@ import { useGeneralWallet } from '@/providers/generalWalletProvider'; import { IProjectAcceptedToken } from '@/apollo/types/gqlTypes'; const useFetchBalance = (token: IProjectAcceptedToken | undefined) => { + console.log(token) const { walletAddress } = useGeneralWallet(); const [balance, setBalance] = useState(undefined); From a99891ed3c724b2e7fdfaeed5021ba98c3b94ed9 Mon Sep 17 00:00:00 2001 From: Hrithik Sampson Date: Wed, 28 Aug 2024 10:01:45 +0530 Subject: [PATCH 09/49] fix:Solve React Queueing issue --- .../views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx b/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx index c2f7a9c138..c2517b0a5a 100644 --- a/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx +++ b/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx @@ -79,7 +79,7 @@ const SelectTokenInnerModal: FC = ({ const tokenBalancesMap = useRef<{ [key: string]: bigint | undefined }>({}) tokens?.map((token) => { - const balance = useFetchBalance(token); + const balance = fetchBalance(token); tokenBalancesMap.current[token.address] = balance; }); From 446c907760168f83bed2628eaa19728a7cfacf1c Mon Sep 17 00:00:00 2001 From: Hrithik Sampson Date: Wed, 28 Aug 2024 10:33:27 +0530 Subject: [PATCH 10/49] fix: run linter --- .../OnTime/SelectTokenModal/SelectTokenModal.tsx | 14 +++++--------- .../SelectTokenModal/useFetchBalanceHook.tsx | 1 - 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx b/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx index c2517b0a5a..19ff4735c1 100644 --- a/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx +++ b/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx @@ -8,9 +8,9 @@ import { IconGIVBack24, IconSearch16, } from '@giveth/ui-design-system'; -import { useState, type FC, useEffect, useCallback, use, useMemo, useRef } from 'react'; +import { useState, type FC, useEffect, useRef } from 'react'; import { useIntl } from 'react-intl'; -import { Address, erc20Abi, isAddress } from 'viem'; // Assuming `isAddress` is a function from the `viem` library to validate Ethereum addresses +import { erc20Abi, isAddress } from 'viem'; // Assuming `isAddress` is a function from the `viem` library to validate Ethereum addresses import { useAccount } from 'wagmi'; import { readContracts } from 'wagmi/actions'; import { IModal } from '@/types/common'; @@ -58,14 +58,11 @@ export const SelectTokenModal: FC = props => { ); }; - - const SelectTokenInnerModal: FC = ({ tokens, acceptCustomToken, setShowModal, }) => { - const [hideZeroBalance, setHideZeroBalance] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [filteredTokens, setFilteredTokens] = useState(tokens || []); @@ -76,13 +73,13 @@ const SelectTokenInnerModal: FC = ({ const { isOnEVM } = useGeneralWallet(); const { chain: evmChain } = useAccount(); const fetchBalance = useFetchBalance; - const tokenBalancesMap = useRef<{ [key: string]: bigint | undefined }>({}) + const tokenBalancesMap = useRef<{ [key: string]: bigint | undefined }>({}); - tokens?.map((token) => { + tokens?.map(token => { const balance = fetchBalance(token); tokenBalancesMap.current[token.address] = balance; }); - + useEffect(() => { if (tokens) { if (isAddress(searchQuery)) { @@ -159,7 +156,6 @@ const SelectTokenInnerModal: FC = ({ } }, [searchQuery, tokens]); - const tokenBalances = filteredTokens.map(token => ({ token, balance: tokenBalancesMap.current[token.address], diff --git a/src/components/views/donate/OnTime/SelectTokenModal/useFetchBalanceHook.tsx b/src/components/views/donate/OnTime/SelectTokenModal/useFetchBalanceHook.tsx index 0ba6ce8cb5..c8f6d8574d 100644 --- a/src/components/views/donate/OnTime/SelectTokenModal/useFetchBalanceHook.tsx +++ b/src/components/views/donate/OnTime/SelectTokenModal/useFetchBalanceHook.tsx @@ -7,7 +7,6 @@ import { useGeneralWallet } from '@/providers/generalWalletProvider'; import { IProjectAcceptedToken } from '@/apollo/types/gqlTypes'; const useFetchBalance = (token: IProjectAcceptedToken | undefined) => { - console.log(token) const { walletAddress } = useGeneralWallet(); const [balance, setBalance] = useState(undefined); From a4953465dab15ff392040756fca73b7a5e120bc8 Mon Sep 17 00:00:00 2001 From: Hrithik Sampson Date: Fri, 30 Aug 2024 10:13:47 +0530 Subject: [PATCH 11/49] remove hooks for preventing application client error --- .../SelectTokenModal/SelectTokenModal.tsx | 82 ++++++++++++++----- .../SelectTokenModal/getBalanceForToken.tsx | 50 +++++++++++ .../SelectTokenModal/useFetchBalanceHook.tsx | 40 --------- 3 files changed, 111 insertions(+), 61 deletions(-) create mode 100644 src/components/views/donate/OnTime/SelectTokenModal/getBalanceForToken.tsx delete mode 100644 src/components/views/donate/OnTime/SelectTokenModal/useFetchBalanceHook.tsx diff --git a/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx b/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx index 19ff4735c1..ee13bd144a 100644 --- a/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx +++ b/src/components/views/donate/OnTime/SelectTokenModal/SelectTokenModal.tsx @@ -8,11 +8,12 @@ import { IconGIVBack24, IconSearch16, } from '@giveth/ui-design-system'; -import { useState, type FC, useEffect, useRef } from 'react'; +import { useState, type FC, useEffect } from 'react'; import { useIntl } from 'react-intl'; import { erc20Abi, isAddress } from 'viem'; // Assuming `isAddress` is a function from the `viem` library to validate Ethereum addresses import { useAccount } from 'wagmi'; import { readContracts } from 'wagmi/actions'; +import { useConnection } from '@solana/wallet-adapter-react'; import { IModal } from '@/types/common'; import { Modal } from '@/components/modals/Modal'; import { useModalAnimation } from '@/hooks/useModalAnimation'; @@ -24,7 +25,7 @@ import { shortenAddress, showToastError } from '@/lib/helpers'; import { useGeneralWallet } from '@/providers/generalWalletProvider'; import { wagmiConfig } from '@/wagmiConfigs'; import { ChainType } from '@/types/config'; -import useFetchBalance from './useFetchBalanceHook'; +import { getBalanceForToken } from './getBalanceForToken'; export interface ISelectTokenModalProps extends IModal { tokens?: IProjectAcceptedToken[]; @@ -66,24 +67,24 @@ const SelectTokenInnerModal: FC = ({ const [hideZeroBalance, setHideZeroBalance] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [filteredTokens, setFilteredTokens] = useState(tokens || []); + const { connection } = useConnection(); + const [tokenBalances, setTokenBalances] = useState< + { token: IProjectAcceptedToken; balance: bigint | undefined }[] + >([]); const [customToken, setCustomToken] = useState< IProjectAcceptedToken | undefined >(); + const [customTokenBalance, setCustomTokenBalance] = useState< + bigint | undefined + >(undefined); const { setSelectedOneTimeToken } = useDonateData(); - const { isOnEVM } = useGeneralWallet(); - const { chain: evmChain } = useAccount(); - const fetchBalance = useFetchBalance; - const tokenBalancesMap = useRef<{ [key: string]: bigint | undefined }>({}); - - tokens?.map(token => { - const balance = fetchBalance(token); - tokenBalancesMap.current[token.address] = balance; - }); + const { walletAddress, isOnEVM } = useGeneralWallet(); + const { chain: evmChain, address } = useAccount(); useEffect(() => { if (tokens) { if (isAddress(searchQuery)) { - const existingToken = tokens?.find( + const existingToken = tokens.find( token => token.address.toLowerCase() === searchQuery.toLowerCase(), @@ -142,7 +143,7 @@ const SelectTokenInnerModal: FC = ({ } } else { setCustomToken(undefined); - const filtered = tokens?.filter( + const filtered = tokens.filter( token => token.symbol .toLowerCase() @@ -154,17 +155,56 @@ const SelectTokenInnerModal: FC = ({ setFilteredTokens(filtered); } } - }, [searchQuery, tokens]); + }, [searchQuery, tokens, walletAddress]); - const tokenBalances = filteredTokens.map(token => ({ - token, - balance: tokenBalancesMap.current[token.address], - })); + useEffect(() => { + (async () => { + if (customToken) { + const balance = await getBalanceForToken( + customToken, + walletAddress, + ); + setCustomTokenBalance(balance); + } else { + setCustomTokenBalance(undefined); + } + })(); + }, [customToken, walletAddress]); - const customTokenBalance = fetchBalance(customToken); + useEffect(() => { + const fetchTokenBalances = async () => { + try { + const balances = await Promise.all( + filteredTokens.map(async token => { + const isEvm = token?.chainType === ChainType.EVM; + return isEvm + ? { + token, + balance: await getBalanceForToken( + token, + walletAddress, + ), + } + : { + token, + balance: await getBalanceForToken( + token, + walletAddress, + connection, + ), + }; + }), + ); + setTokenBalances(balances); + } catch (error) { + console.error('Error fetching token balances:', error); + } + }; + fetchTokenBalances(); + }, [tokens, filteredTokens, walletAddress]); // Sort tokens by balance - const sortedTokens = tokenBalances.sort((a, b) => { + const sortedTokens = tokenBalances.sort((a: any, b: any) => { if (a.balance === undefined) return 1; if (b.balance === undefined) return -1; return Number(b.balance) - Number(a.balance); @@ -200,7 +240,7 @@ const SelectTokenInnerModal: FC = ({ }} /> ) : sortedTokens.length > 0 ? ( - sortedTokens.map(({ token, balance }) => ( + sortedTokens.map(({ token, balance }: any) => ( => { + const isEvm = token?.chainType === ChainType.EVM; + const address = walletAddress as Address | null; + + try { + if (isEvm) { + return await fetchBalance(token.address, address!); + } else { + const solAddress = new PublicKey(walletAddress!); + if (!token || (token.address as string) === solanaNativeAddress) { + return connection + ?.getBalance(solAddress) + .then(solBalance => BigInt(solBalance)); + } + let splTokenMintAddress; + try { + splTokenMintAddress = new PublicKey(token?.address); + } catch (e) { + console.error('Invalid token address:', e); + return 0n; + } + const tokenAccounts = + await connection?.getParsedTokenAccountsByOwner(solAddress, { + mint: splTokenMintAddress, + }); + if (tokenAccounts?.value.length === 0) { + console.error('No token accounts found'); + return 0n; + } + const accountInfo = tokenAccounts?.value[0].account.data; + const balance = accountInfo?.parsed.info.tokenAmount + .amount as bigint; + return balance; + } + } catch (error) { + console.error('error on fetchBalance', { error }); + return; + } +}; diff --git a/src/components/views/donate/OnTime/SelectTokenModal/useFetchBalanceHook.tsx b/src/components/views/donate/OnTime/SelectTokenModal/useFetchBalanceHook.tsx deleted file mode 100644 index c8f6d8574d..0000000000 --- a/src/components/views/donate/OnTime/SelectTokenModal/useFetchBalanceHook.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useBalance } from 'wagmi'; -import { Address, zeroAddress } from 'viem'; -import { useSolanaBalance } from '@/hooks/useSolanaBalance'; -import { ChainType } from '@/types/config'; -import { useGeneralWallet } from '@/providers/generalWalletProvider'; -import { IProjectAcceptedToken } from '@/apollo/types/gqlTypes'; - -const useFetchBalance = (token: IProjectAcceptedToken | undefined) => { - const { walletAddress } = useGeneralWallet(); - const [balance, setBalance] = useState(undefined); - - const isEvm = token?.chainType === ChainType.EVM; - const address = walletAddress as Address | null; - - const evmBalanceQuery = useBalance({ - token: token?.address === zeroAddress ? undefined : token?.address, - address: address as Address, - query: { - enabled: !!token && isEvm && address !== null, - }, - }); - - const solanaBalanceQuery = useSolanaBalance({ - token: token?.address, - address: !isEvm && address !== null ? address : undefined, - }); - - useEffect(() => { - if (isEvm && evmBalanceQuery.data) { - setBalance(evmBalanceQuery.data.value); - } else if (!isEvm && solanaBalanceQuery.data) { - setBalance(solanaBalanceQuery.data); - } - }, [isEvm, evmBalanceQuery.data, solanaBalanceQuery.data]); - - return balance; -}; - -export default useFetchBalance; From 41a33abbaf8dc5f848cb155fbf180020ca2f2be5 Mon Sep 17 00:00:00 2001 From: Meriem-BM Date: Fri, 30 Aug 2024 15:11:24 +0100 Subject: [PATCH 12/49] fix: add validation for adding stellar project address with memo --- src/apollo/gql/gqlProjects.ts | 4 +-- src/components/views/create/CreateProject.tsx | 11 ++++++ .../views/create/WalletAddressInput.tsx | 36 +++++++++++-------- src/components/views/create/helpers.ts | 13 +++++-- src/hooks/useQRCodeDonation.ts | 1 - 5 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/apollo/gql/gqlProjects.ts b/src/apollo/gql/gqlProjects.ts index e3559d5429..ca20e27d18 100644 --- a/src/apollo/gql/gqlProjects.ts +++ b/src/apollo/gql/gqlProjects.ts @@ -491,8 +491,8 @@ export const UPLOAD_IMAGE = gql` `; export const WALLET_ADDRESS_IS_VALID = gql` - query WalletAddressIsValid($address: String!) { - walletAddressIsValid(address: $address) + query WalletAddressIsValid($address: String!, $chainType: String, $memo: String) { + walletAddressIsValid(address: $address, chainType: $chainType, memo: $memo) } `; diff --git a/src/components/views/create/CreateProject.tsx b/src/components/views/create/CreateProject.tsx index 98cf89dc7e..e1507dcaf8 100644 --- a/src/components/views/create/CreateProject.tsx +++ b/src/components/views/create/CreateProject.tsx @@ -349,6 +349,17 @@ const CreateProject: FC = ({ project }) => { return; } + // replace memo with undefined if it is not a stellar chain or if it is a stellar chain but memo is empty + addresses.forEach(address => { + if ( + address.chainType !== ChainType.STELLAR || + !address.memo || + address.memo === '' + ) { + address.memo = undefined; + } + }); + const projectData: IProjectCreation = { title: name, description: description!, diff --git a/src/components/views/create/WalletAddressInput.tsx b/src/components/views/create/WalletAddressInput.tsx index ef1325a187..8a16c47d33 100644 --- a/src/components/views/create/WalletAddressInput.tsx +++ b/src/components/views/create/WalletAddressInput.tsx @@ -111,7 +111,7 @@ const WalletAddressInput: FC = ({ else throw formatMessage({ id: 'label.invalid_ens_address' }); }; - const addressValidation = async (address?: string) => { + const addressValidation = async (address?: string, memo?: string) => { try { setError({ ...error, message: '' }); setResolvedENS(undefined); @@ -151,7 +151,11 @@ const WalletAddressInput: FC = ({ { type: 'ETH' }, ); } - const res = await gqlAddressValidation(_address); + const res = await gqlAddressValidation({ + address: _address, + chainType, + memo: isStellarChain ? memo : undefined, + }); setIsValidating(false); return res; } catch (e: any) { @@ -180,16 +184,16 @@ const WalletAddressInput: FC = ({ useEffect(() => { //We had an issue with onBlur so when the user clicks on submit exactly after filling the address, then process of address validation began, so i changed it to this. - if (walletAddressValue === prevAddress) + if (walletAddressValue === prevAddress && memoValue === prevMemo) setError({ ...error, message: '' }); - addressValidation(walletAddressValue).then(res => { + addressValidation(walletAddressValue, memoValue).then(res => { if (res === true) { setError({ ...error, message: '' }); return; } setError({ ...error, message: res }); }); - }, [walletAddressValue]); + }, [walletAddressValue, memoValue]); const [inputRef] = useFocus(); @@ -234,15 +238,6 @@ const WalletAddressInput: FC = ({ !error.message || !walletAddressValue ? undefined : error } /> - {delayedIsAddressUsed && ( - - )} {isStellarChain && ( <>
@@ -255,9 +250,22 @@ const WalletAddressInput: FC = ({ size={InputSize.LARGE} value={memoValue} onChange={e => setMemoValue(e.target.value)} + maxLength={28} /> )} + {delayedIsAddressUsed && ( + + )} {delayedResolvedENS && ( { try { @@ -19,11 +20,19 @@ export const gqlTitleValidation = async (title: string, locale: string) => { } }; -export const gqlAddressValidation = async (address: string) => { +export const gqlAddressValidation = async ({ + address, + chainType, + memo, +}: { + address: string; + chainType?: ChainType; + memo?: string; +}) => { try { const { data } = await client.query({ query: WALLET_ADDRESS_IS_VALID, - variables: { address }, + variables: { address, chainType, memo }, }); return data.walletAddressIsValid; } catch (error: any) { diff --git a/src/hooks/useQRCodeDonation.ts b/src/hooks/useQRCodeDonation.ts index 363f514798..c254b2c54a 100644 --- a/src/hooks/useQRCodeDonation.ts +++ b/src/hooks/useQRCodeDonation.ts @@ -91,7 +91,6 @@ export const useQRCodeDonation = () => { }); // save draft donation to local storage - const localStorageItem = localStorage.getItem( StorageLabel.DRAFT_DONATIONS, ); From 603076af3cb57698d02c679a50ff446a42bf75bb Mon Sep 17 00:00:00 2001 From: Meriem-BM Date: Fri, 30 Aug 2024 15:15:34 +0100 Subject: [PATCH 13/49] fix: change text when time is up --- .../SelectTokenModal/QRCodeDonation/QRDonationDetails.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationDetails.tsx b/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationDetails.tsx index 4414531654..35269b0497 100644 --- a/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationDetails.tsx +++ b/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationDetails.tsx @@ -186,7 +186,9 @@ const QRDonationDetails = () => { $alignItems='center' > - {formatMessage({ id: 'label.please_wait' })} + {formatMessage({ + id: isFailedOperation ? 'label.the_time_is_up' : 'label.please_wait', + })} From 9c6622b075e72b5bfe73345570028b87c95974e7 Mon Sep 17 00:00:00 2001 From: Meriem-BM Date: Fri, 30 Aug 2024 15:25:38 +0100 Subject: [PATCH 14/49] fix: update QR code issue --- lang/ca.json | 2 + lang/en.json | 2 + lang/es.json | 2 + .../QRCodeDonation/QRDonationCard.tsx | 2 - .../transaction/DonationStatusSection.tsx | 52 +++++++++++++------ 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/lang/ca.json b/lang/ca.json index 791e811348..8c438ac827 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -1011,6 +1011,7 @@ "label.the_service_is_a_kycfree_authorized_financial_intermediary": "El servei és un intermediari financer autoritzat lliure de KYC, amb seu a Suïssa", "label.think_about_where_your_potential_donors_might_look_for_a_project_like_yours": "Pensa on els teus possibles donants podrien buscar un projecte com el teu.", "label.this_address_is_already_used": "Aquesta adreça ja s'utilitza per a un altre projecte. Si us plau, introduïu una adreça que no estigui associada actualment a cap altre projecte.", + "label.this_address_and_memo_is_already_used": "Aquesta adreça i nota ja s'utilitzen per a un altre projecte. Introduïu una adreça amb una nota diferent.", "label.this_documentation": "aquest article de documentació", "label.this_farm_has_ended": "Aquesta farm ha finalitzat", "label.this_feature_will_be_available_soon": "Aquesta funció estarà disponible aviat.", @@ -1228,6 +1229,7 @@ "label.new_qr_code_needed": "Si no ho has aconseguit a temps, has de generar un nou codi QR.", "label.please_wait_for_you_donation_to_come_through": "Espera que la teva donació es processi!", "label.please_wait": "Espera", + "label.the_time_is_up": "S'ha acabat el temps.", "label.the_community_of_makers": "La comunitat de creadors", "page.project.we_are_supporting_stellar": "Estem donant suport a Stellar", "page.project.donate_with_stellar": "Dona amb Stellar", diff --git a/lang/en.json b/lang/en.json index ee0d5f2299..e07dffe1cb 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1014,6 +1014,7 @@ "label.the_service_is_a_kycfree_authorized_financial_intermediary": "The service is a KYC-free authorized financial intermediary based in Switzerland", "label.think_about_where_your_potential_donors_might_look_for_a_project_like_yours": "Think about where your potential donors might look for a project like yours.", "label.this_address_is_already_used": "This address is already used for another project. Please enter an address which is not currently associated with any other project.", + "label.this_address_and_memo_is_already_used": "This address and memo is already used for another project. Please enter an address with a different memo.", "label.this_documentation": "this documentation article", "label.this_farm_has_ended": "This farm has ended", "label.this_feature_will_be_available_soon": "This feature will be available soon.", @@ -1232,6 +1233,7 @@ "label.new_qr_code_needed": "If you didn’t make it in time, you need to generate a new QR code.", "label.please_wait_for_you_donation_to_come_through": "Please wait for your donation to come through!", "label.please_wait": "Please wait", + "label.the_time_is_up": "The time is up.", "label.the_community_of_makers": "The community of makers", "page.project.we_are_supporting_stellar": "We are supporting Stellar", "page.project.donate_with_stellar": "Donate with Stellar", diff --git a/lang/es.json b/lang/es.json index 22caedb7e9..e7151c5964 100644 --- a/lang/es.json +++ b/lang/es.json @@ -1011,6 +1011,7 @@ "label.the_service_is_a_kycfree_authorized_financial_intermediary": "Son una entidad financiera autorizada localizada en Suiza, libre de KYC", "label.think_about_where_your_potential_donors_might_look_for_a_project_like_yours": "Piensa dónde tus posibles donantes podrían buscar un proyecto como el tuyo.", "label.this_address_is_already_used": "Esta dirección ya esta en uso para otro proyecto. Por favor ingrese una dirección que no este actualmente asociada a ningún otro proyecto.", + "label.this_address_and_memo_is_already_used": "Esta dirección y esta nota ya se han utilizado para otro proyecto. Por favor, introduzca una dirección con una nota diferente.", "label.this_documentation": "este artículo de documentación", "label.this_farm_has_ended": "Esta Farm ha terminado", "label.this_feature_will_be_available_soon": "Esta función estará disponible pronto.", @@ -1228,6 +1229,7 @@ "label.new_qr_code_needed": "Si no lo lograste a tiempo, necesitas generar un nuevo código QR.", "label.please_wait_for_you_donation_to_come_through": "¡Por favor espera a que tu donación se procese!", "label.please_wait": "Por favor espera", + "label.the_time_is_up": "Se acabó el tiempo.", "label.the_community_of_makers": "La comunidad de creadores", "page.project.we_are_supporting_stellar": "Estamos apoyando Stellar", "page.project.donate_with_stellar": "Dona con Stellar", diff --git a/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationCard.tsx b/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationCard.tsx index 1ca5791292..dd05f843bf 100644 --- a/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationCard.tsx +++ b/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationCard.tsx @@ -106,8 +106,6 @@ export const QRDonationCard: FC = ({ } }; - eventSource.onopen = () => console.log('>>> Connection opened!'); - eventSource.onmessage = (event: MessageEvent) => { console.log('event ===> ', event); const { data, type } = JSON.parse(event.data); diff --git a/src/components/views/transaction/DonationStatusSection.tsx b/src/components/views/transaction/DonationStatusSection.tsx index b6c9070029..2474e53890 100644 --- a/src/components/views/transaction/DonationStatusSection.tsx +++ b/src/components/views/transaction/DonationStatusSection.tsx @@ -28,6 +28,16 @@ import { client } from '@/apollo/apolloClient'; import { MARK_DRAFT_DONATION_AS_FAILED } from '@/apollo/gql/gqlDonations'; type IColor = 'golden' | 'jade' | 'punch' | 'blueSky'; +interface TimerProps { + status: TQRStatus; + endDate: Date; + locale: string; + draftDonationId: number; + setStatus: (status: TQRStatus) => void; + checkDraftDonationStatus: ( + draftDonationId: number, + ) => Promise; +} const StatusMap: Record = { pending: { @@ -94,16 +104,14 @@ const formatTime = (date: Date, locale: string) => { }; // Timer that keep counting time before the donation expires (mm Minutes ss Seconds) format -const Timer = ( - status: TQRStatus, - endDate: Date, - locale: string, - draftDonationId: number, - setStatus: (status: TQRStatus) => void, - checkDraftDonationStatus: ( - draftDonationId: number, - ) => Promise, -) => { +const Timer: React.FC = ({ + status, + endDate, + locale, + draftDonationId, + setStatus, + checkDraftDonationStatus, +}) => { const _endDate = new Date(endDate.toLocaleString(locale)); const calculateTimeLeft = () => { @@ -135,11 +143,12 @@ const Timer = ( } }; - const tick = () => { + const tick = async () => { const timeLeft = calculateTimeLeft(); if (timeLeft.minutes === 0 && timeLeft.seconds === 0) { - handleTimeout(); + await handleTimeout(); + clearInterval(interval); // Stop the interval after handling timeout return; } @@ -149,7 +158,14 @@ const Timer = ( const interval = setInterval(tick, 1000); return () => clearInterval(interval); - }, [_endDate]); + }, [ + _endDate, + draftDonationId, + status, + locale, + checkDraftDonationStatus, + setStatus, + ]); return (time.minutes === 0 && time.seconds === 0) || status === 'failed' ? ( @@ -294,14 +310,16 @@ const DonationStatusSection: FC = ({ ) ) : ( - {Timer( + {Timer({ status, - new Date(draftDonationData?.expiresAt!), + endDate: new Date( + draftDonationData?.expiresAt!, + ), locale, - Number(draftDonationData.id), + draftDonationId: Number(draftDonationData.id), setStatus, checkDraftDonationStatus, - )} + })} )} From d66b1000f0da0608481bc17b93f875b089d2e23f Mon Sep 17 00:00:00 2001 From: Meriem-BM Date: Fri, 30 Aug 2024 15:30:13 +0100 Subject: [PATCH 15/49] fix: user can't update QR code while there is another open donation --- src/components/views/donate/DonateIndex.tsx | 80 +++++++++++++++++---- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/src/components/views/donate/DonateIndex.tsx b/src/components/views/donate/DonateIndex.tsx index d5950c7142..7082406204 100644 --- a/src/components/views/donate/DonateIndex.tsx +++ b/src/components/views/donate/DonateIndex.tsx @@ -38,12 +38,13 @@ import QRDonationDetails from './OnTime/SelectTokenModal/QRCodeDonation/QRDonati import InlineToast, { EToastType } from '@/components/toasts/InlineToast'; import { client } from '@/apollo/apolloClient'; import { FETCH_DONATION_BY_ID } from '@/apollo/gql/gqlDonations'; -import { IDonation } from '@/apollo/types/types'; +import { IDonation, IWalletAddress } from '@/apollo/types/types'; import config from '@/configuration'; import { ChainType } from '@/types/config'; import { useQRCodeDonation } from '@/hooks/useQRCodeDonation'; import EndaomentProjectsInfo from '@/components/views/project/EndaomentProjectsInfo'; import { IDraftDonation } from '@/apollo/types/gqlTypes'; +import StorageLabel from '@/lib/localStorage'; const DonateIndex: FC = () => { const { formatMessage } = useIntl(); @@ -57,9 +58,10 @@ const DonateIndex: FC = () => { setSuccessDonation, setQRDonationStatus, setDraftDonationData, + setPendingDonationExists, startTimer, } = useDonateData(); - const { renewExpirationDate } = useQRCodeDonation(); + const { renewExpirationDate, retrieveDraftDonation } = useQRCodeDonation(); const alreadyDonated = useAlreadyDonatedToProject(project); const dispatch = useAppDispatch(); @@ -124,18 +126,62 @@ const DonateIndex: FC = () => { const updateQRCode = async () => { if (!draftDonationData?.id) return; - const expiresAt = await renewExpirationDate(draftDonationData?.id); - setDraftDonationData((prev: IDraftDonation | null) => { - if (!prev) return null; - return { - ...prev, - status: 'pending', - expiresAt: expiresAt?.toString() ?? undefined, - }; - }); - setQRDonationStatus('waiting'); - const stopTimerFun = startTimer?.(new Date(expiresAt!)); - setStopTimer(() => stopTimerFun); + const draftDonations = localStorage.getItem( + StorageLabel.DRAFT_DONATIONS, + ); + + const parsedLocalStorageItem = JSON.parse(draftDonations!); + + const projectAddress: IWalletAddress | undefined = + project.addresses?.find( + address => address.chainType === ChainType.STELLAR, + ); + let draftDonationId = parsedLocalStorageItem + ? parsedLocalStorageItem[projectAddress?.address!] + : null; + + const retDraftDonation = !!draftDonationId + ? await retrieveDraftDonation(Number(draftDonationId)) + : null; + + if (retDraftDonation && retDraftDonation.status === 'pending') { + setPendingDonationExists?.(true); + parsedLocalStorageItem[projectAddress?.address!] = + retDraftDonation.id; + localStorage.setItem( + StorageLabel.DRAFT_DONATIONS, + JSON.stringify(parsedLocalStorageItem), + ); + router.push( + { + query: { + ...router.query, + draft_donation: retDraftDonation.id, + }, + }, + undefined, + { shallow: true }, + ); + } else { + const expiresAt = await renewExpirationDate(draftDonationData?.id); + setDraftDonationData((prev: IDraftDonation | null) => { + if (!prev) return null; + return { + ...prev, + status: 'pending', + expiresAt: expiresAt?.toString() ?? undefined, + }; + }); + parsedLocalStorageItem[projectAddress?.address!] = + draftDonationData.id; + localStorage.setItem( + StorageLabel.DRAFT_DONATIONS, + JSON.stringify(parsedLocalStorageItem), + ); + setQRDonationStatus('waiting'); + const stopTimerFun = startTimer?.(new Date(expiresAt!)); + setStopTimer(() => stopTimerFun); + } }; useEffect(() => { @@ -143,6 +189,12 @@ const DonateIndex: FC = () => { else setStopTimer(() => undefined); }, [showQRCode]); + useEffect(() => { + if (qrDonationStatus === 'failed') { + stopTimer?.(); + } + }, [qrDonationStatus]); + return successDonation ? ( <> From 1a5c6006431876ae6fdd792af1108325a68afa40 Mon Sep 17 00:00:00 2001 From: Meriem-BM Date: Fri, 30 Aug 2024 15:32:19 +0100 Subject: [PATCH 16/49] fix: add check_donations link to receipt page on succuss donation --- src/components/views/donate/DonationInfo.tsx | 30 +++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/components/views/donate/DonationInfo.tsx b/src/components/views/donate/DonationInfo.tsx index eeb6a9cc34..47f9c7bf30 100644 --- a/src/components/views/donate/DonationInfo.tsx +++ b/src/components/views/donate/DonationInfo.tsx @@ -54,9 +54,10 @@ const TxRow = ({ export const DonationInfo = () => { const { formatMessage } = useIntl(); - const { successDonation, project } = useDonateData(); + const { successDonation, project, draftDonationData } = useDonateData(); const { txHash = [] } = successDonation || {}; const hasMultipleTxs = txHash.length > 1; + const isStellar = txHash[0]?.chainType === ChainType.STELLAR; return ( @@ -64,6 +65,24 @@ export const DonationInfo = () => { {formatMessage({ id: 'label.your_transactions_have_been_submitted', })} + {isStellar && ( + + {' '} + + window.open( + Routes.Invoice + + '/' + + draftDonationData?.id, + '_blank', + ) + } + > + {formatMessage({ id: 'label.check_donations' })}{' '} + + + + )}
{formatMessage({ id: 'label.you_can_view_them_on_a_blockchain_explorer_here', @@ -110,3 +129,12 @@ const TxLink = styled(Lead)` color: ${neutralColors.gray[700]}; } `; + +const CheckDonation = styled.span` + display: inline-flex; + gap: 8px; + align-items: center; + cursor: pointer; + text-transform: capitalize; + color: ${brandColors.pinky[500]} !important; +`; From ee158271977323ebf0fed814112d75eec7f515b5 Mon Sep 17 00:00:00 2001 From: Meriem-BM Date: Sun, 1 Sep 2024 05:07:28 +0100 Subject: [PATCH 17/49] fix: linter errors --- src/apollo/gql/gqlProjects.ts | 12 ++++++++++-- .../QRCodeDonation/QRDonationDetails.tsx | 4 +++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/apollo/gql/gqlProjects.ts b/src/apollo/gql/gqlProjects.ts index ca20e27d18..2b59212024 100644 --- a/src/apollo/gql/gqlProjects.ts +++ b/src/apollo/gql/gqlProjects.ts @@ -491,8 +491,16 @@ export const UPLOAD_IMAGE = gql` `; export const WALLET_ADDRESS_IS_VALID = gql` - query WalletAddressIsValid($address: String!, $chainType: String, $memo: String) { - walletAddressIsValid(address: $address, chainType: $chainType, memo: $memo) + query WalletAddressIsValid( + $address: String! + $chainType: String + $memo: String + ) { + walletAddressIsValid( + address: $address + chainType: $chainType + memo: $memo + ) } `; diff --git a/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationDetails.tsx b/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationDetails.tsx index 35269b0497..83cc78fc8c 100644 --- a/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationDetails.tsx +++ b/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationDetails.tsx @@ -187,7 +187,9 @@ const QRDonationDetails = () => { > {formatMessage({ - id: isFailedOperation ? 'label.the_time_is_up' : 'label.please_wait', + id: isFailedOperation + ? 'label.the_time_is_up' + : 'label.please_wait', })} Date: Sun, 1 Sep 2024 07:47:08 +0100 Subject: [PATCH 18/49] fix: add validation with memo for manage address --- lang/ca.json | 2 +- lang/en.json | 2 +- lang/es.json | 2 +- src/components/modals/ManageProjectAddresses/AddNewAddress.tsx | 1 + src/config/production.tsx | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lang/ca.json b/lang/ca.json index b3241b7c91..d727b894b8 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -1011,7 +1011,7 @@ "label.the_service_is_a_kycfree_authorized_financial_intermediary": "El servei és un intermediari financer autoritzat lliure de KYC, amb seu a Suïssa", "label.think_about_where_your_potential_donors_might_look_for_a_project_like_yours": "Pensa on els teus possibles donants podrien buscar un projecte com el teu.", "label.this_address_is_already_used": "Aquesta adreça ja s'utilitza per a un altre projecte. Si us plau, introduïu una adreça que no estigui associada actualment a cap altre projecte.", - "label.this_address_and_memo_is_already_used": "Aquesta adreça i nota ja s'utilitzen per a un altre projecte. Introduïu una adreça amb una nota diferent.", + "label.this_address_and_memo_is_already_used": "Aquesta adreça ja s'utilitza per a un altre projecte amb el mateix MEMO. Introduïu una adreça diferent o un MEMO diferent.", "label.this_documentation": "aquest article de documentació", "label.this_farm_has_ended": "Aquesta farm ha finalitzat", "label.this_feature_will_be_available_soon": "Aquesta funció estarà disponible aviat.", diff --git a/lang/en.json b/lang/en.json index 5743d5ec89..876605f8cd 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1014,7 +1014,7 @@ "label.the_service_is_a_kycfree_authorized_financial_intermediary": "The service is a KYC-free authorized financial intermediary based in Switzerland", "label.think_about_where_your_potential_donors_might_look_for_a_project_like_yours": "Think about where your potential donors might look for a project like yours.", "label.this_address_is_already_used": "This address is already used for another project. Please enter an address which is not currently associated with any other project.", - "label.this_address_and_memo_is_already_used": "This address and memo is already used for another project. Please enter an address with a different memo.", + "label.this_address_and_memo_is_already_used": "This address is already used for another project with the same MEMO. Please enter a different address or a different MEMO.", "label.this_documentation": "this documentation article", "label.this_farm_has_ended": "This farm has ended", "label.this_feature_will_be_available_soon": "This feature will be available soon.", diff --git a/lang/es.json b/lang/es.json index 04d4b42985..7f4f591bed 100644 --- a/lang/es.json +++ b/lang/es.json @@ -1011,7 +1011,7 @@ "label.the_service_is_a_kycfree_authorized_financial_intermediary": "Son una entidad financiera autorizada localizada en Suiza, libre de KYC", "label.think_about_where_your_potential_donors_might_look_for_a_project_like_yours": "Piensa dónde tus posibles donantes podrían buscar un proyecto como el tuyo.", "label.this_address_is_already_used": "Esta dirección ya esta en uso para otro proyecto. Por favor ingrese una dirección que no este actualmente asociada a ningún otro proyecto.", - "label.this_address_and_memo_is_already_used": "Esta dirección y esta nota ya se han utilizado para otro proyecto. Por favor, introduzca una dirección con una nota diferente.", + "label.this_address_and_memo_is_already_used": "Esta dirección ya se utiliza para otro proyecto con el mismo MEMO. Por favor, introduzca una dirección diferente o un MEMO diferente.", "label.this_documentation": "este artículo de documentación", "label.this_farm_has_ended": "Esta Farm ha terminado", "label.this_feature_will_be_available_soon": "Esta función estará disponible pronto.", diff --git a/src/components/modals/ManageProjectAddresses/AddNewAddress.tsx b/src/components/modals/ManageProjectAddresses/AddNewAddress.tsx index 7b23ab8eb9..2f0118338f 100644 --- a/src/components/modals/ManageProjectAddresses/AddNewAddress.tsx +++ b/src/components/modals/ManageProjectAddresses/AddNewAddress.tsx @@ -153,6 +153,7 @@ export const AddNewAddress: FC = ({ placeholder={formatMessage({ id: 'label.enter_the_memo', })} + maxLength={28} /> )} {errors.address && ( diff --git a/src/config/production.tsx b/src/config/production.tsx index deb0c5f74c..0014d7ffe5 100644 --- a/src/config/production.tsx +++ b/src/config/production.tsx @@ -94,7 +94,7 @@ const EVM_CHAINS = [ polygonZkEvm, ] as readonly [Chain, ...Chain[]]; -const NON_EVM_CHAINS: NonEVMChain[] = [SOLANA_NETWORK, STELLAR_NOTWORK]; +const NON_EVM_CHAINS: NonEVMChain[] = [STELLAR_NOTWORK, SOLANA_NETWORK]; const BASE_ROUTE = process.env.NEXT_PUBLIC_BASE_ROUTE || 'https://mainnet.serve.giveth.io'; From f6105b1bd282c9e02912d672db1d9a53891b8298 Mon Sep 17 00:00:00 2001 From: Meriem-BM Date: Sun, 1 Sep 2024 19:26:59 +0100 Subject: [PATCH 19/49] fix: show more accurate Stellar USD amount --- .../QRCodeDonation/QRDonationCard.tsx | 126 ++++++++----- .../QRCodeDonation/QRDonationDetails.tsx | 3 +- .../views/transaction/Transaction.view.tsx | 170 +++++++++--------- 3 files changed, 161 insertions(+), 138 deletions(-) diff --git a/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationCard.tsx b/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationCard.tsx index dd05f843bf..6ecf83d2a1 100644 --- a/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationCard.tsx +++ b/src/components/views/donate/OnTime/SelectTokenModal/QRCodeDonation/QRDonationCard.tsx @@ -9,10 +9,13 @@ import { Flex, neutralColors, IconArrowLeft, + Caption, mediaQueries, + semanticColors, } from '@giveth/ui-design-system'; import { useIntl } from 'react-intl'; import { formatUnits } from 'viem'; + import { InputWrapper, SelectTokenWrapper, @@ -33,12 +36,18 @@ import { IWalletAddress } from '@/apollo/types/types'; import InlineToast, { EToastType } from '@/components/toasts/InlineToast'; import { useAppSelector } from '@/features/hooks'; import { useModalCallback } from '@/hooks/useModalCallback'; +import links from '@/lib/constants/links'; interface QRDonationCardProps extends IDonationCardProps { qrAcceptedTokens: IProjectAcceptedToken[]; setIsQRDonation: (isQRDonation: boolean) => void; } -export const formatAmoutToDisplay = (amount: bigint) => { + +interface IMessage { + text: string; +} + +const formatAmountToDisplay = (amount: bigint) => { const decimals = 18; return truncateToDecimalPlaces( formatUnits(amount, decimals), @@ -46,6 +55,17 @@ export const formatAmoutToDisplay = (amount: bigint) => { ).toString(); }; +const Message: FC = ({ text }) => ( + + {text}{' '} + + + Learn more + + + +); + export const QRDonationCard: FC = ({ showQRCode, qrAcceptedTokens, @@ -54,7 +74,6 @@ export const QRDonationCard: FC = ({ }) => { const { formatMessage } = useIntl(); const router = useRouter(); - const { isSignedIn, isEnabled } = useAppSelector(state => state.user); const [showDonateModal, setShowDonateModal] = useState(false); const { modalCallback: signInThenDonate } = useModalCallback(() => @@ -78,13 +97,13 @@ export const QRDonationCard: FC = ({ draftDonationData, draftDonationLoading, } = useDonateData(); - const { addresses, id } = project; + const { addresses, id } = project; const draftDonationId = Number(router.query.draft_donation!); - const [amount, setAmount] = useState(0n); const [usdAmount, setUsdAmount] = useState('0.00'); const [tokenPrice, setTokenPrice] = useState(0); + const stellarToken = qrAcceptedTokens.find( token => token.chainType === ChainType.STELLAR, ); @@ -102,22 +121,21 @@ export const QRDonationCard: FC = ({ if (draftDonation?.status === 'matched') { setQRDonationStatus('success'); setDraftDonationData(draftDonation); - return; } }; eventSource.onmessage = (event: MessageEvent) => { - console.log('event ===> ', event); const { data, type } = JSON.parse(event.data); - - if (type === 'new-donation') { - if (data.draftDonationId === draftDonationId) { - handleFetchDraftDonation?.(draftDonationId); - } - } else if (type === 'draft-donation-failed') { - if (data.draftDonationId === draftDonationId) { - setQRDonationStatus('failed'); - } + if ( + type === 'new-donation' && + data.draftDonationId === draftDonationId + ) { + handleFetchDraftDonation(draftDonationId); + } else if ( + type === 'draft-donation-failed' && + data.draftDonationId === draftDonationId + ) { + setQRDonationStatus('failed'); } }; @@ -139,7 +157,6 @@ export const QRDonationCard: FC = ({ setDraftDonationData(draftDonation); return; } - await markDraftDonationAsFailed(draftDonationId); setPendingDonationExists?.(false); setShowQRCode(false); @@ -156,18 +173,15 @@ export const QRDonationCard: FC = ({ const draftDonations = localStorage.getItem( StorageLabel.DRAFT_DONATIONS, ); - const parsedLocalStorageItem = JSON.parse(draftDonations!); - - const projectAddress: IWalletAddress | undefined = - project.addresses?.find( - address => address.chainType === ChainType.STELLAR, - ); + const projectAddress = project.addresses?.find( + address => address.chainType === ChainType.STELLAR, + ); let draftDonationId = parsedLocalStorageItem ? parsedLocalStorageItem[projectAddress?.address!] : null; - const retDraftDonation = !!draftDonationId + const retDraftDonation = draftDonationId ? await retrieveDraftDonation(Number(draftDonationId)) : null; @@ -176,10 +190,10 @@ export const QRDonationCard: FC = ({ } else { if (!stellarToken?.symbol || !projectAddress?.address) return; - const patyload = { + const payload = { walletAddress: projectAddress.address, projectId: Number(id), - amount: Number(formatAmoutToDisplay(amount)), + amount: Number(formatAmountToDisplay(amount)), token: stellarToken, anonymous: true, symbol: stellarToken.symbol, @@ -189,7 +203,7 @@ export const QRDonationCard: FC = ({ memo: projectAddress.memo, }; - draftDonationId = await createDraftDonation(patyload); + draftDonationId = await createDraftDonation(payload); setPendingDonationExists?.(false); } @@ -209,21 +223,16 @@ export const QRDonationCard: FC = ({ } }; - const convertAmountToUSD = (amount: bigint) => { - if (!stellarToken) { - return '0.00'; - } - - if (tokenPrice) { - const priceBigInt = BigInt(Math.floor(tokenPrice * 100)); - const amountInUsd = (amount * priceBigInt) / 100n; - - const _displayAmount = formatAmoutToDisplay(amountInUsd); + const convertAmountToUSD = (amount: bigint | number) => { + if (!stellarToken || !tokenPrice) return '0.00'; - return formatBalance(_displayAmount); - } else { - return '0.00'; + if (typeof amount === 'number') { + return formatBalance(amount * tokenPrice); } + + const priceBigInt = BigInt(Math.floor(tokenPrice * 100)); + const amountInUsd = (amount * priceBigInt) / 100n; + return formatBalance(formatAmountToDisplay(amountInUsd)); }; useEffect(() => { @@ -235,9 +244,7 @@ export const QRDonationCard: FC = ({ const coingeckoChainId = config.NETWORKS_CONFIG[ChainType.STELLAR].coingeckoChainName; const price = await fetchPriceWithCoingeckoId(coingeckoChainId); - if (price) { - setTokenPrice(price); - } + if (price) setTokenPrice(price); }; fetchTokenPrice(); @@ -306,7 +313,7 @@ export const QRDonationCard: FC = ({ {project.title || '--'}

- {formatAmoutToDisplay(amount)} + {formatAmountToDisplay(amount)} = ({ id: 'label.your_total_donation', })} - {formatAmoutToDisplay(amount)} + {formatAmountToDisplay(amount)}