diff --git a/packages/grant-explorer/src/attestationStore.ts b/packages/grant-explorer/src/attestationStore.ts new file mode 100644 index 000000000..4b2e9c3b3 --- /dev/null +++ b/packages/grant-explorer/src/attestationStore.ts @@ -0,0 +1,121 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { create } from "zustand"; +import { devtools } from "zustand/middleware"; +import { CartProject, AttestationFrameProps } from "./features/api/types"; +import { persist } from "zustand/middleware"; + +import { Hex } from "viem"; + +interface AttestationState { + checkedOutProjectsByTx: Record; + setCheckedOutProjectsByTx: (tx: Hex, projects: CartProject[]) => void; + getCheckedOutProjectsByTx: (tx: Hex) => CartProject[]; + cleanCheckedOutProjects: () => void; + getCheckedOutTransactions: () => Hex[]; + getFrameProps: (txHashes: Hex[]) => AttestationFrameProps; +} + +export const useAttestationStore = create()( + persist( + devtools((set, get) => ({ + checkedOutProjectsByTx: {}, + setCheckedOutProjectsByTx: (tx: Hex, projects: CartProject[]) => { + set((oldState) => ({ + checkedOutProjectsByTx: { + ...oldState.checkedOutProjectsByTx, + [tx]: projects, + }, + })); + }, + getCheckedOutProjectsByTx: (tx: Hex) => { + return get().checkedOutProjectsByTx[tx] || []; + }, + cleanCheckedOutProjects: () => { + set({ + checkedOutProjectsByTx: {}, + }); + }, + // Create a function that gets an array of transactionHashes and returns the FrameProps object where projects Array + // contains the top 3 projects based on those checked out transactions max donation amount in usd + // The top round is the round with the most funds allocated in total amount of projects allocated to all transactions in total rounds in all transaction in total chains allocated in these transactions + getCheckedOutTransactions: () => { + return Object.keys(get().checkedOutProjectsByTx) as Hex[]; + }, + getFrameProps: (txHashes: Hex[]) => { + const allProjects: CartProject[] = []; + const roundsSet = new Set(); + const chainsSet = new Set(); + const amountByRound: Record< + string, + { + roundId: string; + chainId: number; + totalAmount: number; + } + > = {}; + + if (txHashes.length === 0) { + return { + selectedBackground: "", + topRound: { + roundId: "", + chainId: 0, + }, + projectsFunded: 0, + roundsSupported: 0, + checkedOutChains: 0, + projects: [], + } as AttestationFrameProps; + } + + for (const txHash of txHashes) { + const projects = get().getCheckedOutProjectsByTx(txHash); + allProjects.push(...projects); + projects.forEach((project) => { + roundsSet.add(project.roundId); + chainsSet.add(project.chainId); + amountByRound[project.roundId] = amountByRound[project.roundId] || { + roundName: project.roundId, + totalAmount: 0, + }; + // TODO CHANGE WITH ACTUAL ROUNDNAME + amountByRound[project.roundId].roundId = project.roundId; + amountByRound[project.roundId].chainId = project.chainId; + amountByRound[project.roundId].totalAmount += Number( + project.amount + ); + }); + } + const topProjects = allProjects + .sort((a, b) => Number(b.amount) - Number(a.amount)) + .slice(0, 3) + .map((project, i) => ({ + rank: i + 1, + name: project.projectMetadata.title, + round: project.roundId, + roundId: project.roundId, + image: + project.projectMetadata?.logoImg ?? + project.projectMetadata?.bannerImg ?? + "", + chainId: project.chainId, + })); + const topRound = Object.values(amountByRound).sort( + (a, b) => b.totalAmount - a.totalAmount + )[0]; + return { + selectedBackground: "", + topRound: topRound, + projectsFunded: allProjects.length, + roundsSupported: roundsSet.size, + checkedOutChains: chainsSet.size, + projects: topProjects, + } as AttestationFrameProps; + }, + })), + { + name: "attestation-store", + version: 1, + } + ) +); diff --git a/packages/grant-explorer/src/checkoutStore.ts b/packages/grant-explorer/src/checkoutStore.ts index 547176a45..4b2ceee6f 100644 --- a/packages/grant-explorer/src/checkoutStore.ts +++ b/packages/grant-explorer/src/checkoutStore.ts @@ -1,11 +1,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { create } from "zustand"; import { devtools } from "zustand/middleware"; -import { - CartProject, - ProgressStatus, - AttestationFrameProps, -} from "./features/api/types"; +import { CartProject, ProgressStatus } from "./features/api/types"; import { AlloV2, createEthersTransactionSender, @@ -14,6 +10,7 @@ import { getChainById, } from "common"; import { useCartStorage } from "./store"; +import { useAttestationStore } from "./attestationStore"; import { getContract, Hex, @@ -70,12 +67,6 @@ interface CheckoutState { getCheckedOutProjects: () => CartProject[]; checkedOutProjects: CartProject[]; setCheckedOutProjects: (newArray: CartProject[]) => void; - checkedOutProjectsByTx: Record; - setCheckedOutProjectsByTx: (tx: Hex, projects: CartProject[]) => void; - getCheckedOutProjectsByTx: (tx: Hex) => CartProject[]; - cleanCheckedOutProjects: () => void; - getCheckedOutTransactions: () => Hex[]; - getFrameProps: (txHashes: Hex[]) => AttestationFrameProps; } const defaultProgressStatusForAllChains = Object.fromEntries( @@ -332,11 +323,9 @@ export const useCheckoutStore = create()( set({ checkedOutProjects: [...get().checkedOutProjects, ...donations], }); - set({ - checkedOutProjectsByTx: { - [receipt.transactionHash]: donations, - }, - }); + useAttestationStore + .getState() + .setCheckedOutProjectsByTx(receipt.transactionHash, donations); } catch (error) { let context: Record = { chainId, @@ -372,91 +361,6 @@ export const useCheckoutStore = create()( checkedOutProjects: newArray, }); }, - checkedOutProjectsByTx: {}, - setCheckedOutProjectsByTx: (tx: Hex, projects: CartProject[]) => { - set((oldState) => ({ - checkedOutProjectsByTx: { - ...oldState.checkedOutProjectsByTx, - [tx]: projects, - }, - })); - }, - getCheckedOutProjectsByTx: (tx: Hex) => { - return get().checkedOutProjectsByTx[tx] || []; - }, - cleanCheckedOutProjects: () => { - set({ - checkedOutProjectsByTx: {}, - }); - }, - // Create a function that gets an array of transactionHashes and returns the FrameProps object where projects Array - // contains the top 3 projects based on those checked out transactions max donation amount in usd - // The top round is the round with the most funds allocated in total amount of projects allocated to all transactions in total rounds in all transaction in total chains allocated in these transactions - getCheckedOutTransactions: () => { - return Object.keys(get().checkedOutProjectsByTx) as Hex[]; - }, - getFrameProps: (txHashes: Hex[]) => { - const allProjects: CartProject[] = []; - const roundsSet = new Set(); - const chainsSet = new Set(); - const amountByRound: Record< - string, - { - roundName: string; - totalAmount: number; - } - > = {}; - - if (txHashes.length === 0) { - return { - selectedBackground: "", - topRound: "", - projectsFunded: 0, - roundsSupported: 0, - checkedOutChains: 0, - projects: [], - } as AttestationFrameProps; - } - - for (const txHash of txHashes) { - const projects = get().getCheckedOutProjectsByTx(txHash); - allProjects.push(...projects); - projects.forEach((project) => { - roundsSet.add(project.roundId); - chainsSet.add(project.chainId); - amountByRound[project.roundId] = amountByRound[project.roundId] || { - roundName: project.roundId, - totalAmount: 0, - }; - // TODO CHANGE WITH ACTUAL ROUNDNAME - amountByRound[project.roundId].roundName = project.roundId; - amountByRound[project.roundId].totalAmount += Number(project.amount); - }); - } - const topProjects = allProjects - .sort((a, b) => Number(b.amount) - Number(a.amount)) - .slice(0, 3) - .map((project, i) => ({ - rank: i, - name: project.projectMetadata.title, - round: project.roundId, - image: - project.projectMetadata?.logoImg ?? - project.projectMetadata?.bannerImg ?? - "", - })); - const topRoundName = Object.values(amountByRound).sort( - (a, b) => b.totalAmount - a.totalAmount - )[0].roundName; - return { - selectedBackground: "", - topRound: topRoundName, - projectsFunded: allProjects.length, - roundsSupported: roundsSet.size, - checkedOutChains: chainsSet.size, - projects: topProjects, - } as AttestationFrameProps; - }, })) ); diff --git a/packages/grant-explorer/src/context/RoundContext.tsx b/packages/grant-explorer/src/context/RoundContext.tsx index c32f829a5..090d931bb 100644 --- a/packages/grant-explorer/src/context/RoundContext.tsx +++ b/packages/grant-explorer/src/context/RoundContext.tsx @@ -158,3 +158,43 @@ export const useRoundById = ( getRoundByIdError: context.state.getRoundByIdError, }; }; + +import { useQuery } from "@tanstack/react-query"; + +export const useRoundNamesByIds = (rounds: Record) => { + const dataLayer = useDataLayer(); + + return useQuery({ + queryKey: ["roundNamesByIds", rounds], // Query key + enabled: !!rounds && Object.keys(rounds).length > 0, // Only fetch if there are rounds + queryFn: async () => { + if (!rounds) { + return {}; + } + // Create an empty object to store chainId -> roundId -> roundName + const roundNames: Record> = {}; + + await Promise.all( + Object.entries(rounds).map(async ([chainId, roundId]) => { + // If the name is missing, fetch the round data + const fetchedRound = ( + await dataLayer.getRoundForExplorer({ + roundId, + chainId: Number(chainId), + }) + )?.round; + + // Store the fetched round name in the result object + if (fetchedRound?.roundMetadata?.name) { + if (!roundNames[Number(chainId)]) { + roundNames[Number(chainId)] = {}; + } + roundNames[Number(chainId)][roundId] = + fetchedRound.roundMetadata.name; + } + }) + ); + return roundNames; + }, + }); +}; diff --git a/packages/grant-explorer/src/features/api/types.ts b/packages/grant-explorer/src/features/api/types.ts index 9190098bb..75c4b02a1 100644 --- a/packages/grant-explorer/src/features/api/types.ts +++ b/packages/grant-explorer/src/features/api/types.ts @@ -85,10 +85,16 @@ export type AttestationProject = { round: string; image: string; amount?: number; + chainId?: number; + roundId?: string; }; export type AttestationFrameProps = { - topRound: string; + topRound?: { + roundId: string; + chainId: number; + }; + topRoundName?: string; projectsFunded: number; roundsSupported: number; checkedOutChains: number; diff --git a/packages/grant-explorer/src/features/attestations/MintYourImpactComponents.tsx b/packages/grant-explorer/src/features/attestations/MintYourImpactComponents.tsx index 6aeafa2dd..9e72fa6d6 100755 --- a/packages/grant-explorer/src/features/attestations/MintYourImpactComponents.tsx +++ b/packages/grant-explorer/src/features/attestations/MintYourImpactComponents.tsx @@ -364,7 +364,7 @@ export const HiddenAttestationFrame = ({ checkedOutChains={FrameProps.checkedOutChains} projectsFunded={FrameProps.projectsFunded} roundsSupported={FrameProps.roundsSupported} - topRound={FrameProps.topRound} + topRound={FrameProps.topRoundName ?? ""} address={address} ensName={name} /> @@ -376,8 +376,12 @@ import { ImageWithLoading } from "../common/components/ImageWithLoading"; export const ImpactMintingSuccess = ({ impactImageCid, + containerSize = "w-[430px] h-[430px]", + imageSize = "w-[400px] h-[400px]", }: { impactImageCid?: string; + containerSize?: string; + imageSize?: string; }) => { const { data: image, @@ -390,11 +394,14 @@ export const ImpactMintingSuccess = ({ className="flex flex-col items-center text-center w-full relative bg-bottom bg-cover " style={{ backgroundImage: `url(${bgImage})` }} > -
+
diff --git a/packages/grant-explorer/src/features/attestations/utils/getRoundsToFetchNames.ts b/packages/grant-explorer/src/features/attestations/utils/getRoundsToFetchNames.ts new file mode 100644 index 000000000..fdf8b74c6 --- /dev/null +++ b/packages/grant-explorer/src/features/attestations/utils/getRoundsToFetchNames.ts @@ -0,0 +1,15 @@ +import { AttestationFrameProps } from "../../api/types"; + +export const getRoundsToFetchNames = (props: AttestationFrameProps) => { + if (props.projects.length === 0) { + return {}; + } + const roundsToFetchNames: Record = {}; + props.projects.forEach((project) => { + roundsToFetchNames[project?.chainId ?? 0] = project.roundId ?? ""; + }); + roundsToFetchNames[props.topRound?.chainId ?? 0] = + props.topRound?.roundId ?? ""; + + return roundsToFetchNames; +}; diff --git a/packages/grant-explorer/src/features/contributors/utils/getContributionFrameProps.ts b/packages/grant-explorer/src/features/contributors/utils/getContributionFrameProps.ts index 111fc663a..3f0dd8a08 100644 --- a/packages/grant-explorer/src/features/contributors/utils/getContributionFrameProps.ts +++ b/packages/grant-explorer/src/features/contributors/utils/getContributionFrameProps.ts @@ -56,10 +56,24 @@ export const getContributionFrameProps = ( )[0]?.roundName || ""; return { - topRound, + topRoundName: topRound, projectsFunded: allProjects.length, roundsSupported: roundsSet.size, checkedOutChains: chainsSet.size, projects: topProjects, }; }; + +export const getRoundsToFetchNames = (props: AttestationFrameProps) => { + if (props.projects.length === 0) { + return {}; + } + const roundsToFetchNames: Record = {}; + props.projects.forEach((project) => { + roundsToFetchNames[project?.chainId ?? 0] = project.roundId ?? ""; + }); + roundsToFetchNames[props.topRound?.chainId ?? 0] = + props.topRound?.roundId ?? ""; + + return roundsToFetchNames; +}; diff --git a/packages/grant-explorer/src/features/round/ThankYou.tsx b/packages/grant-explorer/src/features/round/ThankYou.tsx index e7e95a237..fadda55b7 100755 --- a/packages/grant-explorer/src/features/round/ThankYou.tsx +++ b/packages/grant-explorer/src/features/round/ThankYou.tsx @@ -6,16 +6,17 @@ import Navbar from "../common/Navbar"; import { useCartStorage } from "../../store"; import { useCheckoutStore } from "../../checkoutStore"; import { ProgressStatus } from "../api/types"; -import { useRoundById } from "../../context/RoundContext"; +import { useRoundById, useRoundNamesByIds } from "../../context/RoundContext"; import image from "../../assets/gitcoinlogo-black.svg"; import alt1 from "../../assets/alt1.svg"; import { useWindowSize } from "react-use"; -import { ShareButtons, ThankYouSectionButtons } from "../common/ShareButtons"; +import { ThankYouSectionButtons } from "../common/ShareButtons"; import { - AttestationFrame, HiddenAttestationFrame, + ImpactMintingSuccess, PreviewFrame, } from "../attestations/MintYourImpactComponents"; +import { getRoundsToFetchNames } from "../attestations/utils/getRoundsToFetchNames"; import MintAttestationProgressModal from "../attestations/MintAttestationProgressModal"; // Adjust the import path as needed import { MintProgressModalBodyThankYou } from "../attestations/MintProgressModalBody"; // We'll define this next import { useGetAttestationData } from "../../hooks/attestations/useGetAttestationData"; @@ -30,6 +31,7 @@ import { AttestationFee, } from "../attestations/utils/constants"; import { ethers } from "ethers"; +import { useAttestationStore } from "../../attestationStore"; export default function ThankYou() { datadogLogs.logger.info( @@ -39,6 +41,7 @@ export default function ThankYou() { const cart = useCartStorage(); const checkoutStore = useCheckoutStore(); + const attestationStore = useAttestationStore(); /** Fetch round data for tweet */ const checkedOutProjects = useCheckoutStore((state) => @@ -100,48 +103,18 @@ export default function ThankYou() { window.scrollTo(0, 100); }, []); - // const transactions = useCheckoutStore((state) => - // state.getCheckedOutTransactions() - // ); - - // const ImpactFrameProps = useCheckoutStore((state) => { - // return state.getFrameProps(transactions); - // }); - - // For testing out otherwise comment till transaction and uncomment the above { transactions } and { ImpactFrameProps } - const projectsData = [ - { - rank: 1, - name: "Saving forests around the world", - round: "Climate Round", - image: image, - }, - { - rank: 2, - name: "Funding schools in Mexico", - round: "Education Round", - image: image, - }, - { - rank: 3, - name: "Accessible software for everyone", - round: "OSS Round", - image: image, - }, - ]; - - const ImpactFrameProps = { - topRound: round?.roundMetadata?.name ?? "", - projectsFunded: 20, - roundsSupported: 5, - checkedOutChains: 6, - projects: projectsData, - }; + const transactions = useAttestationStore((state) => + state.getCheckedOutTransactions() + ); + + const ImpactFrameProps = useAttestationStore((state) => { + return state.getFrameProps(transactions); + }); + + const roundsToFetchName = getRoundsToFetchNames(ImpactFrameProps); - // For testing add one or multiple test transaction hash/es - const transactions = [ - "0x9c058fb124c2899602f923afe8e4f61a86a4901435460272bf16939cfb719f3d", - ]; + const { data: roundNames, isLoading: isLoadingRoundNames } = + useRoundNamesByIds(roundsToFetchName); const [minted, setMinted] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); @@ -158,7 +131,7 @@ export default function ThankYou() { const handleSetMinted = () => { setMinted(true); setIsModalOpen(false); - checkoutStore.cleanCheckedOutProjects(); + attestationStore.cleanCheckedOutProjects(); }; const { width } = useWindowSize(); @@ -173,20 +146,21 @@ export default function ThankYou() { isModalOpen ); - // const { data, isLoading, isRefetching } = useGetAttestationData( - // transactions, - // handleGetAttestationPreview, - // isLoadingENS || isLoadingImages || !isModalOpen, - // selectedBackground - // ); - - // For testing out otherwise comment and uncomment the above const { data, isLoading } = useGetAttestationData( transactions, handleGetAttestationPreview, - isLoadingENS, + isLoadingENS || isLoadingImages || !isModalOpen || isLoadingRoundNames, selectedBackground ); + + const [impactImageCid, setImpactImageCid] = useState(); + + useEffect(() => { + if (data?.impactImageCid) { + setImpactImageCid(data.impactImageCid); + } + }, [data]); + const frameId = ethers.utils.solidityKeccak256(["string[]"], [transactions]); const { @@ -211,6 +185,24 @@ export default function ThankYou() { ? balance.value <= AttestationFee + gasEstimation : false; + const topRoundName = roundNames + ? roundNames[ImpactFrameProps?.topRound?.chainId ?? 0][ + ImpactFrameProps?.topRound?.roundId ?? "" + ] + : ""; + + const ImpactFramePropsWithNames = { + ...ImpactFrameProps, + topRoundName, + projects: ImpactFrameProps.projects.map((project) => ({ + ...project, + round: roundNames + ? (roundNames[project?.chainId ?? 0][project?.roundId ?? ""] ?? + project.round) + : project.round, + })), + }; + return ( <> @@ -270,21 +262,14 @@ export default function ThankYou() { Share with your friends!
- - ) : (
@@ -321,7 +306,7 @@ export default function ThankYou() { } />