Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
form

add checker

add voter tab

add date tab

restructure

wire in data

fix

updated tabs
  • Loading branch information
thelostone-mc authored and 0xKurt committed Jan 29, 2025
1 parent 1db4e3e commit 913158f
Show file tree
Hide file tree
Showing 33 changed files with 1,316 additions and 36 deletions.
13 changes: 12 additions & 1 deletion apps/admin/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
import { reactJsConfig } from "@repo/eslint-config/react-js";

/** @type {import("eslint").Linter.Config} */
export default reactJsConfig;
export default {
...reactJsConfig,
ignores: [
"node_modules/",
"**/*.less",
"**/*.css",
"**/*.scss",
"**/*.json",
"**/*.png",
"**/*.svg",
],
};
2 changes: 2 additions & 0 deletions apps/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"dotenv": "^16.4.5",
"events": "^3.3.0",
"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",
Expand All @@ -39,6 +40,7 @@
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.20",
"eslint": "^9.15.0",
"eslint-plugin-react-refresh": "^0.4.18",
"globals": "^15.14.0",
"lint-staged": "~13.2.2",
"postcss": "^8.4.49",
Expand Down
9 changes: 0 additions & 9 deletions apps/admin/src/.eslintignore

This file was deleted.

25 changes: 25 additions & 0 deletions apps/admin/src/components/Message.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={`flex min-h-[50vh] flex-col items-center justify-center p-4 ${className}`}>
<div className="mx-auto max-w-md text-center">
<h1 className="text-gray-900 mb-3 text-2xl font-bold">{title}</h1>

<p className="text-gray-600 mb-8">{message}</p>

{action && <Button onClick={action.onClick} value={action.label} />}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useQuery } from "@tanstack/react-query";
import { getRoundByChainIdAndPoolId } from "@/services/allo-indexer/dataLayer";

export const useGetRoundByChainIdAndPoolId = (chainId: number, poolId: string) => {
return useQuery({
queryKey: ["round", chainId, poolId],
queryFn: () => getRoundByChainIdAndPoolId(chainId, poolId),
});
};
1 change: 0 additions & 1 deletion apps/admin/src/hooks/backend/useGetMetrics/index.ts

This file was deleted.

16 changes: 16 additions & 0 deletions apps/admin/src/hooks/backend/useGetPool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useQuery } from "@tanstack/react-query";
import { getPool } from "@/services/backend/dataLayer";

export const useGetPool = (alloPoolId: string, chainId: number) => {
const query = useQuery({
queryKey: ["pool", alloPoolId, chainId],
queryFn: async () => await getPool(alloPoolId, chainId),
});

return {
data: query.data,
isLoading: query.isLoading,
isError: query.isError,
error: query.error,
};
};
2 changes: 2 additions & 0 deletions apps/admin/src/hooks/checker/usePerformEvaluation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./usePerformEvaluation";
export * from "./utils";
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useState, useEffect } from "react";
import { EvaluationBody } from "@gitcoin/ui/checker";
import { useMutation } from "@tanstack/react-query";
import { Hex } from "viem";
import { useAccount, useWalletClient } from "wagmi";
import { deterministicKeccakHash, submitEvaluation } from "./utils";

export const usePerformEvaluation = () => {
const [evaluationBody, setEvaluationBody] = useState<EvaluationBody | null>(null);
const { address } = useAccount();
const { data: walletClient } = useWalletClient();

const handleSetEvaluationBody = (data: EvaluationBody) => {
setEvaluationBody(data);
};

const signEvaluationBody = async (): Promise<Hex> => {
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,
};
};
65 changes: 65 additions & 0 deletions apps/admin/src/hooks/checker/usePerformEvaluation/utils.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> {
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<T>(obj: T): Promise<Hex> {
const deterministicString = stringify(obj);
return keccak256(toHex(deterministicString));
}
2 changes: 2 additions & 0 deletions apps/admin/src/hooks/checker/usePerformOnChainReview/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./usePerformOnChainReview";
export * from "./utils";
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { useState, useEffect, useMemo } from "react";
import { ReviewBody } from "@gitcoin/ui/checker";
import { ProgressStatus } from "@gitcoin/ui/types";
import { useMutation } from "@tanstack/react-query";
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<ReviewBody | null>(null);
const [contractUpdatingStatus, setContractUpdatingStatus] = useState<ProgressStatus>(
ProgressStatus.NOT_STARTED,
);
const [indexingStatus, setIndexingStatus] = useState<ProgressStatus>(ProgressStatus.NOT_STARTED);
const [finishingStatus, setFinishingStatus] = useState<ProgressStatus>(
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,
};
};
Loading

0 comments on commit 913158f

Please sign in to comment.