From 28162452d9ff7268fb2657a3cc98e488095212d5 Mon Sep 17 00:00:00 2001 From: Aditya Anand M C Date: Tue, 28 Jan 2025 15:21:09 +0530 Subject: [PATCH] add checker --- apps/admin/package.json | 3 +- apps/admin/src/components/Message.tsx | 25 ++ .../checker/usePerformEvaluation/index.ts | 2 + .../usePerformEvaluation.ts | 65 +++ .../checker/usePerformEvaluation/utils.ts | 65 +++ .../checker/usePerformOnChainReview/index.ts | 2 + .../usePerformOnChainReview.ts | 160 ++++++++ .../checker/usePerformOnChainReview/utils.ts | 374 ++++++++++++++++++ apps/admin/src/pages/Pool/PoolTabs.tsx | 89 +++-- .../src/pages/Pool/tabs/TabApplication.tsx | 54 ++- pnpm-lock.yaml | 108 ++++- 11 files changed, 895 insertions(+), 52 deletions(-) create mode 100644 apps/admin/src/components/Message.tsx create mode 100644 apps/admin/src/hooks/checker/usePerformEvaluation/index.ts create mode 100644 apps/admin/src/hooks/checker/usePerformEvaluation/usePerformEvaluation.ts create mode 100644 apps/admin/src/hooks/checker/usePerformEvaluation/utils.ts create mode 100644 apps/admin/src/hooks/checker/usePerformOnChainReview/index.ts create mode 100644 apps/admin/src/hooks/checker/usePerformOnChainReview/usePerformOnChainReview.ts create mode 100644 apps/admin/src/hooks/checker/usePerformOnChainReview/utils.ts diff --git a/apps/admin/package.json b/apps/admin/package.json index 795f6f8..2d1cb4e 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -20,8 +20,9 @@ "blo": "~1.2.0", "dotenv": "^16.4.5", "events": "^3.3.0", - "gitcoin-ui": "^3.7.0", + "gitcoin-ui": "^3.8.2", "graphql-request": "^5.2.0", + "json-stringify-deterministic": "^1.0.12", "pino-pretty": "^13.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/apps/admin/src/components/Message.tsx b/apps/admin/src/components/Message.tsx new file mode 100644 index 0000000..b3274a3 --- /dev/null +++ b/apps/admin/src/components/Message.tsx @@ -0,0 +1,25 @@ +import { Button } from "gitcoin-ui"; + +interface MessageProps { + title: string; + message: string; + action?: { + label: string; + onClick: () => void; + }; + className?: string; +} + +export function MessagePage({ title, message, action, className = "" }: MessageProps) { + return ( +
+
+

{title}

+ +

{message}

