diff --git a/.eslintrc.js b/.eslintrc.js index 5b999efa4..fc519f6d2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,10 +1,5 @@ module.exports = { - root: true, - // This tells ESLint to load the config from the package `eslint-config-custom` + root: true, // This tells ESLint to load the config from the package `eslint-config-custom` extends: ["custom"], - settings: { - next: { - rootDir: ["apps/*/"], - }, - }, + settings: { next: { rootDir: ["apps/*/"] } }, }; diff --git a/.gitmodules b/.gitmodules index a0a8ada2b..a9f320eb4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,4 +15,4 @@ url = https://github.com/OpenZeppelin/openzeppelin-contracts [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable - url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index c39d26c1c..64805fc65 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,8 +2,9 @@ "solidity.packageDefaultDependenciesContractsDirectory": "pkg/contracts/src", "solidity.packageDefaultDependenciesDirectory": "lib", "[solidity]": { - "editor.defaultFormatter": "JuanBlanco.solidity", + "editor.defaultFormatter": "NomicFoundation.hardhat-solidity", "editor.formatOnSave": true }, - "solidity.formatter": "forge" -} + "solidity.formatter": "forge", + "editor.formatOnSave": true, +} \ No newline at end of file diff --git a/apps/docs/pages/index.tsx b/apps/docs/pages/index.tsx index 0d1dabc12..1d72a6fef 100644 --- a/apps/docs/pages/index.tsx +++ b/apps/docs/pages/index.tsx @@ -1,3 +1,4 @@ +import _ from "react"; import { Button } from "ui"; export default function Docs() { diff --git a/apps/web/.eslintrc.js b/apps/web/.eslintrc.js index 16cf4ecde..d86a8a457 100644 --- a/apps/web/.eslintrc.js +++ b/apps/web/.eslintrc.js @@ -2,15 +2,88 @@ * @type {import('eslint').Linter.Config} */ module.exports = { - extends: ["next/core-web-vitals", "turbo", "prettier"], - ignorePatterns: ["node_modules", "dist"], + env: { + browser: true, + es2021: true, + node: true, + }, + extends: [ + "airbnb/hooks", + "airbnb-typescript", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:jsx-a11y/recommended", + "next/core-web-vitals", + "prettier/prettier", + ], + plugins: ["react"], + ignorePatterns: ["node_modules", "dist", "src/generated.ts", "public"], + parser: "@typescript-eslint/parser", parserOptions: { - babelOptions: { - presets: [require.resolve("next/babel")], - }, + project: "./tsconfig.json", }, rules: { + "react/jsx-filename-extension": [ + 2, + { extensions: [".js", ".jsx", ".ts", ".tsx"] }, + ], "react-hooks/exhaustive-deps": "off", - "no-html-link-for-pages": "off", + "@next/next/no-html-link-for-pages": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/ban-ts-comment": "off", + "@next/next/no-page-custom-font": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/prefer-nullish-coalescing": "error", + "react/self-closing-comp": [ + "error", + { + component: true, + html: true, + }, + ], + "no-unreachable": "warn", + "import/order": [ + "error", + { + groups: ["builtin", "external", "internal"], + pathGroups: [ + { + pattern: "react", + group: "external", + position: "before", + }, + ], + pathGroupsExcludedImportTypes: ["react"], + alphabetize: { + order: "asc", + caseInsensitive: true, + }, + }, + ], + "no-console": [ + "warn", + { + allow: ["warn", "error", "info", "debug", "table"], + }, + ], + "prefer-const": "off", + "@typescript-eslint/consistent-type-definitions": "off", + "no-unused-expressions": "error", + "no-unsafe-optional-chaining": "error", + "import/extensions": "off", + "@typescript-eslint/quotes": ["error", "double"], + "@typescript-eslint/no-use-before-define": "off", + "jsx-a11y/no-static-element-interactions": "off", + "react/no-array-index-key": "warn", + indent: "off", + "@typescript-eslint/indent": "off", }, }; diff --git a/apps/web/.vscode/extensions.json b/apps/web/.vscode/extensions.json index cdfb20ce3..643dfeccc 100644 --- a/apps/web/.vscode/extensions.json +++ b/apps/web/.vscode/extensions.json @@ -1,6 +1,3 @@ { - "recommendations": [ - "skillhub.daisy-ui-latest-snippets", - "bradlc.vscode-tailwindcss" - ] -} \ No newline at end of file + "recommendations": ["skillhub.daisy-ui-latest-snippets", "bradlc.vscode-tailwindcss"] +} diff --git a/apps/web/.vscode/launch.json b/apps/web/.vscode/launch.json index f3704121e..70d4de43c 100644 --- a/apps/web/.vscode/launch.json +++ b/apps/web/.vscode/launch.json @@ -5,7 +5,7 @@ "name": "Next.js: debug server-side", "type": "node-terminal", "request": "launch", - "command": "pnpm dev", + "command": "pnpm dev" }, { "name": "Next.js: debug client-side", diff --git a/apps/web/.vscode/settings.json b/apps/web/.vscode/settings.json new file mode 100644 index 000000000..f2d1bb736 --- /dev/null +++ b/apps/web/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "rvest.vs-code-prettier-eslint", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "always" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + }, + "[typescript]": { + "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + }, + "css.lint.unknownAtRules": "ignore", + "[javascript]": { + "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + }, + "editor.tabSize": 2, +} \ No newline at end of file diff --git a/apps/web/actions/getProposals.ts b/apps/web/actions/getProposals.ts index b5e478425..bcd54f0d5 100644 --- a/apps/web/actions/getProposals.ts +++ b/apps/web/actions/getProposals.ts @@ -6,7 +6,7 @@ export async function getProposals( strategy: LightCVStrategy, ) { try { - async function fetchIPFSDataBatch( + const fetchIPFSDataBatch = async function ( proposals: LightProposal[], batchSize = 5, delay = 300, @@ -41,28 +41,30 @@ export async function getProposals( } return results; - } + }; - async function transformProposals(strategy: LightCVStrategy) { - const proposalsData = await fetchIPFSDataBatch(strategy.proposals); - const transformedProposals = proposalsData.map((data, index) => { - const p = strategy.proposals[index]; - return { - ...p, - voterStakedPointsPct: 0, - stakedAmount: strategy.proposals[index].stakedAmount, - title: data.title, - type: strategy.config?.proposalType as number, - status: strategy.proposals[index].proposalStatus, - }; - }); + const transformProposals = async function (_strategy: LightCVStrategy) { + const proposalsData = await fetchIPFSDataBatch(_strategy.proposals); + const transformedProposals = proposalsData + .map((data, index) => { + const p = _strategy.proposals[index]; + return { + ...p, + voterStakedPointsPct: 0, + stakedAmount: _strategy.proposals[index].stakedAmount, + title: data.title, + type: _strategy.config?.proposalType as number, + status: _strategy.proposals[index].proposalStatus, + }; + }) + .sort((a, b) => +a.proposalNumber - +b.proposalNumber); // Sort by proposal number ascending return transformedProposals; - } + }; let transformedProposals = await transformProposals(strategy); return transformedProposals; } catch (error) { - console.log(error); + console.error("Error while getting proposal ipfs metadata", error); } } diff --git a/apps/web/app/(app)/docs/page.tsx b/apps/web/app/(app)/docs/page.tsx index 133632706..ea2a5bca2 100644 --- a/apps/web/app/(app)/docs/page.tsx +++ b/apps/web/app/(app)/docs/page.tsx @@ -1,5 +1,5 @@ import React from "react"; -export default function Docs() { +export default function Page() { return
Docs...
; } diff --git a/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/[poolId]/[proposalId]/page.tsx b/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/[poolId]/[proposalId]/page.tsx index ea010f4bd..08b19ca04 100644 --- a/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/[poolId]/[proposalId]/page.tsx +++ b/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/[poolId]/[proposalId]/page.tsx @@ -1,50 +1,27 @@ "use client"; -import { Badge, Statistic, DisplayNumber } from "@/components"; -import { EthAddress } from "@/components"; -import { cvStrategyABI } from "@/src/generated"; -import { Address, formatUnits } from "viem"; -import { ConvictionBarChart } from "@/components/Charts/ConvictionBarChart"; +import { useEffect, useState } from "react"; +import { Hashicon } from "@emeraldpay/hashicon-react"; +import { InformationCircleIcon, UserIcon } from "@heroicons/react/24/outline"; +import { toast } from "react-toastify"; +import { Address, encodeAbiParameters, formatUnits } from "viem"; import { getProposalDataDocument, getProposalDataQuery, } from "#/subgraph/.graphclient"; -import { calculatePercentageBigInt } from "@/utils/numbers"; -import Image from "next/image"; +import { Badge, Button, DisplayNumber, EthAddress, Statistic } from "@/components"; +import { ConvictionBarChart } from "@/components/Charts/ConvictionBarChart"; +import { LoadingSpinner } from "@/components/LoadingSpinner"; +import { usePubSubContext } from "@/contexts/pubsub.context"; +import { useChainIdFromPath } from "@/hooks/useChainIdFromPath"; +import { useContractWriteWithConfirmations } from "@/hooks/useContractWriteWithConfirmations"; +import { useConvictionRead } from "@/hooks/useConvictionRead"; +import { useSubgraphQuery } from "@/hooks/useSubgraphQuery"; +import { alloABI } from "@/src/generated"; +import { poolTypes, proposalStatus } from "@/types"; +import { abiWithErrors } from "@/utils/abiWithErrors"; +import { useErrorDetails } from "@/utils/getErrorName"; import { getIpfsMetadata } from "@/utils/ipfsUtils"; -import { UserIcon, InformationCircleIcon } from "@heroicons/react/24/outline"; -import { proposalStatus, poolTypes } from "@/types"; -import { proposalImg } from "@/assets"; -import useSubgraphQuery from "@/hooks/useSubgraphQuery"; -import { useState, useEffect } from "react"; -import { useContractRead } from "wagmi"; -import LoadingSpinner from "@/components/LoadingSpinner"; - -export const dynamic = "force-dynamic"; - -type ProposalsMock = { - title: string; - type: "funding" | "streaming" | "signaling"; - description: string; - value?: number; - id: number; -}; - -type UnparsedProposal = { - submitter: Address; - beneficiary: Address; - requestedToken: Address; - requestedAmount: number; - stakedTokens: number; - proposalType: any; - proposalStatus: any; - blockLast: number; - convictionLast: number; - agreementActionId: number; - threshold: number; - voterStakedPointsPct: number; -}; - -type Proposal = UnparsedProposal & ProposalsMock; +import { logOnce } from "@/utils/log"; const prettyTimestamp = (timestamp: number) => { const date = new Date(timestamp * 1000); @@ -56,12 +33,16 @@ const prettyTimestamp = (timestamp: number) => { return `${day} ${month} ${year}`; }; -export default function Proposal({ - params: { proposalId, poolId, chain, garden }, +export default function Page({ + params: { proposalId, garden, poolId }, }: { - params: { proposalId: string; poolId: string; chain: string; garden: string }; + params: { + proposalId: string; + poolId: string; + chain: string; + garden: string; + }; }) { - // TODO: fetch garden decimals in query const { data } = useSubgraphQuery({ query: getProposalDataDocument, variables: { @@ -79,63 +60,93 @@ export default function Proposal({ const metadata = proposalData?.metadata; + const proposalIdNumber = proposalData?.proposalNumber as number; + + const { publish } = usePubSubContext(); + const chainId = useChainIdFromPath(); + const [ipfsResult, setIpfsResult] = useState>>(); useEffect(() => { if (metadata) { - getIpfsMetadata(metadata).then((data) => { - setIpfsResult(data); + getIpfsMetadata(metadata).then((d) => { + setIpfsResult(d); }); } }, [metadata]); - const cvStrategyContract = { - address: proposalData?.strategy.id as Address, - abi: cvStrategyABI, - }; - - const proposalIdNumber = proposalData?.proposalNumber; + const isProposalEnded = + !!proposalData && + (proposalStatus[proposalData.proposalStatus] === "executed" || + proposalStatus[proposalData.proposalStatus] === "cancelled"); + logOnce("debug", { isProposalEnded, proposalStatus: proposalStatus[proposalData?.proposalStatus] }); - const { data: thFromContract } = useContractRead({ - ...cvStrategyContract, - functionName: "calculateThreshold", - args: [proposalIdNumber], - enabled: !!proposalIdNumber, + const { currentConvictionPct, thresholdPct, totalSupportPct, updateConvictionLast } = useConvictionRead({ + proposalData, + tokenData: data?.tokenGarden, }); - const { data: totalEffectiveActivePoints } = useContractRead({ - ...cvStrategyContract, - functionName: "totalEffectiveActivePoints", - }); + const tokenSymbol = data?.tokenGarden?.symbol; + const proposalType = proposalData?.strategy.config?.proposalType; + const requestedAmount = proposalData?.requestedAmount; + const beneficiary = proposalData?.beneficiary as Address | undefined; + const submitter = proposalData?.submitter as Address | undefined; + const status = proposalData?.proposalStatus; - const { data: stakeAmountFromContract } = useContractRead({ - ...cvStrategyContract, - functionName: "getProposalStakedAmount", - args: [proposalIdNumber], - enabled: !!proposalIdNumber, - }); + const isSignalingType = poolTypes[proposalType] === "signaling"; - const { data: updateConvictionLast } = useContractRead({ - ...cvStrategyContract, - functionName: "updateProposalConviction" as any, // TODO: fix CVStrategy.updateProposalConviction to view in contract - args: [proposalIdNumber], - enabled: !!proposalIdNumber, - }) as { data: bigint | undefined }; + //encode proposal id to pass as argument to distribute function + const encodedDataProposalId = (proposalId_: number) => { + if (!proposalId_) { + return; + } + const encodedProposalId = encodeAbiParameters( + [{ name: "proposalId", type: "uint" }], + [BigInt(proposalId_)], + ); + return encodedProposalId; + }; - const { data: maxCVSupply } = useContractRead({ - ...cvStrategyContract, - functionName: "getMaxConviction", - args: [totalEffectiveActivePoints || 0n], - enabled: !!totalEffectiveActivePoints, + //distribution function from Allo contract + //args: poolId, strategyId, encoded proposalId + const { + write: writeDistribute, + error: errorDistribute, + isError: isErrorDistribute, + } = useContractWriteWithConfirmations({ + address: data?.allos[0]?.id as Address, + abi: abiWithErrors(alloABI), + functionName: "distribute", + contractName: "Allo", + fallbackErrorMessage: "Error executing proposal. Please try again.", + onConfirmations: () => { + publish({ + topic: "proposal", + type: "update", + function: "distribute", + id:proposalId, + containerId: data?.cvproposal?.strategy?.id, + chainId, + }); + }, }); + const distributeErrorName = useErrorDetails(errorDistribute); + useEffect(() => { + if (isErrorDistribute && distributeErrorName.errorName !== undefined) { + toast.error("NOT EXECUTABLE:" + " " + distributeErrorName.errorName); + } + }, [isErrorDistribute]); + if ( !proposalData || !ipfsResult || - !maxCVSupply || - !totalEffectiveActivePoints || - updateConvictionLast == null + currentConvictionPct == null || + thresholdPct == null || + totalSupportPct == null || + !proposalIdNumber || + (updateConvictionLast == null && !isProposalEnded) ) { return (
@@ -144,87 +155,11 @@ export default function Proposal({ ); } - const tokenSymbol = data.tokenGarden?.symbol; - const tokenDecimals = data.tokenGarden?.decimals; - const convictionLast = proposalData.convictionLast; - const threshold = proposalData.threshold; - const proposalType = proposalData.strategy.config?.proposalType; - const requestedAmount = proposalData.requestedAmount; - const beneficiary = proposalData.beneficiary as Address; - const submitter = proposalData.submitter as Address; - const status = proposalData.proposalStatus; - const stakedAmount = proposalData.stakedAmount; - - const isSignalingType = poolTypes[proposalType] === "signaling"; - - //logs for debugging in arb sepolia - //TODO: remove before merge - console.log("requesteAmount: %s", requestedAmount); - console.log("maxCVSupply: %s", maxCVSupply); - //thresholda - // console.log(threshold); - console.log("threshold: %s", threshold); - // console.log(thFromContract); - console.log("thFromContract: %s", thFromContract); - //stakeAmount - // console.log(stakedAmount); - console.log("stakedAmount: %s", stakedAmount); - // console.log(stakeAmountFromContract); - console.log("stakeAmountFromContract: %s", stakeAmountFromContract); - // console.log(totalEffectiveActivePoints); - console.log("totalEffectiveActivePoints: %s", totalEffectiveActivePoints); - // console.log(updateConvictionLast); - console.log("updateConvictionLast: %s", updateConvictionLast); - // console.log(convictionLast); - console.log("convictionLast: %s", convictionLast); - - let thresholdPct = calculatePercentageBigInt( - threshold, - maxCVSupply, - tokenDecimals, - ); - - console.log("thresholdPct: %s", thresholdPct); - - // console.log("ff: %s", ff); - - // const totalSupportPct = calculatePercentageDecimals( - // stakedAmount, - // totalEffectiveActivePoints, - // tokenDecimals, - // ); - - let totalSupportPct = calculatePercentageBigInt( - stakedAmount, - totalEffectiveActivePoints, - tokenDecimals, - ); - - console.log("totalSupportPct: %s", totalSupportPct); - // const currentConvictionPct = calculatePercentageDecimals( - // updateConvictionLast, - // maxCVSupply, - // tokenDecimals, - // ); - - let currentConvictionPct = calculatePercentageBigInt( - BigInt(updateConvictionLast), - maxCVSupply, - tokenDecimals, - ); - - console.log("currentConviction: %s", currentConvictionPct); - return (
- {`proposal +
@@ -270,21 +205,27 @@ export default function Proposal({

Metrics

{/* TODO: need designs for this entire section */} - {status && proposalStatus[status] == "executed" ? ( -
- Proposal passed and executed successfully + {status && proposalStatus[status] === "executed" ? +
+
+ Proposal passed and executed successfully +
- ) : ( -
+ : ( -
- )} + )} +
+ {proposalStatus[status] !== "executed" && !isSignalingType && ( )} + +
); } + diff --git a/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/[poolId]/create-proposal/page.tsx b/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/[poolId]/create-proposal/page.tsx index 545dde41e..879f866f4 100644 --- a/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/[poolId]/create-proposal/page.tsx +++ b/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/[poolId]/create-proposal/page.tsx @@ -1,16 +1,16 @@ "use client"; +import React, { useEffect, useState } from "react"; +import { Address } from "viem"; import { getPoolDataDocument, getPoolDataQuery } from "#/subgraph/.graphclient"; import { ProposalForm } from "@/components/Forms"; -import LoadingSpinner from "@/components/LoadingSpinner"; -import useSubgraphQuery from "@/hooks/useSubgraphQuery"; +import { LoadingSpinner } from "@/components/LoadingSpinner"; +import { useSubgraphQuery } from "@/hooks/useSubgraphQuery"; import { getIpfsMetadata } from "@/utils/ipfsUtils"; -import { MAX_RATIO_CONSTANT, CV_SCALE_PRECISION } from "@/utils/numbers"; -import React, { useEffect, useState } from "react"; -import { Address } from "viem"; +import { CV_SCALE_PRECISION, MAX_RATIO_CONSTANT } from "@/utils/numbers"; export default function Page({ - params: { chain, poolId, garden }, + params: { poolId, garden }, }: { params: { chain: string; poolId: number; garden: string }; }) { diff --git a/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/[poolId]/page.tsx b/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/[poolId]/page.tsx index b254ae849..d5e85473e 100644 --- a/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/[poolId]/page.tsx +++ b/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/[poolId]/page.tsx @@ -1,46 +1,49 @@ "use client"; +import { useEffect, useState } from "react"; import { - Badge, - Proposals, - PoolMetrics, - EthAddress, - Statistic, -} from "@/components"; -import { grassLarge, blueLand } from "@/assets"; + BoltIcon, + ChartBarIcon, + ClockIcon, + InformationCircleIcon, + Square3Stack3DIcon, +} from "@heroicons/react/24/outline"; import Image from "next/image"; import { Allo, - TokenGarden, getAlloQuery, getPoolDataDocument, getPoolDataQuery, + TokenGarden, } from "#/subgraph/.graphclient"; import { Address } from "#/subgraph/src/scripts/last-addr"; -import { getIpfsMetadata } from "@/utils/ipfsUtils"; +import { blueLand, grassLarge } from "@/assets"; +import { + Badge, + EthAddress, + PoolMetrics, + Proposals, + Statistic, +} from "@/components"; +import { LoadingSpinner } from "@/components/LoadingSpinner"; +import { QUERY_PARAMS } from "@/constants/query-params"; +import { useCollectQueryParams } from "@/hooks/useCollectQueryParams"; +import { useSubgraphQuery } from "@/hooks/useSubgraphQuery"; import { pointSystems, poolTypes } from "@/types"; +import { getIpfsMetadata } from "@/utils/ipfsUtils"; import { CV_SCALE_PRECISION } from "@/utils/numbers"; -import { - InformationCircleIcon, - ChartBarIcon, - BoltIcon, - Square3Stack3DIcon, - ClockIcon, -} from "@heroicons/react/24/outline"; -import { useEffect, useState } from "react"; -import LoadingSpinner from "@/components/LoadingSpinner"; -import useSubgraphQuery from "@/hooks/useSubgraphQuery"; export const dynamic = "force-dynamic"; export type AlloQuery = getAlloQuery["allos"][number]; -export default function Pool({ +export default function Page({ params: { chain, poolId, garden }, }: { params: { chain: string; poolId: number; garden: string }; }) { - const { data, error } = useSubgraphQuery({ + const searchParams = useCollectQueryParams(); + const { data, refetch, error } = useSubgraphQuery({ query: getPoolDataDocument, variables: { poolId: poolId, garden: garden }, changeScope: [ @@ -69,12 +72,36 @@ export default function Pool({ useEffect(() => { if (metadata && !ipfsResult) { - getIpfsMetadata(metadata).then((data) => { - setIpfsResult(data); + getIpfsMetadata(metadata).then((d) => { + setIpfsResult(d); }); } }, [metadata]); + const strategyObj = data?.cvstrategies?.[0]; + + useEffect(() => { + if (!strategyObj) { + return; + } + console.debug( + "maxRatio: " + strategyObj?.config?.maxRatio, + "minThresholdPoints: " + strategyObj?.config?.minThresholdPoints, + "poolAmount: " + strategyObj?.poolAmount, + ); + }, [strategyObj?.config, strategyObj?.config, strategyObj?.poolAmount]); + + useEffect(() => { + const newProposalId = searchParams[QUERY_PARAMS.poolPage.newPropsoal]; + if ( + newProposalId && + data && + !strategyObj?.proposals.some((c) => c.proposalNumber === newProposalId) + ) { + refetch(); + } + }, [searchParams, strategyObj?.proposals]); + if (!data || !ipfsResult) { return (
@@ -83,7 +110,6 @@ export default function Pool({ ); } - const strategyObj = data?.cvstrategies?.[0]; if (!data || !strategyObj) { return
Pool {poolId} not found
; } @@ -101,12 +127,6 @@ export default function Pool({ const spendingLimitPct = (Number(strategyObj?.config?.maxRatio || 0) / CV_SCALE_PRECISION) * 100; - console.log( - "maxRatio: " + strategyObj?.config?.maxRatio, - "minThresholdPoints: " + strategyObj?.config?.minThresholdPoints, - "poolAmount: " + poolAmount, - ); - return (
{/* Header */} @@ -147,18 +167,17 @@ export default function Pool({
- {!isEnabled ? ( + {!isEnabled ?
Waiting for council approval
- ) : ( - pool image - )} + } {isEnabled && ( @@ -181,7 +200,7 @@ export default function Pool({ strategy={strategyObj} alloInfo={alloInfo} communityAddress={communityAddress} - createProposalUrl={`/gardens/${chain}/${garden}/${poolId}/create-proposal`} + createProposalUrl={`/gardens/${chain}/${garden}/${communityAddress}/${poolId}/create-proposal`} proposalType={proposalType} /> diff --git a/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/create-pool/page.tsx b/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/create-pool/page.tsx index 9a17d04c2..f8bfced16 100644 --- a/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/create-pool/page.tsx +++ b/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/create-pool/page.tsx @@ -1,52 +1,46 @@ "use client"; +import React from "react"; +import { Address } from "viem"; import { - TokenGarden, getPoolCreationDataDocument, getPoolCreationDataQuery, + TokenGarden, } from "#/subgraph/.graphclient"; -import PoolForm from "@/components/Forms/PoolForm"; -import LoadingSpinner from "@/components/LoadingSpinner"; -import useSubgraphQuery from "@/hooks/useSubgraphQuery"; -import React from "react"; -import { Address } from "viem"; +import { PoolForm } from "@/components/Forms/PoolForm"; +import { LoadingSpinner } from "@/components/LoadingSpinner"; +import { useSubgraphQuery } from "@/hooks/useSubgraphQuery"; -export default function CreatePool({ +export default function Page({ params: { chain, garden, community }, }: { params: { chain: number; garden: string; community: string }; }) { - const { data: result, error: error } = - useSubgraphQuery({ - query: getPoolCreationDataDocument, - variables: { communityAddr: community, tokenAddr: garden }, - }); + const { data: result } = useSubgraphQuery({ + query: getPoolCreationDataDocument, + variables: { communityAddr: community, tokenAddr: garden }, + }); let token = result?.tokenGarden; let alloAddr = result?.allos[0]?.id as Address; let communityName = result?.registryCommunity?.communityName as string; - return result ? ( -
-
-
-

- Create a Pool in {communityName} community -

- {/*
-

subtitle for pool form creation...

-
*/} -
- -
-
- ) : ( -
- -
- ); + return result ? +
+
+
+

+ Create a Pool in {communityName} community +

+
+ +
+
+ :
+ +
; } diff --git a/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/page.tsx b/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/page.tsx index 5ef168e74..bf55fdcee 100644 --- a/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/page.tsx +++ b/apps/web/app/(app)/gardens/[chain]/[garden]/[community]/page.tsx @@ -1,48 +1,57 @@ "use client"; -import { commImg, groupFlowers } from "@/assets"; import React, { useEffect, useState } from "react"; -import Image from "next/image"; import { - EthAddress, - Statistic, - PoolCard, - RegisterMember, - DisplayNumber, - IncreasePower, - Button, -} from "@/components"; + CurrencyDollarIcon, + PlusIcon, + RectangleGroupIcon, +} from "@heroicons/react/24/outline"; +import { Dnum } from "dnum"; +import Image from "next/image"; +import Link from "next/link"; import { Address } from "viem"; import { getCommunityDocument, getCommunityQuery, } from "#/subgraph/.graphclient"; +import { commImg, groupFlowers } from "@/assets"; import { - CurrencyDollarIcon, - ExclamationCircleIcon, - PlusIcon, - RectangleGroupIcon, -} from "@heroicons/react/24/outline"; + Button, + DisplayNumber, + EthAddress, + IncreasePower, + PoolCard, + RegisterMember, + Statistic, + InfoIcon, +} from "@/components"; +import { LoadingSpinner } from "@/components/LoadingSpinner"; +import { TokenGardenFaucet } from "@/components/TokenGardenFaucet"; +import { isProd } from "@/constants/contracts"; +import { QUERY_PARAMS } from "@/constants/query-params"; +import { useCollectQueryParams } from "@/hooks/useCollectQueryParams"; +import { useDisableButtons } from "@/hooks/useDisableButtons"; +import { useSubgraphQuery } from "@/hooks/useSubgraphQuery"; import { poolTypes } from "@/types"; import { - SCALE_PRECISION, - SCALE_PRECISION_DECIMALS, dn, parseToken, + SCALE_PRECISION, + SCALE_PRECISION_DECIMALS, } from "@/utils/numbers"; -import { Dnum } from "dnum"; -import useSubgraphQuery from "@/hooks/useSubgraphQuery"; -import LoadingSpinner from "@/components/LoadingSpinner"; -import Link from "next/link"; -import { useDisableButtons } from "@/hooks/useDisableButtons"; -export default function CommunityPage({ +export default function Page({ params: { chain, garden: tokenAddr, community: communityAddr }, }: { params: { chain: number; garden: string; community: string }; }) { + const searchParams = useCollectQueryParams(); const [covenant, setCovenant] = useState(); - const { data: result, error } = useSubgraphQuery({ + const { + data: result, + error, + refetch, + } = useSubgraphQuery({ query: getCommunityDocument, variables: { communityAddr: communityAddr, tokenAddr: tokenAddr }, changeScope: [ @@ -50,7 +59,7 @@ export default function CommunityPage({ { topic: "member", containerId: communityAddr }, ], }); - + const { tooltipMessage, isConnected, missmatchUrl } = useDisableButtons(); useEffect(() => { if (error) { @@ -72,22 +81,14 @@ export default function CommunityPage({ if (typeof json.covenant === "string") { setCovenant(json.covenant); } - } catch (error) { - console.log(error); + } catch (err) { + console.error(err); } } }; fetchCovenant(); }, [covenantIpfsHash]); - if (!tokenGarden || !result?.registryCommunity) { - return ( -
- -
- ); - } - let { communityName, members, @@ -96,7 +97,7 @@ export default function CommunityPage({ registerStakeAmount, registerToken, protocolFee, - } = result.registryCommunity; + } = result?.registryCommunity ?? {}; const communityStakedTokens = members?.reduce( @@ -117,10 +118,28 @@ export default function CommunityPage({ poolTypes[strategy.config?.proposalType] === "funding" && strategy.isEnabled, ); + const activePools = strategies?.filter((strategy) => strategy?.isEnabled); const poolsInReview = strategies.filter((strategy) => !strategy.isEnabled); - const activePools = strategies?.filter((strategy) => strategy?.isEnabled); + useEffect(() => { + const newPoolId = searchParams[QUERY_PARAMS.communityPage.newPool]; + if ( + newPoolId && + result && + !poolsInReview.some((c) => c.poolId === newPoolId) + ) { + refetch(); + } + }, [searchParams, poolsInReview]); + + if (!tokenGarden || !result?.registryCommunity) { + return ( +
+ +
+ ); + } const parsedCommunityFee = () => { try { @@ -134,8 +153,8 @@ export default function CommunityPage({ ] as dn.Dnum; return dn.multiply(membership, feePercentage); - } catch (error) { - console.log(error); + } catch (err) { + console.error(err); } return [0n, 0] as dn.Dnum; }; @@ -195,23 +214,18 @@ export default function CommunityPage({

Registration cost:

-
+ - -
+
@@ -301,15 +315,11 @@ export default function CommunityPage({

Covenant

- {covenantIpfsHash ? ( - covenant ? ( + {covenantIpfsHash ? + covenant ?

{covenant}

- ) : ( - - ) - ) : ( -

No covenant was submitted.

- )} + : + :

No covenant was submitted.

}
+ {!isProd && tokenGarden && }
); } diff --git a/apps/web/app/(app)/gardens/[chain]/[garden]/create-community/page.tsx b/apps/web/app/(app)/gardens/[chain]/[garden]/create-community/page.tsx index 6834cab8b..bfbfea05c 100644 --- a/apps/web/app/(app)/gardens/[chain]/[garden]/create-community/page.tsx +++ b/apps/web/app/(app)/gardens/[chain]/[garden]/create-community/page.tsx @@ -1,14 +1,15 @@ "use client"; + +import React, { useEffect } from "react"; +import { Address } from "viem"; import { - TokenGarden, getCommunityCreationDataDocument, getCommunityCreationDataQuery, + TokenGarden, } from "#/subgraph/.graphclient"; import { CommunityForm } from "@/components/Forms"; -import LoadingSpinner from "@/components/LoadingSpinner"; -import useSubgraphQuery from "@/hooks/useSubgraphQuery"; -import React, { useEffect } from "react"; -import { Address } from "viem"; +import { LoadingSpinner } from "@/components/LoadingSpinner"; +import { useSubgraphQuery } from "@/hooks/useSubgraphQuery"; export default function Page({ params: { chain, garden }, @@ -16,10 +17,10 @@ export default function Page({ params: { chain: number; garden: string }; }) { const { data: result, error: getCommunityCreationDataQueryError } = - useSubgraphQuery({ - query: getCommunityCreationDataDocument, - variables: { addr: garden }, - }); + useSubgraphQuery({ + query: getCommunityCreationDataDocument, + variables: { addr: garden }, + }); useEffect(() => { if (getCommunityCreationDataQueryError) { @@ -40,12 +41,13 @@ export default function Page({

- Welcome to the {tokenGarden.symbol} Community Form! + Welcome to the {tokenGarden.symbol} Community Form!

- Create a vibrant community around the {tokenGarden.name} by - providing the necessary details below. + Create a vibrant community around the{" "} + {tokenGarden.name} by providing the necessary + details below.

diff --git a/apps/web/app/(app)/gardens/[chain]/[garden]/page.tsx b/apps/web/app/(app)/gardens/[chain]/[garden]/page.tsx index 6602225fc..b024331eb 100644 --- a/apps/web/app/(app)/gardens/[chain]/[garden]/page.tsx +++ b/apps/web/app/(app)/gardens/[chain]/[garden]/page.tsx @@ -1,161 +1,178 @@ -"use client"; - -import { tree2, tree3, grassLarge, ecosystem } from "@/assets"; -import Image from "next/image"; -import { - Button, - Communities, - EthAddress, - Statistic, - TokenLabel, -} from "@/components"; -import { getGardenDocument, getGardenQuery } from "#/subgraph/.graphclient"; -import React, { useEffect } from "react"; -import { CubeTransparentIcon, PlusIcon } from "@heroicons/react/24/outline"; -import LoadingSpinner from "@/components/LoadingSpinner"; -import useSubgraphQuery from "@/hooks/useSubgraphQuery"; -import { isProd } from "@/constants/contracts"; -import TokenGardenFaucet from "@/components/TokenGardenFaucet"; -import { Address } from "viem"; -import Link from "next/link"; -import { useDisableButtons } from "@/hooks/useDisableButtons"; - -export const dynamic = "force-dynamic"; - -export default function Garden({ - params: { chain, garden }, -}: { - params: { chain: number; garden: string }; -}) { - const { data: result, error } = useSubgraphQuery({ - query: getGardenDocument, - variables: { addr: garden }, - changeScope: [ - { topic: "member" }, - { - topic: "community", - }, - { - topic: "garden", - id: garden, - }, - ], - }); - - const { tooltipMessage, isConnected, missmatchUrl } = useDisableButtons(); - - useEffect(() => { - if (error) { - console.error("Error while fetching garden data: ", error); - } - }, [error]); - - let communities = result?.tokenGarden?.communities || []; - - communities = communities.filter((com) => com.isValid); - - const tokenGarden = result?.tokenGarden; - - if (!tokenGarden) { - return ( -
- -
- ); - } - - const gardenTotalMembers = () => { - const uniqueMembers = new Set(); - - communities.forEach((community) => - community.members?.forEach((member) => - uniqueMembers.add(member?.memberAddress), - ), - ); - - return uniqueMembers.size; - }; - - return ( -
-
-
- {`${tokenGarden?.name}`} -
-
-
-
-
-

{tokenGarden?.name}

-
- -
-

- Discover communities in the - {tokenGarden?.name} Garden, - where you connect with people and support proposals bounded by a - shared - covenant. -

-
-
- } - count={communities?.length ?? 0} - /> - -
-
-
- -
-
-
-

- Create your own community -

-
-
- - - - tree - tree - grass -
-
-
- {!isProd && tokenGarden && ( - - )} -
- ); -} +"use client"; + +import React, { useEffect } from "react"; +import { CubeTransparentIcon, PlusIcon } from "@heroicons/react/24/outline"; +import Image from "next/image"; +import Link from "next/link"; +import { Address } from "viem"; +import { getGardenDocument, getGardenQuery } from "#/subgraph/.graphclient"; +import { ecosystem, grassLarge, tree2, tree3 } from "@/assets"; +import { + Button, + Communities, + EthAddress, + Statistic, + TokenLabel, +} from "@/components"; +import { LoadingSpinner } from "@/components/LoadingSpinner"; +import { TokenGardenFaucet } from "@/components/TokenGardenFaucet"; +import { isProd } from "@/constants/contracts"; +import { QUERY_PARAMS } from "@/constants/query-params"; +import { useCollectQueryParams } from "@/hooks/useCollectQueryParams"; +import { useDisableButtons } from "@/hooks/useDisableButtons"; +import { useSubgraphQuery } from "@/hooks/useSubgraphQuery"; + +export const dynamic = "force-dynamic"; + +export default function Page({ + params: { chain, garden }, +}: { + params: { chain: number; garden: string }; +}) { + const searchParams = useCollectQueryParams(); + const { + data: result, + error, + refetch, + } = useSubgraphQuery({ + query: getGardenDocument, + variables: { addr: garden }, + changeScope: [ + { topic: "member" }, + { + topic: "community", + }, + { + topic: "garden", + id: garden, + }, + ], + }); + + const { tooltipMessage, isConnected, missmatchUrl } = useDisableButtons(); + + useEffect(() => { + if (error) { + console.error("Error while fetching garden data: ", error); + } + }, [error]); + + let communities = + result?.tokenGarden?.communities?.filter((com) => com.isValid) ?? []; + + useEffect(() => { + const newCommunityId = + searchParams[QUERY_PARAMS.gardenPage.newCommunity]?.toLowerCase(); + + if ( + newCommunityId && + result && + !communities.some((c) => c.id.toLowerCase() === newCommunityId) + ) { + refetch(); + } + }, [searchParams, result]); + + const tokenGarden = result?.tokenGarden; + + if (!tokenGarden) { + return ( +
+ +
+ ); + } + + const gardenTotalMembers = () => { + const uniqueMembers = new Set(); + + communities.forEach((community) => + community.members?.forEach((member) => + uniqueMembers.add(member?.memberAddress), + ), + ); + + return uniqueMembers.size; + }; + + return ( +
+
+
+ {`${tokenGarden?.name}`} +
+
+
+
+
+

{tokenGarden?.name}

+
+ +
+

+ Discover communities in the + {tokenGarden?.name} Garden, + where you connect with people and support proposals bounded by a + shared + covenant. +

+
+
+ } + count={communities?.length ?? 0} + /> + +
+
+
+ +
+
+
+

+ Create your own community +

+
+
+ + + + tree + tree + grass +
+
+
+ {!isProd && tokenGarden && } +
+ ); +} diff --git a/apps/web/app/(app)/gardens/page.tsx b/apps/web/app/(app)/gardens/page.tsx index 48bebd300..39e0e24ef 100644 --- a/apps/web/app/(app)/gardens/page.tsx +++ b/apps/web/app/(app)/gardens/page.tsx @@ -2,18 +2,16 @@ import React, { useEffect, useMemo } from "react"; import Image from "next/image"; -import { clouds1, clouds2, gardenHeader } from "@/assets"; -import { GardenCard } from "@/components"; import { - getTokenGardensQuery, getTokenGardensDocument, + getTokenGardensQuery, } from "#/subgraph/.graphclient"; -import useSubgraphQueryMultiChain from "@/hooks/useSubgraphQueryMultiChain"; -import LoadingSpinner from "@/components/LoadingSpinner"; - -export const dynamic = "force-dynamic"; +import { clouds1, clouds2, gardenHeader } from "@/assets"; +import { GardenCard } from "@/components"; +import { LoadingSpinner } from "@/components/LoadingSpinner"; +import { useSubgraphQueryMultiChain } from "@/hooks/useSubgraphQueryMultiChain"; -export default function Gardens() { +export default function Page() { const { data: gardens, fetching, @@ -36,30 +34,32 @@ export default function Gardens() { } }, [errors.size]); - const tokenGardens = useMemo(() => { - return gardens - ?.flatMap((g) => g.tokenGardens) - .filter((x): x is NonNullable => !!x); - }, [gardens]); + const tokenGardens = useMemo( + () => + gardens + ?.flatMap((g) => g.tokenGardens) + .filter((x): x is NonNullable => !!x), + [gardens], + ); const GardenList = useMemo(() => { if (fetching) { return ; - } else if (tokenGardens?.length) { + } + if (tokenGardens?.length) { return ( <> - {tokenGardens.map((garden, id) => ( -
+ {tokenGardens.map((garden) => ( +
))} ); - } else { - return ( -

No Gardens

- ); } + return ( +

No Gardens

+ ); }, [fetching, tokenGardens?.length]); return ( @@ -83,7 +83,7 @@ export default function Gardens() { clouds
-
+
diff --git a/apps/web/app/(app)/layout.tsx b/apps/web/app/(app)/layout.tsx index 4284df4ef..e85df046c 100644 --- a/apps/web/app/(app)/layout.tsx +++ b/apps/web/app/(app)/layout.tsx @@ -1,8 +1,8 @@ import React from "react"; -import { NavBar } from "@/components"; -import { GoBackButton } from "@/components"; +import { GoBackButton, NavBar } from "@/components"; import { Breadcrumbs } from "@/components/Breadcrumbs"; -export default function layout({ children }: { children: React.ReactNode }) { + +export default function Layout({ children }: { children: React.ReactNode }) { return ( <> diff --git a/apps/web/app/api/ably-auth/route.ts b/apps/web/app/api/ably-auth/route.ts index ed670dbcc..6d3c087c7 100644 --- a/apps/web/app/api/ably-auth/route.ts +++ b/apps/web/app/api/ably-auth/route.ts @@ -1,10 +1,12 @@ -import { isProd } from "@/constants/contracts"; -import { CHANGE_EVENT_CHANNEL_NAME } from "@/globals"; +// api/ably-auth + import Ably from "ably"; -import { NextResponse, NextRequest } from "next/server"; +import { NextResponse } from "next/server"; import { HTTP_CODES } from "../utils"; +import { isProd } from "@/constants/contracts"; +import { CHANGE_EVENT_CHANNEL_NAME } from "@/globals"; -export async function POST(req: NextRequest, res: NextResponse) { +export async function POST() { // Used for linter that fails if (!process.env.NEXT_ABLY_API_KEY) { console.error("NEXT_ABLY_API_KEY env must be"); diff --git a/apps/web/app/api/ipfs/route.ts b/apps/web/app/api/ipfs/route.ts index 88d1d1acb..f9b6f3b8b 100644 --- a/apps/web/app/api/ipfs/route.ts +++ b/apps/web/app/api/ipfs/route.ts @@ -1,29 +1,24 @@ -import { NextResponse } from "next/server"; -import { NextRequest } from "next/server"; -import pinataSDK from "@pinata/sdk"; +// api/ipfs + import { Readable } from "stream"; +import pinataSDK from "@pinata/sdk"; +import { NextRequest, NextResponse } from "next/server"; const pinata = new pinataSDK({ pinataJWTKey: process.env.PINATA_JWT }); - const saveFile = async (buffer: Buffer, fileName: string) => { - try { - const readable = new Readable(); - readable.push(buffer); - readable.push(null); + const readable = new Readable(); + readable.push(buffer); + readable.push(null); - const options = { - pinataMetadata: { - name: fileName, - }, - }; - const response = await pinata.pinFileToIPFS(readable, options); + const options = { + pinataMetadata: { + name: fileName, + }, + }; + const response = await pinata.pinFileToIPFS(readable, options); - return response; - } catch (error) { - // console.log(error); - throw error; - } + return response; }; export async function POST(req: NextRequest) { @@ -37,7 +32,7 @@ export async function POST(req: NextRequest) { } catch (error) { return NextResponse.json( { message: "Error uploading json to IPFS" }, - { status: 500 } + { status: 500 }, ); } } else if (contentType?.startsWith("multipart/form-data")) { @@ -58,10 +53,13 @@ export async function POST(req: NextRequest) { } catch (error) { return NextResponse.json( { message: "Error uploading file to IPFS" }, - { status: 500 } + { status: 500 }, ); } } else { - return NextResponse.json({ message: "Invalid request" }, { status: 400 }); + return NextResponse.json( + { message: "Invalid request" }, + { status: 400 }, + ); } } diff --git a/apps/web/app/api/passport-oracle/addStrategy/route.ts b/apps/web/app/api/passport-oracle/addStrategy/route.ts index 6743c0238..43e6db456 100644 --- a/apps/web/app/api/passport-oracle/addStrategy/route.ts +++ b/apps/web/app/api/passport-oracle/addStrategy/route.ts @@ -1,4 +1,4 @@ -// pages/api/addStrategy.ts +// api/add-strategy import { NextResponse } from "next/server"; import { @@ -7,57 +7,34 @@ import { createWalletClient, custom, Address, - Chain, } from "viem"; -import { localhost, arbitrumSepolia, sepolia } from "viem/chains"; import { privateKeyToAccount } from "viem/accounts"; +import { getConfigByChain } from "@/constants/contracts"; import { passportScorerABI, registryCommunityABI, cvStrategyABI, } from "@/src/generated"; -import { getContractsAddrByChain } from "@/constants/contracts"; +import { getViemChain } from "@/utils/viem"; const LIST_MANAGER_PRIVATE_KEY = process.env.LIST_MANAGER_PRIVATE_KEY; -const CHAIN = process.env.CHAIN_ID ? parseInt(process.env.CHAIN_ID) : 1337; +const CHAIN_ID = process.env.CHAIN_ID ? parseInt(process.env.CHAIN_ID) : 1337; const LOCAL_RPC = "http://127.0.0.1:8545"; -const RPC_URL = getContractsAddrByChain(CHAIN)?.rpcUrl || LOCAL_RPC; +const RPC_URL = getConfigByChain(CHAIN_ID)?.rpcUrl ?? LOCAL_RPC; -const PASSPORT_SCORER_ADDRESS = getContractsAddrByChain(CHAIN) +const PASSPORT_SCORER_ADDRESS = getConfigByChain(CHAIN_ID) ?.passportScorer as Address; -function getViemChain(chain: number): Chain { - let viemChain: Chain; - - switch (chain) { - case localhost.id: - viemChain = localhost; - break; - case arbitrumSepolia.id: - viemChain = arbitrumSepolia; - break; - case sepolia.id: - viemChain = sepolia; - break; - - default: - viemChain = localhost; - break; - } - - return viemChain; -} - const client = createPublicClient({ - chain: getViemChain(CHAIN), + chain: getViemChain(CHAIN_ID), transport: http(RPC_URL), }); const walletClient = createWalletClient({ account: privateKeyToAccount(`${LIST_MANAGER_PRIVATE_KEY}` as Address), - chain: localhost, + chain: getViemChain(CHAIN_ID), transport: custom(client.transport), }); diff --git a/apps/web/app/api/passport-oracle/dailyJob/route.ts b/apps/web/app/api/passport-oracle/dailyJob/route.ts index e95cad2e4..41a81b98e 100644 --- a/apps/web/app/api/passport-oracle/dailyJob/route.ts +++ b/apps/web/app/api/passport-oracle/dailyJob/route.ts @@ -1,27 +1,27 @@ +// api/passport-oracles/daily-job + import { NextResponse } from "next/server"; +import { gql } from "urql"; import { createPublicClient, http, createWalletClient, custom, Address, - Chain, } from "viem"; -import { localhost, arbitrumSepolia, sepolia } from "viem/chains"; import { privateKeyToAccount } from "viem/accounts"; -import { gql } from "urql"; +import { getConfigByChain } from "@/constants/contracts"; import { initUrqlClient } from "@/providers/urql"; import { passportScorerABI } from "@/src/generated"; -import { getContractsAddrByChain } from "@/constants/contracts"; import { CV_PERCENTAGE_SCALE } from "@/utils/numbers"; +import { getViemChain } from "@/utils/viem"; -const LIST_MANAGER_PRIVATE_KEY = process.env.LIST_MANAGER_PRIVATE_KEY || ""; -const CHAIN = process.env.CHAIN_ID ? parseInt(process.env.CHAIN_ID) : 1337; +const LIST_MANAGER_PRIVATE_KEY = process.env.LIST_MANAGER_PRIVATE_KEY ?? ""; +const CHAIN_ID = process.env.CHAIN_ID ? parseInt(process.env.CHAIN_ID) : 1337; const LOCAL_RPC = "http://127.0.0.1:8545"; -const RPC_URL = getContractsAddrByChain(CHAIN)?.rpcUrl || LOCAL_RPC; -const CONTRACT_ADDRESS = getContractsAddrByChain(CHAIN) - ?.passportScorer as Address; -const SUBGRAPH = getContractsAddrByChain(CHAIN)?.subgraphUrl as string; +const RPC_URL = getConfigByChain(CHAIN_ID)?.rpcUrl ?? LOCAL_RPC; +const CONTRACT_ADDRESS = getConfigByChain(CHAIN_ID)?.passportScorer as Address; +const SUBGRAPH = getConfigByChain(CHAIN_ID)?.subgraphUrl as string; const API_ENDPOINT = "/api/passport/scores"; interface PassportUser { @@ -42,33 +42,18 @@ interface ApiScore { stamp_scores: Record; } -function getViemChain(chain: number): Chain { - switch (chain) { - case localhost.id: - return localhost; - case arbitrumSepolia.id: - return arbitrumSepolia; - case sepolia.id: - return sepolia; - default: - return localhost; - } -} - const client = createPublicClient({ - chain: getViemChain(CHAIN), + chain: getViemChain(CHAIN_ID), transport: http(RPC_URL), }); const walletClient = createWalletClient({ - account: privateKeyToAccount( - (`${LIST_MANAGER_PRIVATE_KEY}` as Address) || "", - ), - chain: getViemChain(CHAIN), + account: privateKeyToAccount(LIST_MANAGER_PRIVATE_KEY as Address), + chain: getViemChain(CHAIN_ID), transport: custom(client.transport), }); -const { urqlClient } = initUrqlClient({ chainId: CHAIN }); +const { urqlClient } = initUrqlClient({ chainId: CHAIN_ID }); const query = gql` query { @@ -84,7 +69,7 @@ const query = gql` const fetchScoresFromService = async (): Promise => { const url = new URL( API_ENDPOINT, - `http://${process.env.HOST || "localhost"}:${process.env.PORT || 3000}`, + `http://${process.env.HOST ?? "localhost"}:${process.env.PORT ?? 3000}`, ); const response = await fetch(url.toString(), { @@ -160,17 +145,15 @@ const updateScoresOnChain = async ( const updateScores = async () => { const subgraphResponse = await urqlClient .query<{ passportUsers: PassportUser[] }>( - query, - {}, - { - url: SUBGRAPH, - requestPolicy: "network-only", - }, - ) + query, + {}, + { + url: SUBGRAPH, + requestPolicy: "network-only", + }, + ) .toPromise(); - console.log("subgraphResponse ", subgraphResponse); - console.log("DATA ", subgraphResponse.data); if (!subgraphResponse.data) { throw new Error("Failed to fetch data from subgraph"); } diff --git a/apps/web/app/api/passport-oracle/writeScore/route.ts b/apps/web/app/api/passport-oracle/writeScore/route.ts index 634378c7d..229ea2acf 100644 --- a/apps/web/app/api/passport-oracle/writeScore/route.ts +++ b/apps/web/app/api/passport-oracle/writeScore/route.ts @@ -1,4 +1,4 @@ -// pages/api/addUserScore.ts +// api/passport-oracle/write-score import { NextResponse } from "next/server"; import { @@ -7,50 +7,25 @@ import { createWalletClient, custom, Address, - Chain, } from "viem"; -import { localhost, arbitrumSepolia, sepolia } from "viem/chains"; import { privateKeyToAccount } from "viem/accounts"; +import { getConfigByChain } from "@/constants/contracts"; import { passportScorerABI } from "@/src/generated"; -import { getContractsAddrByChain } from "@/constants/contracts"; import { CV_PERCENTAGE_SCALE } from "@/utils/numbers"; +import { getViemChain } from "@/utils/viem"; const LIST_MANAGER_PRIVATE_KEY = process.env.LIST_MANAGER_PRIVATE_KEY; - -const CHAIN = process.env.CHAIN_ID ? parseInt(process.env.CHAIN_ID) : 1337; +const CHAIN_ID = process.env.CHAIN_ID ? parseInt(process.env.CHAIN_ID) : 1337; const LOCAL_RPC = "http://127.0.0.1:8545"; -const RPC_URL = getContractsAddrByChain(CHAIN)?.rpcUrl || LOCAL_RPC; +const RPC_URL = getConfigByChain(CHAIN_ID)?.rpcUrl ?? LOCAL_RPC; -const CONTRACT_ADDRESS = getContractsAddrByChain(CHAIN) - ?.passportScorer as Address; +const CONTRACT_ADDRESS = getConfigByChain(CHAIN_ID)?.passportScorer as Address; const API_ENDPOINT = "/api/passport"; -function getViemChain(chain: number): Chain { - let viemChain: Chain; - - switch (chain) { - case localhost.id: - viemChain = localhost; - break; - case arbitrumSepolia.id: - viemChain = arbitrumSepolia; - break; - case sepolia.id: - viemChain = sepolia; - break; - - default: - viemChain = localhost; - break; - } - - return viemChain; -} - const client = createPublicClient({ - chain: getViemChain(CHAIN), + chain: getViemChain(CHAIN_ID), transport: http(RPC_URL), }); @@ -58,14 +33,14 @@ const walletClient = createWalletClient({ account: privateKeyToAccount( (`${LIST_MANAGER_PRIVATE_KEY}` as Address) || "", ), - chain: localhost, + chain: getViemChain(CHAIN_ID), transport: custom(client.transport), }); const fetchScoreFromGitcoin = async (user: string) => { const url = new URL( API_ENDPOINT, - `http://${process.env.HOST || "localhost"}:${process.env.PORT || 3000}`, + `http://${process.env.HOST ?? "localhost"}:${process.env.PORT ?? 3000}`, ); const response = await fetch(`${url}/${user}`, { method: "GET", diff --git a/apps/web/app/api/passport/[account]/route.ts b/apps/web/app/api/passport/[account]/route.ts index 27472220d..268d43ce3 100644 --- a/apps/web/app/api/passport/[account]/route.ts +++ b/apps/web/app/api/passport/[account]/route.ts @@ -1,4 +1,5 @@ -// app/api/passport/[account]/route.ts +// api/passport/[account] + import { NextResponse } from "next/server"; interface Params { @@ -25,7 +26,7 @@ export async function GET(request: Request, { params }: Params) { return NextResponse.json({ error: "API key is missing" }, { status: 500 }); } - console.log("Making request to endpoint:", endpoint); + console.info("Making request to endpoint:", endpoint); try { const response = await fetch(endpoint, { @@ -36,15 +37,15 @@ export async function GET(request: Request, { params }: Params) { }, }); - console.log("Response status:", response.status); - console.log("Response status text:", response.statusText); + console.info("Response status:", response.status); + console.info("Response status text:", response.statusText); if (response.ok) { const data = await response.json(); return NextResponse.json(data, { status: 200 }); } else { const errorData = await response.json(); - console.log("Error data:", errorData); + console.info("Error data:", errorData); return NextResponse.json( { error: errorData.message }, { status: response.status }, diff --git a/apps/web/app/api/passport/scores/route.ts b/apps/web/app/api/passport/scores/route.ts index 40c89bae4..b04c96dda 100644 --- a/apps/web/app/api/passport/scores/route.ts +++ b/apps/web/app/api/passport/scores/route.ts @@ -1,4 +1,5 @@ -// app/api/passport/scores/route.ts +// api/passport/scores + import { NextResponse } from "next/server"; export async function GET() { diff --git a/apps/web/app/api/passport/signMessage/route.ts b/apps/web/app/api/passport/signMessage/route.ts index b5e3f4f4d..f83d3b830 100644 --- a/apps/web/app/api/passport/signMessage/route.ts +++ b/apps/web/app/api/passport/signMessage/route.ts @@ -1,4 +1,5 @@ -// app/api/passport/getSigningMessage/route.ts +// api/passport/sign-message + import { NextResponse } from "next/server"; export async function GET() { @@ -19,10 +20,10 @@ export async function GET() { cache: "no-store", }); - console.log("SignMessage response ", response); + console.info("SignMessage response ", response); if (response.ok) { const data = await response.json(); - console.log("DATA ", data); + console.info("DATA ", data); return NextResponse.json(data, { status: 200 }); } else { const errorData = await response.json(); diff --git a/apps/web/app/api/passport/submitPassport/route.ts b/apps/web/app/api/passport/submitPassport/route.ts index 959fe7e25..a1394f851 100644 --- a/apps/web/app/api/passport/submitPassport/route.ts +++ b/apps/web/app/api/passport/submitPassport/route.ts @@ -1,4 +1,5 @@ -// app/api/passport/submit-passport/route.ts +// api/passport/submit-passport + import { NextResponse } from "next/server"; interface PassportData { @@ -8,7 +9,6 @@ interface PassportData { } export async function POST(request: Request) { - console.log("Received POST request"); const apiKey = process.env.GITCOIN_PASSPORT_API_KEY; const scorerId = process.env.SCORER_ID; const endpoint = "https://api.scorer.gitcoin.co/registry/submit-passport"; diff --git a/apps/web/app/favicon.ico b/apps/web/app/favicon.ico new file mode 100644 index 000000000..edbe550c1 Binary files /dev/null and b/apps/web/app/favicon.ico differ diff --git a/apps/web/app/global-error.tsx b/apps/web/app/global-error.tsx new file mode 100644 index 000000000..51275cc6d --- /dev/null +++ b/apps/web/app/global-error.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { useEffect } from "react"; +import * as Sentry from "@sentry/nextjs"; +import NextError from "next/error"; + +export default function GloblError({ + error, +}: { + error: Error & { digest?: string }; +}) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + return ( + + + {/* `NextError` is the default Next.js error page component. Its type + definition requires a `statusCode` prop. However, since the App Router + does not expose status codes for errors, we simply pass 0 to render a + generic error message. */} + + + + ); +} diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index a7a783caf..41eaabaff 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,10 +1,8 @@ import "@/styles/globals.css"; import React from "react"; import { Chakra_Petch, Inter } from "next/font/google"; -import Providers from "@/providers/Providers"; -import { Metadata } from "next"; import { Bounce, ToastContainer } from "react-toastify"; - +import Providers from "@/providers/Providers"; import "react-toastify/dist/ReactToastify.css"; const inter = Inter({ @@ -19,11 +17,6 @@ const chakra = Chakra_Petch({ weight: ["400", "500", "600", "700"], }); -const metadata: Metadata = { - title: "Gardens v2", - // description: "Gardens description...", -}; - export default function RootLayout({ children, }: { diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index faa73516b..ef4d0a9ee 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,6 +1,485 @@ -import React from "react"; -import { redirect } from "next/navigation"; +"use client"; +import { JSX, SVGProps, useState } from "react"; +import { Dialog } from "@headlessui/react"; +import { CheckIcon } from "@heroicons/react/20/solid"; +import { + Bars3Icon, + XMarkIcon, + ArrowPathIcon, + FingerPrintIcon, + LockClosedIcon, + ArrowLongRightIcon, + Battery50Icon, +} from "@heroicons/react/24/outline"; +import Image from "next/image"; +import { newLogo, commF } from "@/assets"; +import { ChainIcon } from "@/configs/chainServer"; -export default function Home() { - redirect("/gardens"); +export default function Page() { + return ( + <> + + + + + +