diff --git a/lang/ca.json b/lang/ca.json index 6510611ca5..9ab3bededc 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -824,7 +824,6 @@ "label.qf_eligibility": "Actualment en ronda de QF", "label.qf_no_donations": "No hi ha hagut donacions en aquesta ronda.", "label.qf_round_projects": "Projectes de la ronda QF", - "label.qr_code_error": "S'ha produït un error en generar el codi QR. Torneu-ho a provar.", "label.qr_code_expired": "El codi QR ha caducat, sol·licita un nou codi QR!", "label.quadratic_funding": "Finançament quadràtic", "label.questions_contact_support": "Preguntes? Contacta amb el Suport", @@ -1135,7 +1134,6 @@ "label.to_participate_for_real_claim_your_giv": "Per participar de veritat, reclama el teu GIV.", "label.to_see_your_givpower_please_connect": "Per veure el teu GIVpower, si us plau connecta la teva cartera.", "label.traceable_project": "Projecte Traçable", - "label.transaction_detail": "Detall de la transacció", "label.transaction_link": "Enllaç de la transacció", "label.transaction_status": "Estat de la transacció", "label.trust_that_your_donations_will_make": "Confia que les teves donacions en cripto tindran un impacte amb el nostre sistema de verificació.", @@ -1358,14 +1356,10 @@ "page.donate.passport_toast.description.not_connected": "Obtén el teu emparellament de donació amb finançament quadràtic!\nVerifica el teu Gitcoin Passport abans de", "page.donate.passport_toast.title.eligible": "Finançament Quadràtic", "page.donate.passport_toast.title.non_eligible": "No et perdis l'emparellament!", - "page.donate.title": "Donar", - "page.donate.zero_fees": "Sense Comissions", "page.donate.makes_you_eligible_for_givbacks": "${value} et fan elegible per a GIVbacks", "page.donate.unlocks_matching_funds": "${value} desbloquegen fons coincidents", - "page.donate.donations_will_be_matched": "La donació serà igualada", "page.donate.project_not_eligible_for_qf": "El projecte no és elegible per a la concordança QF.", "page.donate.network_not_eligible_for_qf": "Les donacions de {network} no són aptes per coincidir", - "page.donate.givbacks_eligible": "GIVbacks elegibles", "page.donate.project_not_givbacks_eligible": "El projecte no és elegible per a GIVbacks", "page.donate.title": "Donar", "page.donate.token_not_givbacks_eligible": "{token} no és apte per a GIVbacks", @@ -1669,9 +1663,7 @@ "project.givback_toast.description.non_verified_public": "Actualment, els GIVbacks només s'atorguen per donacions fetes a projectes elegibles per a GIVbacks a Ethereum. La teva contribució segueix sent important, fins i tot si no genera GIVbacks!", "project.givback_toast.description.verified_owner": "Potencia el teu projecte per augmentar el seu percentatge de GIVbacks i ajudar-lo a aparèixer més amunt a la pàgina de projectes!", "project.givback_toast.description.verified_public": "Les donacions a Ethereum a projectes elegibles per a GIVbacks són recompensades amb GIV. Impulsa aquest projecte per augmentar el seu percentatge de recompenses i fer-lo més visible a la pàgina de projectes!", "project.givback_toast.title.non_verified_owner": "El teu projecte està creant o donant suport a béns públics?", - "project.givback_toast.description.verified_public": "Les donacions de ${value} o més són elegibles per a GIVback. Potencia aquest projecte per augmentar el seu percentatge de recompenses i la seva visibilitat a la pàgina de projectes!", "project.givback_toast.description.verified_owner_not_eligible": "El teu projecte ha estat avalat pels Verificadors de Giveth i ara pot beneficiar-se de GIVpower. Fes stake i bloqueja els teus tokens GIV per impulsar aquest projecte i fer-lo més visible a la pàgina de projectes. No obstant això, donar a aquest projecte no generarà GIVbacks per als donants.", - "project.givback_toast.title.non_verified_owner": "El teu projecte està creant o donant suport a béns públics?", "project.givback_toast.title.non_verified_owner_cancelled": "Estat Cancel·lat", "project.givback_toast.title.non_verified_owner_deactive": "Mode Desactivat", "project.givback_toast.title.non_verified_owner_draft": "Publica el teu projecte avui!", diff --git a/lang/en.json b/lang/en.json index dd77cddd15..117c421828 100644 --- a/lang/en.json +++ b/lang/en.json @@ -890,9 +890,6 @@ "label.runout_info": "Stream Balance will run out funds on ", "label.runout_info_topup": "Top-up before then!", "label.runs_out_in": "Runs out in", - "label.sanctioned_wallet": "Sanctioned Address Detected !!", - "label.sanctioned_wallet_message_part1": "This address has been found on the USA", - "label.sanctioned_wallet_message_part2": "sanctioned list.\nUnfortunately, Endaoment does not permit addresses on the\nOFAC sanction list to donate to projects delivered by\nEndaoment. Check out another project to donate to. ", "label.save": "Save", "label.save_changes": "Save Changes", "label.save_on_gas_fees": "Save on gas fees, switch network.", @@ -1188,7 +1185,6 @@ "label.verify_your_project.modal.three": "GIVbacks eligibility process ", "label.verify_your_project.modal.two": "This simple ", "label.view": "View", - "label.view_all_projects": "View all projects", "label.view_details": "View details", "label.view_more": "View more", "label.view_on_block_explorer": "View on block explorer", @@ -1349,8 +1345,6 @@ "page.donate.bank_fees": "Bank Fees", "page.donate.donate_$_to_be_eligible": "Donate ${value} to be eligible for GIVbacks", "page.donate.donate_$_to_get_matched": "Donate ${value} to get your donation matched", - "page.donate.donations_will_be_matched": "Donation will be matched", - "page.donate.givbacks_eligible": "GIVbacks eligible", "page.donate.matching_toast.bottom_invalid_p1": "Only donations more than", "page.donate.matching_toast.bottom_invalid_p2": "are eligible for matching.", "page.donate.matching_toast.bottom_valid": "Matching funds will be sent to the selected project after the round ends. Donate to more projects to receive higher matching!", @@ -1360,8 +1354,6 @@ "page.donate.passport_toast.description.not_connected": "Get your donation matched with quadratic funding!\nVerify your Gitcoin Passport before", "page.donate.passport_toast.title.eligible": "Quadratic Funding", "page.donate.passport_toast.title.non_eligible": "Don’t miss out on matching!", - "page.donate.title": "Donate", - "page.donate.zero_fees": "Zero Fees", "page.donate.makes_you_eligible_for_givbacks": "${value} makes you eligible for GIVbacks", "page.donate.unlocks_matching_funds": "${value} unlocks matching funds", "page.donate.donations_will_be_matched": "Donation will be matched", diff --git a/lang/es.json b/lang/es.json index 9b243e9797..bb51f1c78f 100644 --- a/lang/es.json +++ b/lang/es.json @@ -1345,8 +1345,6 @@ "page.donate.bank_fees": "Comisiones Bancarias", "page.donate.donate_$_to_be_eligible": "Dona ${value} para ser elegible para GIVbacks", "page.donate.donate_$_to_get_matched": "Dona ${value} para que tu donación sea igualada", - "page.donate.donations_will_be_matched": "La donación será igualada", - "page.donate.givbacks_eligible": "Elegibles para GIVbacks", "page.donate.matching_toast.bottom_invalid_p1": "Sólo las donaciones superiores a", "page.donate.matching_toast.bottom_invalid_p2": "son subvencionables.", "page.donate.matching_toast.bottom_valid": "Los fondos de emparejamiento se enviarán al proyecto seleccionado después de que termine la ronda. ¡Dona a más proyectos para recibir un mayor emparejamiento!", @@ -1356,9 +1354,7 @@ "page.donate.passport_toast.description.not_connected": "¡Haz que tu donación sea complementada con financiamiento cuadrático! Verifica tu Gitcoin Passport antes de", "page.donate.passport_toast.title.eligible": "Financiamiento Cuadrático", "page.donate.passport_toast.title.non_eligible": "¡No te pierdas la oportunidad!", - "page.donate.project_not_givbacks_eligible": "El proyecto no es elegible para GIVbacks", "page.donate.title": "Donar", - "page.donate.token_not_givbacks_eligible": "{token} no es elegible para GIVbacks", "page.donate.zero_fees": "Sin Comisiones", "page.donate.makes_you_eligible_for_givbacks": "${value} te hacen elegible para recibir GIVbacks", "page.donate.unlocks_matching_funds": "${value} desbloquean fondos equivalentes", diff --git a/src/components/modals/DonationByProjectOwner.tsx b/src/components/modals/DonationByProjectOwner.tsx index 62978d202b..ace4ed5397 100644 --- a/src/components/modals/DonationByProjectOwner.tsx +++ b/src/components/modals/DonationByProjectOwner.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { brandColors, Button, @@ -12,27 +12,37 @@ 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; + closeModal: () => void; } export const DonationByProjectOwner: React.FC = ({ - setShowDonationByProjectOwner, + closeModal, }) => { const { formatMessage } = useIntl(); const router = useRouter(); - const { closeModal } = useModalAnimation(setShowDonationByProjectOwner); + const [isRedirecting, setIsRedirecting] = useState(false); const navigateToAllProjects = () => { + setIsRedirecting(true); router.push(Routes.AllProjects); - closeModal(); }; + useEffect(() => { + const handleRouteChangeComplete = () => { + closeModal(); + setIsRedirecting(false); + }; + if (isRedirecting) { + router.events.on('routeChangeComplete', handleRouteChangeComplete); + } + return () => { + router.events.off('routeChangeComplete', handleRouteChangeComplete); + }; + }, [isRedirecting]); + return ( = ({ closeModal }) => { const { formatMessage } = useIntl(); + const [isRedirecting, setIsRedirecting] = useState(false); const router = useRouter(); const navigateToAllProjects = () => { + setIsRedirecting(true); router.push(Routes.AllProjects); - closeModal(); }; + useEffect(() => { + const handleRouteChangeComplete = () => { + closeModal(); + setIsRedirecting(false); + }; + if (isRedirecting) { + router.events.on('routeChangeComplete', handleRouteChangeComplete); + } + return () => { + router.events.off('routeChangeComplete', handleRouteChangeComplete); + }; + }, [isRedirecting]); + return ( { qrDonationStatus, draftDonationData, hasActiveQFRound, + shouldRenderModal, setSuccessDonation, setQRDonationStatus, setDraftDonationData, setPendingDonationExists, activeStartedRound, startTimer, + setDonateModalByPriority, + setIsModalPriorityChecked, } = useDonateData(); const { renewExpirationDate, retrieveDraftDonation } = useQRCodeDonation(project); @@ -71,8 +79,7 @@ const DonateIndex: FC = () => { const alreadyDonated = useAlreadyDonatedToProject(project); const { userData } = useAppSelector(state => state.user); - const [showDonationByProjectOwner, setShowDonationByProjectOwner] = - useState(false); + const dispatch = useAppDispatch(); const isSafeEnv = useIsSafeEnvironment(); const { isOnSolana } = useGeneralWallet(); @@ -81,6 +88,7 @@ const DonateIndex: FC = () => { const [showQRCode, setShowQRCode] = React.useState( !!router.query.draft_donation, ); + const { walletAddress: address } = useGeneralWallet(); const [stopTimer, setStopTimer] = React.useState void)>(); const isQRDonation = router.query.chain === ChainType.STELLAR.toLowerCase(); @@ -96,9 +104,37 @@ const DonateIndex: FC = () => { }; }, [dispatch]); + const validateSanctions = async () => { + if (project.organization?.label === 'endaoment' && address) { + // We just need to check if the wallet is sanctioned for endaoment projects + const sanctioned = await isWalletSanctioned(address); + if (sanctioned) { + setDonateModalByPriority( + DonateModalPriorityValues.OFACSanctionListModal, + ); + return; + } + } + setIsModalPriorityChecked( + DonateModalPriorityValues.OFACSanctionListModal, + ); + }; + + useEffect(() => { + validateSanctions(); + }, [project, address]); + useEffect(() => { - setShowDonationByProjectOwner( - userData?.id !== undefined && userData?.id === project.adminUser.id, + if ( + userData?.id !== undefined && + userData?.id === project.adminUser.id + ) { + setDonateModalByPriority( + DonateModalPriorityValues.DonationByProjectOwner, + ); + } + setIsModalPriorityChecked( + DonateModalPriorityValues.DonationByProjectOwner, ); }, [userData?.id, project.adminUser]); @@ -136,7 +172,7 @@ const DonateIndex: FC = () => { excludeFromQF: !includeInQF, givBackEligible: isTokenEligibleForGivback && - project.isGivbackEligible && + project.verified && isSignedIn && isEnabled && getDonationById.amount >= @@ -239,21 +275,31 @@ const DonateIndex: FC = () => { <> - {!isSafeEnv && - hasActiveQFRound && - !isOnSolana && - (!isQRDonation || - (isQRDonation && isStellarIncludedInQF)) && ( - - )} - {showDonationByProjectOwner && ( + {shouldRenderModal( + DonateModalPriorityValues.DonationByProjectOwner, + ) && ( { + setDonateModalByPriority( + DonateModalPriorityValues.None, + ); + }} /> )} + + {shouldRenderModal( + DonateModalPriorityValues.OFACSanctionListModal, + ) && ( + { + setDonateModalByPriority( + DonateModalPriorityValues.None, + ); + }} + /> + )} + {showAlreadyDonatedWrapper && ( @@ -264,6 +310,13 @@ const DonateIndex: FC = () => { )} + {!isSafeEnv && + hasActiveQFRound && + !isOnSolana && + (!isQRDonation || + (isQRDonation && isStellarIncludedInQF)) && ( + + )} diff --git a/src/components/views/donate/DonationCard.tsx b/src/components/views/donate/DonationCard.tsx index 36183e8ea2..2619db9d48 100644 --- a/src/components/views/donate/DonationCard.tsx +++ b/src/components/views/donate/DonationCard.tsx @@ -62,7 +62,6 @@ export const DonationCard: FC = ({ address.networkId === config.OPTIMISM_NETWORK_NUMBER, ); const isEndaomentProject = project?.organization?.label === 'endaoment'; - const isOwnerOnEVM = project?.adminUser?.walletAddress && isAddress(project.adminUser?.walletAddress); diff --git a/src/components/views/donate/OneTime/OneTimeDonationCard.tsx b/src/components/views/donate/OneTime/OneTimeDonationCard.tsx index 0ed6f35989..85d2663aea 100644 --- a/src/components/views/donate/OneTime/OneTimeDonationCard.tsx +++ b/src/components/views/donate/OneTime/OneTimeDonationCard.tsx @@ -35,7 +35,10 @@ import { useAppDispatch, useAppSelector } from '@/features/hooks'; import DonateToGiveth from '@/components/views/donate/DonateToGiveth'; import SaveGasFees from './SaveGasFees'; import SwitchToAcceptedChain from '@/components/views/donate/SwitchToAcceptedChain'; -import { useDonateData } from '@/context/donate.context'; +import { + DonateModalPriorityValues, + useDonateData, +} from '@/context/donate.context'; import { useModalCallback } from '@/hooks/useModalCallback'; import { getActiveRound } from '@/helpers/qf'; import { useGeneralWallet } from '@/providers/generalWalletProvider'; @@ -59,8 +62,6 @@ import { TokenIcon } from '../TokenIcon/TokenIcon'; import { SelectTokenModal } from './SelectTokenModal/SelectTokenModal'; import { Spinner } from '@/components/Spinner'; import { useSolanaBalance } from '@/hooks/useSolanaBalance'; -import { isWalletSanctioned } from '@/services/donation'; -import SanctionModal from '@/components/modals/SanctionedModal'; import { useTokenPrice } from '@/hooks/useTokenPrice'; import EligibilityBadges from '@/components/views/donate/common/EligibilityBadges'; import DonateAnonymously from '@/components/views/donate/common/DonateAnonymously'; @@ -79,7 +80,14 @@ const CryptoDonation: FC<{ const { formatMessage } = useIntl(); const { isSignedIn } = useAppSelector(state => state.user); - const { project, hasActiveQFRound, selectedOneTimeToken } = useDonateData(); + const { + project, + hasActiveQFRound, + selectedOneTimeToken, + shouldRenderModal, + setDonateModalByPriority, + setIsModalPriorityChecked, + } = useDonateData(); const dispatch = useAppDispatch(); const { @@ -100,7 +108,6 @@ const CryptoDonation: FC<{ const [showDonateModal, setShowDonateModal] = useState(false); const [showInsufficientModal, setShowInsufficientModal] = useState(false); const [showChangeNetworkModal, setShowChangeNetworkModal] = useState(false); - const [isSanctioned, setIsSanctioned] = useState(false); const [acceptedChains, setAcceptedChains] = useState( [], ); @@ -150,10 +157,6 @@ const CryptoDonation: FC<{ const tokenPrice = useTokenPrice(selectedOneTimeToken); - useEffect(() => { - validateSanctions(); - }, [project, address]); - useEffect(() => { if ( (networkId || @@ -314,17 +317,6 @@ const CryptoDonation: FC<{ } }, [selectedTokenBalance, amount, selectedOneTimeToken?.address, gasFee]); - const validateSanctions = async () => { - if (project?.organization?.label === 'endaoment' && address) { - // We just need to check if the wallet is sanctioned for endaoment projects - const sanctioned = await isWalletSanctioned(address); - if (sanctioned) { - setIsSanctioned(true); - return; - } - } - }; - const amountErrorText = useMemo(() => { const totalAmount = Number(formatUnits(gasFee, tokenDecimals)).toFixed( 10, @@ -339,6 +331,15 @@ const CryptoDonation: FC<{ ); }, [gasFee, tokenDecimals, selectedOneTimeToken?.symbol, formatMessage]); + useEffect(() => { + if (showChangeNetworkModal && acceptedChains) { + setDonateModalByPriority( + DonateModalPriorityValues.ShowNetworkModal, + ); + } + setIsModalPriorityChecked(DonateModalPriorityValues.ShowNetworkModal); + }, [showChangeNetworkModal, acceptedChains]); + // We need givethDonationAmount here because we need to calculate the donation share // for Giveth. If user want to donate minimal amount to projecct, the donation share for Giveth // has to be 0, disabled in UI and DonationModal @@ -368,20 +369,13 @@ const CryptoDonation: FC<{ return ( - {isSanctioned && ( - { - setIsSanctioned(false); - }} - /> - )} {showQFModal && ( )} - {!isSanctioned && showChangeNetworkModal && acceptedChains && ( + {shouldRenderModal(DonateModalPriorityValues.ShowNetworkModal) && ( >; + setDonateModalByPriority: ( + changeCurrentModal: DonateModalPriorityValues, + ) => void; setSelectedRecurringToken: Dispatch< SetStateAction >; + setIsModalPriorityChecked: (modal: DonateModalPriorityValues) => void; + shouldRenderModal: (modalRender: DonateModalPriorityValues) => boolean; fetchProject: () => Promise; draftDonationData?: IDraftDonation; fetchDraftDonation?: ( @@ -65,6 +71,13 @@ interface IProviderProps { project: IProject; } +export enum DonateModalPriorityValues { + None, + ShowNetworkModal, + DonationByProjectOwner, + OFACSanctionListModal, +} + const DonateContext = createContext({ setSuccessDonation: () => {}, setSelectedOneTimeToken: () => {}, @@ -72,6 +85,9 @@ const DonateContext = createContext({ project: {} as IProject, tokenStreams: {}, fetchProject: async () => {}, + setDonateModalByPriority: (changeModal: DonateModalPriorityValues) => {}, + shouldRenderModal: (modalRender: DonateModalPriorityValues) => false, + setIsModalPriorityChecked: (modal: DonateModalPriorityValues) => {}, draftDonationData: {} as IDraftDonation, fetchDraftDonation: async () => {}, qrDonationStatus: 'waiting', @@ -103,9 +119,17 @@ export const DonateProvider: FC = ({ children, project }) => { const [selectedRecurringToken, setSelectedRecurringToken] = useState< ISelectTokenWithBalance | undefined >(); + const isModalStatusChecked = useRef< + Map + >(new Map()); + const highestModalPriorityUnchecked = useRef< + DonateModalPriorityValues | 'All Checked' + >(DonateModalPriorityValues.None); const [successDonation, setSuccessDonation] = useState(); const [projectData, setProjectData] = useState(project); + const [currentDonateModal, setCurrentDonateModal] = + useState(DonateModalPriorityValues.None); const { chain } = useAccount(); @@ -114,6 +138,66 @@ export const DonateProvider: FC = ({ children, project }) => { setSelectedRecurringToken(undefined); }, [chain]); + const setIsModalPriorityChecked = useCallback( + (modalChecked: DonateModalPriorityValues): void => { + if ( + highestModalPriorityUnchecked.current != 'All Checked' && + (modalChecked <= highestModalPriorityUnchecked.current || + highestModalPriorityUnchecked.current === + DonateModalPriorityValues.None) + ) { + isModalStatusChecked.current.set(modalChecked, true); + let highestModalStatusUnchecked = + DonateModalPriorityValues.None; + let isAllChecked = true; + const modals: DonateModalPriorityValues[] = Object.values( + DonateModalPriorityValues, + ).filter( + modal => typeof modal !== 'string', + ) as DonateModalPriorityValues[]; + for (const modalStatus of modals) { + if (!isModalStatusChecked.current.get(modalStatus)) { + highestModalStatusUnchecked = modalStatus; + } + isAllChecked = + (isAllChecked && + !!isModalStatusChecked.current.get(modalStatus)) || + modalStatus === DonateModalPriorityValues.None; + } + highestModalPriorityUnchecked.current = isAllChecked + ? 'All Checked' + : highestModalStatusUnchecked; + } + }, + [], + ); + + const setDonateModalByPriority = useCallback( + (changeModal: DonateModalPriorityValues) => { + if (!isModalStatusChecked.current.get(changeModal)) { + setIsModalPriorityChecked(changeModal); + } + if (changeModal === DonateModalPriorityValues.None) { + setCurrentDonateModal(DonateModalPriorityValues.None); + } else if (changeModal > currentDonateModal) { + setCurrentDonateModal(changeModal); + } + }, + [currentDonateModal], + ); + + const shouldRenderModal = useCallback( + (modalRender: DonateModalPriorityValues) => { + return ( + (highestModalPriorityUnchecked.current == 'All Checked' || + currentDonateModal >= + highestModalPriorityUnchecked.current) && + currentDonateModal === modalRender + ); + }, + [currentDonateModal], + ); + const fetchProject = useCallback(async () => { const { data } = (await client.query({ query: FETCH_PROJECT_BY_SLUG_DONATION, @@ -154,8 +238,11 @@ export const DonateProvider: FC = ({ children, project }) => { selectedOneTimeToken, pendingDonationExists, selectedRecurringToken, + setDonateModalByPriority, setSelectedOneTimeToken, + shouldRenderModal, setSelectedRecurringToken, + setIsModalPriorityChecked, tokenStreams, fetchProject, draftDonationData: draftDonation as IDraftDonation, diff --git a/src/services/donation.ts b/src/services/donation.ts index b5ae210e9d..534ad18327 100644 --- a/src/services/donation.ts +++ b/src/services/donation.ts @@ -386,7 +386,7 @@ export async function isWalletSanctioned( // Check the response and determine if the address is sanctioned const result = data && data[0]; - return Boolean(result && result.isSanctioned); + return Boolean(result && result.isSanctioned) || true; } catch (error) { console.error('Error checking wallet sanction status:', error); return false;