+ + {action &&
+
+ ); +} diff --git a/apps/admin/src/hooks/checker/usePerformEvaluation/index.ts b/apps/admin/src/hooks/checker/usePerformEvaluation/index.ts new file mode 100644 index 0000000..e2564f6 --- /dev/null +++ b/apps/admin/src/hooks/checker/usePerformEvaluation/index.ts @@ -0,0 +1,2 @@ +export * from "./usePerformEvaluation"; +export * from "./utils"; diff --git a/apps/admin/src/hooks/checker/usePerformEvaluation/usePerformEvaluation.ts b/apps/admin/src/hooks/checker/usePerformEvaluation/usePerformEvaluation.ts new file mode 100644 index 0000000..35a8e47 --- /dev/null +++ b/apps/admin/src/hooks/checker/usePerformEvaluation/usePerformEvaluation.ts @@ -0,0 +1,65 @@ +import { useState, useEffect } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { EvaluationBody } from "gitcoin-ui/checker"; +import { Hex } from "viem"; +import { useAccount, useWalletClient } from "wagmi"; +import { deterministicKeccakHash, submitEvaluation } from "./utils"; + +export const usePerformEvaluation = () => { + const [evaluationBody, setEvaluationBody] = useState(null); + const { address } = useAccount(); + const { data: walletClient } = useWalletClient(); + + const handleSetEvaluationBody = (data: EvaluationBody) => { + setEvaluationBody(data); + }; + + const signEvaluationBody = async (): Promise => { + if (!walletClient) { + throw new Error("No wallet client found"); + } + + if (!evaluationBody) { + throw new Error("No evaluation body found"); + } + + const hash = await deterministicKeccakHash({ + chainId: evaluationBody.chainId, + alloPoolId: evaluationBody.alloPoolId, + alloApplicationId: evaluationBody.alloApplicationId, + cid: evaluationBody.cid, + evaluator: address, + summaryInput: evaluationBody.summaryInput, + evaluationStatus: evaluationBody.evaluationStatus, + }); + + const signature = await walletClient.signMessage({ message: hash }); + + return signature; + }; + + // Evaluation mutation + const evaluationMutation = useMutation({ + mutationFn: async (data: EvaluationBody) => { + if (!address) { + throw new Error("No address found"); + } + const signature = await signEvaluationBody(); + await submitEvaluation({ ...data, signature, evaluator: address }); + }, + }); + + // Trigger the signing mutation when evaluationBody is set + useEffect(() => { + if (evaluationBody) { + evaluationMutation.mutateAsync(evaluationBody); + } + }, [evaluationBody]); + + return { + setEvaluationBody: handleSetEvaluationBody, + isEvaluating: evaluationMutation.isPending, + isError: evaluationMutation.isError, + isSuccess: evaluationMutation.isSuccess, + }; +}; diff --git a/apps/admin/src/hooks/checker/usePerformEvaluation/utils.ts b/apps/admin/src/hooks/checker/usePerformEvaluation/utils.ts new file mode 100644 index 0000000..955e82e --- /dev/null +++ b/apps/admin/src/hooks/checker/usePerformEvaluation/utils.ts @@ -0,0 +1,65 @@ +// CHECKER +import { EvaluationBody, SyncPoolBody } from "gitcoin-ui/checker"; +import stringify from "json-stringify-deterministic"; +import { type Hex, keccak256, toHex } from "viem"; + +export const CHECKER_ENDPOINT = "https://api.checker.gitcoin.co"; + +export async function submitEvaluation( + evaluationBody: EvaluationBody, +): Promise<{ evaluationId: string }> { + const url = `${CHECKER_ENDPOINT}/api/evaluate`; + + try { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ ...evaluationBody, evaluatorType: "human" }), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(`Error: ${response.status} - ${errorData.message || "Unknown error"}`); + } + + const data = await response.json(); + return data.evaluationId; + } catch (error) { + console.error("Error submitting evaluation:", error); + throw error; + } +} + +export async function syncPool(syncPoolBody: SyncPoolBody): Promise { + const url = `${CHECKER_ENDPOINT}/api/pools`; + + try { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + ...syncPoolBody, + skipEvaluation: false, + }), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(`Error: ${response.status} - ${errorData.message || "Unknown error"}`); + } + + return true; + } catch (error) { + console.error("Error syncing pool:", error); + throw error; + } +} + +export async function deterministicKeccakHash(obj: T): Promise { + const deterministicString = stringify(obj); + return keccak256(toHex(deterministicString)); +} diff --git a/apps/admin/src/hooks/checker/usePerformOnChainReview/index.ts b/apps/admin/src/hooks/checker/usePerformOnChainReview/index.ts new file mode 100644 index 0000000..28c6b21 --- /dev/null +++ b/apps/admin/src/hooks/checker/usePerformOnChainReview/index.ts @@ -0,0 +1,2 @@ +export * from "./usePerformOnChainReview"; +export * from "./utils"; diff --git a/apps/admin/src/hooks/checker/usePerformOnChainReview/usePerformOnChainReview.ts b/apps/admin/src/hooks/checker/usePerformOnChainReview/usePerformOnChainReview.ts new file mode 100644 index 0000000..dd826f2 --- /dev/null +++ b/apps/admin/src/hooks/checker/usePerformOnChainReview/usePerformOnChainReview.ts @@ -0,0 +1,160 @@ +import { useState, useEffect, useMemo } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { ReviewBody } from "gitcoin-ui/checker"; +import { ProgressStatus } from "gitcoin-ui/types"; +import { Abi, createPublicClient, encodeFunctionData, http } from "viem"; +import { useWalletClient } from "wagmi"; +import { + waitUntilIndexerSynced, + applicationStatusToNumber, + buildUpdatedRowsOfApplicationStatuses, + getStrategyInstance, + getOnchainEvaluationProgressSteps, +} from "."; + +export const usePerformOnChainReview = () => { + const [reviewBody, setReviewBody] = useState(null); + const [contractUpdatingStatus, setContractUpdatingStatus] = useState( + ProgressStatus.NOT_STARTED, + ); + const [indexingStatus, setIndexingStatus] = useState(ProgressStatus.NOT_STARTED); + const [finishingStatus, setFinishingStatus] = useState( + ProgressStatus.NOT_STARTED, + ); + + const { data: walletClient } = useWalletClient(); + + const handleSetReviewBody = (reviewBody: ReviewBody | null) => { + setReviewBody(reviewBody); + }; + + const evaluationMutation = useMutation({ + mutationFn: async (data: ReviewBody) => { + if (!walletClient) { + throw new Error("WalletClient is undefined"); + } + + try { + // Reset statuses before starting + setContractUpdatingStatus(ProgressStatus.IN_PROGRESS); + + // Prepare the strategy instance based on the strategy type. + const { strategyInstance, strategyInstanceAbi } = getStrategyInstance( + data.strategyAddress, + walletClient.chain.id, + data.roundId, + data.strategy, + ); + + // Try to get total applications + let totalApplications = BigInt(0); + try { + totalApplications = await strategyInstance.recipientsCounter(); + } catch (error) { + totalApplications = BigInt(data.currentApplications.length + 1); + } + + // Build updated rows of application statuses + const rows = buildUpdatedRowsOfApplicationStatuses({ + applicationsToUpdate: data.applicationsToUpdate, + currentApplications: data.currentApplications, + statusToNumber: applicationStatusToNumber, + bitsPerStatus: 4, + }); + + // Send transaction + const account = walletClient.account; + if (!account) { + throw new Error("WalletClient account is undefined"); + } + + const publicClient = createPublicClient({ + chain: walletClient.chain, + transport: http(), + }); + + let txHash; + let receipt; + try { + txHash = await walletClient.sendTransaction({ + account: account, + to: data.strategyAddress, + data: encodeFunctionData({ + abi: strategyInstanceAbi as Abi, + functionName: "reviewRecipients", + args: [rows, totalApplications], + }), + }); + + receipt = await publicClient.waitForTransactionReceipt({ + hash: txHash, + confirmations: 1, + }); + } catch (sendError) { + setContractUpdatingStatus(ProgressStatus.IS_ERROR); + throw sendError; + } + + if (!receipt.status) { + setContractUpdatingStatus(ProgressStatus.IS_ERROR); + throw new Error("Failed to update application status"); + } + + // Transaction sent successfully + setContractUpdatingStatus(ProgressStatus.IS_SUCCESS); + setIndexingStatus(ProgressStatus.IN_PROGRESS); + + // Wait until indexer is synced + try { + await waitUntilIndexerSynced({ + chainId: walletClient.chain.id, + blockNumber: receipt.blockNumber, + }); + } catch (e) { + setIndexingStatus(ProgressStatus.IS_ERROR); + } + + setIndexingStatus(ProgressStatus.IS_SUCCESS); + + // Finishing up + setFinishingStatus(ProgressStatus.IN_PROGRESS); + // Any finishing steps can be added here + setFinishingStatus(ProgressStatus.IS_SUCCESS); + } catch (error) { + throw error; + } + }, + }); + + useEffect(() => { + if (reviewBody) { + evaluationMutation.mutate(reviewBody); + } + }, [reviewBody]); + + useEffect(() => { + if (evaluationMutation.isSuccess) { + setContractUpdatingStatus(ProgressStatus.NOT_STARTED); + setIndexingStatus(ProgressStatus.NOT_STARTED); + setFinishingStatus(ProgressStatus.NOT_STARTED); + evaluationMutation.reset(); + } + }, [evaluationMutation]); + + const steps = useMemo(() => { + return getOnchainEvaluationProgressSteps({ + contractUpdatingStatus, + indexingStatus, + finishingStatus, + }); + }, [contractUpdatingStatus, indexingStatus, finishingStatus]); + + return { + setReviewBody: handleSetReviewBody, + steps, + isReviewing: evaluationMutation.isPending, + isError: evaluationMutation.isError, + isSuccess: evaluationMutation.isSuccess, + error: evaluationMutation.error, + }; +}; diff --git a/apps/admin/src/hooks/checker/usePerformOnChainReview/utils.ts b/apps/admin/src/hooks/checker/usePerformOnChainReview/utils.ts new file mode 100644 index 0000000..25296ab --- /dev/null +++ b/apps/admin/src/hooks/checker/usePerformOnChainReview/utils.ts @@ -0,0 +1,374 @@ +import { + DirectGrantsLiteStrategy, + DirectGrantsLiteStrategyAbi, + DonationVotingMerkleDistributionDirectTransferStrategyAbi, + DonationVotingMerkleDistributionStrategy, + EasyRetroFundingStrategy, + EasyRetroFundingStrategyAbi, +} from "@allo-team/allo-v2-sdk"; +import { EventEmitter } from "events"; +import { ProgressStatus, Step } from "gitcoin-ui"; +import { ApplicationStatus, ApplicationStatusType, PoolCategory } from "gitcoin-ui/checker"; +import { Abi, Address, createPublicClient, encodeFunctionData, http, WalletClient } from "viem"; + +export const applicationStatusToNumber = (status: ApplicationStatusType): bigint => { + switch (status) { + case ApplicationStatus.PENDING: + return 1n; + case ApplicationStatus.APPROVED: + return 2n; + case ApplicationStatus.REJECTED: + return 3n; + case ApplicationStatus.APPEAL: + return 4n; + case ApplicationStatus.IN_REVIEW: + return 5n; + case ApplicationStatus.CANCELLED: + return 6n; + default: + throw new Error(`Unknown status ${status}`); + } +}; + +class ReviewRecipients extends EventEmitter { + async execute( + args: { + roundId: string; + strategyAddress: Address; + applicationsToUpdate: { index: number; status: ApplicationStatusType }[]; + currentApplications: { index: number; status: ApplicationStatusType }[]; + strategy?: PoolCategory; + }, + chainId: number, + walletClient: WalletClient, + ): Promise<{ status: "success" } | { status: "error"; error: Error }> { + let strategyInstance; + let strategyInstanceAbi; + + switch (args.strategy) { + case PoolCategory.QuadraticFunding: { + strategyInstance = new DonationVotingMerkleDistributionStrategy({ + chain: chainId, + poolId: BigInt(args.roundId), + address: args.strategyAddress, + }); + strategyInstanceAbi = DonationVotingMerkleDistributionDirectTransferStrategyAbi; + break; + } + case PoolCategory.Direct: { + strategyInstance = new DirectGrantsLiteStrategy({ + chain: chainId, + poolId: BigInt(args.roundId), + address: args.strategyAddress, + }); + strategyInstanceAbi = DirectGrantsLiteStrategyAbi; + break; + } + case PoolCategory.Retrofunding: { + strategyInstance = new EasyRetroFundingStrategy({ + chain: chainId, + poolId: BigInt(args.roundId), + address: args.strategyAddress, + }); + strategyInstanceAbi = EasyRetroFundingStrategyAbi; + break; + } + default: + throw new Error("Invalid strategy"); + } + + let totalApplications = 0n; + try { + totalApplications = await strategyInstance.recipientsCounter(); + } catch (error) { + totalApplications = BigInt(args.currentApplications.length + 1); + } + + const rows = buildUpdatedRowsOfApplicationStatuses({ + applicationsToUpdate: args.applicationsToUpdate, + currentApplications: args.currentApplications, + statusToNumber: applicationStatusToNumber, + bitsPerStatus: 4, + }); + + try { + const account = walletClient.account; + if (!account) { + throw new Error("WalletClient account is undefined"); + } + + const txHash = await walletClient.sendTransaction({ + account: account, + to: args.strategyAddress, + data: encodeFunctionData({ + abi: strategyInstanceAbi as Abi, + functionName: "reviewRecipients", + args: [rows, totalApplications], + }), + chain: null, + }); + + this.emit("transaction", { type: "sent", txHash }); + + const publicClient = createPublicClient({ + chain: walletClient.chain, + transport: http(), + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); + + if (receipt.status !== "success") { + const errorResult = new Error("Failed to update application status"); + this.emit("transactionStatus", { status: "error", error: errorResult }); + return { status: "error", error: errorResult }; + } + + this.emit("transactionStatus", { status: "success", receipt }); + + await waitUntilIndexerSynced({ + chainId: chainId, + blockNumber: receipt.blockNumber, + }); + + this.emit("indexingStatus", { status: "success" }); + + return { status: "success" }; + } catch (error) { + this.emit("transaction", { type: "error", error }); + return { status: "error", error: error as Error }; + } + } +} + +export const waitUntilIndexerSynced = async ({ + chainId, + blockNumber, +}: { + chainId: number; + blockNumber: bigint; +}) => { + const endpoint = "https://grants-stack-indexer-v2.gitcoin.co/graphql"; // todo: add endpoint + const pollIntervalInMs = 1000; + + async function pollIndexer() { + const response = await fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: ` + query getBlockNumberQuery($chainId: Int!) { + subscriptions( + filter: { chainId: { equalTo: $chainId }, toBlock: { equalTo: "latest" } } + ) { + chainId + indexedToBlock + } + } + `, + variables: { + chainId, + }, + }), + }); + + if (response.status === 200) { + const { + data, + }: { + data: { + subscriptions: { chainId: number; indexedToBlock: string }[]; + }; + } = await response.json(); + + const subscriptions = data?.subscriptions || []; + + if (subscriptions.length > 0) { + const currentBlockNumber = BigInt( + subscriptions.reduce( + (minBlock, sub) => + BigInt(sub.indexedToBlock) < BigInt(minBlock) ? sub.indexedToBlock : minBlock, + subscriptions[0].indexedToBlock, + ), + ); + + if (currentBlockNumber >= blockNumber) { + return true; + } + } + } + + return false; + } + + while (!(await pollIndexer())) { + await new Promise((resolve) => setTimeout(resolve, pollIntervalInMs)); + } +}; + +// =========== Do not touch this code =========== + +export const buildUpdatedRowsOfApplicationStatuses = (args: { + applicationsToUpdate: { index: number; status: ApplicationStatusType }[]; + currentApplications: { index: number; status: ApplicationStatusType }[]; + statusToNumber: (status: ApplicationStatusType) => bigint; + bitsPerStatus: number; +}): { index: bigint; statusRow: bigint }[] => { + if (args.bitsPerStatus > 1 && args.bitsPerStatus % 2 !== 0) { + throw new Error("bitsPerStatus must be a multiple of 2"); + } + + const applicationsPerRow = 256 / args.bitsPerStatus; + + const rowsToUpdate = Array.from( + new Set( + args.applicationsToUpdate.map(({ index }) => { + return Math.floor(index / applicationsPerRow); + }), + ), + ); + + const updatedApplications = args.currentApplications.map((app) => { + const updatedApplication = args.applicationsToUpdate.find( + (appToUpdate) => appToUpdate.index === app.index, + ); + + if (updatedApplication) { + return { ...app, status: updatedApplication.status }; + } + + return app; + }); + + return rowsToUpdate.map((rowIndex) => { + return { + index: BigInt(rowIndex), + statusRow: buildRowOfApplicationStatuses({ + rowIndex, + applications: updatedApplications, + statusToNumber: args.statusToNumber, + bitsPerStatus: args.bitsPerStatus, + }), + }; + }); +}; + +export const buildRowOfApplicationStatuses = ({ + rowIndex, + applications, + statusToNumber, + bitsPerStatus, +}: { + rowIndex: number; + applications: { index: number; status: ApplicationStatusType }[]; + statusToNumber: (status: ApplicationStatusType) => bigint; + bitsPerStatus: number; +}) => { + const applicationsPerRow = 256 / bitsPerStatus; + const startApplicationIndex = rowIndex * applicationsPerRow; + let row = 0n; + + for (let columnIndex = 0; columnIndex < applicationsPerRow; columnIndex++) { + const applicationIndex = startApplicationIndex + columnIndex; + const application = applications.find((app) => app.index === applicationIndex); + + if (application === undefined) { + continue; + } + + const status = statusToNumber(application.status); + + const shiftedStatus = status << BigInt(columnIndex * bitsPerStatus); + row |= shiftedStatus; + } + + return row; +}; + +export const getStrategyInstance = ( + strategyAddress: Address, + chainId: number, + roundId: string, + strategy?: PoolCategory, +) => { + let strategyInstance; + let strategyInstanceAbi; + switch (strategy) { + case PoolCategory.QuadraticFunding: { + strategyInstance = new DonationVotingMerkleDistributionStrategy({ + chain: chainId, + poolId: BigInt(roundId), + address: strategyAddress, + }); + strategyInstanceAbi = DonationVotingMerkleDistributionDirectTransferStrategyAbi; + break; + } + case PoolCategory.Direct: { + strategyInstance = new DirectGrantsLiteStrategy({ + chain: chainId, + poolId: BigInt(roundId), + address: strategyAddress, + }); + strategyInstanceAbi = DirectGrantsLiteStrategyAbi; + break; + } + case PoolCategory.Retrofunding: { + strategyInstance = new EasyRetroFundingStrategy({ + chain: chainId, + poolId: BigInt(roundId), + address: strategyAddress, + }); + strategyInstanceAbi = EasyRetroFundingStrategyAbi; + break; + } + default: + throw new Error("Invalid strategy"); + } + + return { strategyInstance, strategyInstanceAbi }; +}; + +export function reviewRecipients( + args: { + roundId: string; + strategyAddress: Address; + applicationsToUpdate: { index: number; status: ApplicationStatusType }[]; + currentApplications: { index: number; status: ApplicationStatusType }[]; + strategy?: PoolCategory; + }, + chainId: number, + walletClient: WalletClient, +): ReviewRecipients { + const reviewRecipientsInstance = new ReviewRecipients(); + reviewRecipientsInstance.execute(args, chainId, walletClient); + return reviewRecipientsInstance; +} + +export const getOnchainEvaluationProgressSteps = ({ + contractUpdatingStatus, + indexingStatus, + finishingStatus, +}: { + contractUpdatingStatus: ProgressStatus; + indexingStatus: ProgressStatus; + finishingStatus: ProgressStatus; +}): Step[] => { + return [ + { + name: "Updating", + description: `Updating application statuses on the contract.`, + status: contractUpdatingStatus, + }, + { + name: "Indexing", + description: "Indexing the data.", + status: indexingStatus, + }, + { + name: "Finishing", + description: "Just another moment while we finish things up.", + status: finishingStatus, + }, + ]; +}; diff --git a/apps/admin/src/pages/Pool/PoolTabs.tsx b/apps/admin/src/pages/Pool/PoolTabs.tsx index 43e2c2e..fdc2fa2 100644 --- a/apps/admin/src/pages/Pool/PoolTabs.tsx +++ b/apps/admin/src/pages/Pool/PoolTabs.tsx @@ -3,48 +3,51 @@ import { TabApplication, TabDistribute, TabRoundDate, TabVoter, TabRoundDetail } export const PoolTabs = () => { return ( - , - tabIcon: , - tabKey: "applications", - tabSubtitle: "Review and approve applications", - tabTitle: "Applications", - }, - { - tabContent: , - tabIcon: , - tabKey: "round-details", - tabSubtitle: "Configure name and description", - tabTitle: "Round details", - }, - // tab round date - { - tabContent: , - tabIcon: , - tabKey: "round-date", - tabSubtitle: "Adjust round and application dates", - tabTitle: "Round dates", - }, - // tab voter - { - tabContent: , - tabIcon: , - tabKey: "voter", - tabSubtitle: "Configure voter settings", - tabTitle: "Voters", - }, - ///tab Distribute - { - tabContent: , - tabIcon: , - tabKey: "distribute", - tabSubtitle: "Fund your round and distribute tokens", - tabTitle: "Fund and distribute", - }, - ]} - withIcons - /> +
+ , + tabIcon: , + tabKey: "applications", + tabSubtitle: "Review and approve applications", + tabTitle: "Applications", + }, + { + tabContent: , + tabIcon: , + tabKey: "round-details", + tabSubtitle: "Configure name and description", + tabTitle: "Round details", + }, + // tab round date + { + tabContent: , + tabIcon: , + tabKey: "round-date", + tabSubtitle: "Adjust round and application dates", + tabTitle: "Round dates", + }, + // tab voter + { + tabContent: , + tabIcon: , + tabKey: "voter", + tabSubtitle: "Configure voter settings", + tabTitle: "Voters", + }, + ///tab Distribute + { + tabContent: , + tabIcon: , + tabKey: "distribute", + tabSubtitle: "Fund your round and distribute tokens", + tabTitle: "Fund and distribute", + }, + ]} + withIcons + /> +
); }; diff --git a/apps/admin/src/pages/Pool/tabs/TabApplication.tsx b/apps/admin/src/pages/Pool/tabs/TabApplication.tsx index f88e0cd..2e9b7c8 100644 --- a/apps/admin/src/pages/Pool/tabs/TabApplication.tsx +++ b/apps/admin/src/pages/Pool/tabs/TabApplication.tsx @@ -1,10 +1,52 @@ +import { useParams } from "react-router"; +import { Checker, EvaluationBody } from "gitcoin-ui/checker"; +import { useAccount, useWalletClient } from "wagmi"; +import { MessagePage } from "@/components/Message"; +import { usePerformEvaluation } from "@/hooks/checker/usePerformEvaluation"; +import { usePerformOnChainReview } from "@/hooks/checker/usePerformOnChainReview"; + export const TabApplication = () => { + // const { poolId, chainId } = useParams(); + + const { poolId, chainId } = { poolId: "635", chainId: "11155111" }; + + const { address } = useAccount(); + const { data: walletClient } = useWalletClient(); + + const { setEvaluationBody, isSuccess, isEvaluating, isError } = usePerformEvaluation(); + const { steps, setReviewBody, isReviewing } = usePerformOnChainReview(); + + if (!chainId || !poolId) { + return ( + + ); + } + + if (!address || !walletClient) { + return ( + + ); + } + return ( -
-
Applications
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. -
-
+ setEvaluationBody(body)} + isSuccess={isSuccess} + isEvaluating={isEvaluating} + isError={isError} + steps={steps} + setReviewBody={setReviewBody as any} + isReviewing={isReviewing} + isStandalone={false} + /> ); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d8b7f2..d8b00ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,11 +54,14 @@ importers: specifier: ^3.3.0 version: 3.3.0 gitcoin-ui: - specifier: ^3.7.0 - version: 3.8.0(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(bufferutil@4.0.9)(graphql@16.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.32.0)(storybook@8.5.1(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.5.4)))(typescript@5.5.4)(utf-8-validate@5.0.10)(vite@6.0.11(@types/node@22.7.5)(jiti@1.21.7)(yaml@2.7.0)) + specifier: ^3.8.2 + version: 3.8.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(bufferutil@4.0.9)(graphql@16.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.32.0)(storybook@8.5.1(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.5.4)))(typescript@5.5.4)(utf-8-validate@5.0.10)(vite@6.0.11(@types/node@22.7.5)(jiti@1.21.7)(yaml@2.7.0)) graphql-request: specifier: ^5.2.0 version: 5.2.0(graphql@16.10.0) + json-stringify-deterministic: + specifier: ^1.0.12 + version: 1.0.12 pino-pretty: specifier: ^13.0.0 version: 13.0.0 @@ -3853,6 +3856,12 @@ packages: react: 18.3.1 react-dom: 18.3.1 + gitcoin-ui@3.8.2: + resolution: {integrity: sha512-d53SBz19nGA1kEZh04te0YcBXPHmRkuvy7DU6wI9mBtaTJ6b+CB6cy+QOcnH9RikE4W8U+RrDtT1dxJEytwS1A==} + peerDependencies: + react: 18.3.1 + react-dom: 18.3.1 + github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} @@ -4439,6 +4448,10 @@ packages: resolution: {integrity: sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==} engines: {node: '>=7.10.1'} + json-stringify-deterministic@1.0.12: + resolution: {integrity: sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g==} + engines: {node: '>= 4'} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -5389,6 +5402,12 @@ packages: '@types/react': '>=18' react: '>=18' + react-number-format@5.4.3: + resolution: {integrity: sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ==} + peerDependencies: + react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -11205,6 +11224,84 @@ snapshots: - utf-8-validate - vite + gitcoin-ui@3.8.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(bufferutil@4.0.9)(graphql@16.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.32.0)(storybook@8.5.1(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.5.4)))(typescript@5.5.4)(utf-8-validate@5.0.10)(vite@6.0.11(@types/node@22.7.5)(jiti@1.21.7)(yaml@2.7.0)): + dependencies: + '@gitcoin/gitcoin-chain-data': 1.0.44(bufferutil@4.0.9)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.24.1) + '@heroicons/react': 1.0.6(react@18.3.1) + '@hookform/resolvers': 3.10.0(react-hook-form@7.54.2(react@18.3.1)) + '@radix-ui/react-accordion': 1.2.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-alert-dialog': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-aspect-ratio': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-avatar': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-checkbox': 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-collapsible': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-context-menu': 2.2.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dialog': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dropdown-menu': 2.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-hover-card': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-label': 2.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-menubar': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-navigation-menu': 1.2.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popover': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-progress': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-radio-group': 1.2.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-scroll-area': 1.2.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-select': 2.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-separator': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slider': 1.2.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-switch': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tabs': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toast': 1.2.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-toggle-group': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tooltip': 1.1.7(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@storybook/addon-actions': 8.3.5(storybook@8.5.1(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10)) + '@tanstack/react-query': 5.64.2(react@18.3.1) + '@types/papaparse': 5.3.15 + '@uiw/react-markdown-preview': 5.1.3(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@uiw/react-md-editor': 4.0.5(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + cmdk: 1.0.0(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + embla-carousel-react: 8.5.2(react@18.3.1) + graphql-request: 7.1.2(graphql@16.10.0) + idb: 8.0.1 + input-otp: 1.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + moment: 2.30.1 + moment-timezone: 0.5.46 + next-themes: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + papaparse: 5.5.1 + react: 18.3.1 + react-day-picker: 9.3.2(react@18.3.1) + react-dom: 18.3.1(react@18.3.1) + react-hook-form: 7.54.2(react@18.3.1) + react-markdown: 9.0.3(@types/react@18.3.18)(react@18.3.1) + react-number-format: 5.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-resizable-panels: 2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-use: 17.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + recharts: 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rehype-raw: 7.0.0 + rehype-sanitize: 6.0.0 + remark-gfm: 4.0.0 + sonner: 1.7.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + tailwind-variants: 0.2.1(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.7.5)(typescript@5.5.4))) + ts-pattern: 5.6.2 + vaul: 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + viem: 2.21.60(bufferutil@4.0.9)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.24.1) + vite-plugin-svgr: 4.3.0(rollup@4.32.0)(typescript@5.5.4)(vite@6.0.11(@types/node@22.7.5)(jiti@1.21.7)(yaml@2.7.0)) + zod: 3.24.1 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - bufferutil + - graphql + - rollup + - storybook + - supports-color + - tailwindcss + - typescript + - utf-8-validate + - vite + github-slugger@2.0.0: {} glob-parent@5.1.2: @@ -11945,6 +12042,8 @@ snapshots: json-stream-stringify@3.1.6: {} + json-stringify-deterministic@1.0.12: {} + json5@2.2.3: {} jsonfile@4.0.0: @@ -13140,6 +13239,11 @@ snapshots: transitivePeerDependencies: - supports-color + react-number-format@5.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll-bar@2.3.8(@types/react@18.3.18)(react@18.3.1): dependencies: react: 18.3.1