Skip to content

Commit

Permalink
Merge branch 'main' into fix/103-dynamic-reloading-after-voting
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosgj94 authored Mar 8, 2024
2 parents b82050b + 55eda59 commit 7313cac
Show file tree
Hide file tree
Showing 15 changed files with 181 additions and 127 deletions.
Binary file modified bun.lockb
Binary file not shown.
30 changes: 24 additions & 6 deletions context/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { AlertProvider } from "./AlertContext";
import { ReactNode } from "react";
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { config } from "@/context/Web3Modal";
import { createWeb3Modal } from '@web3modal/wagmi/react'
import { State, WagmiProvider } from 'wagmi'
import { State, WagmiProvider, deserialize, serialize } from 'wagmi'
import { PUB_WALLET_CONNECT_PROJECT_ID } from "@/constants";
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'

const queryClient = new QueryClient();
const queryClient = new QueryClient({
defaultOptions: {
queries: {
gcTime: 1_000 * 60 * 60 * 24, // 24 hours
},
},
});

const persister = createAsyncStoragePersister({
serialize,
storage: AsyncStorage,
deserialize,
})

// Create modal
createWeb3Modal({
Expand All @@ -15,14 +30,17 @@ createWeb3Modal({
enableAnalytics: false // Optional - defaults to your Cloud configuration
})

export function RootContextProvider({ children, initialState }: { children: ReactNode, initialState?: State }) {
export function RootContextProvider({ children, initialState }: { children: ReactNode, initialState?: State }) {
return (
<WagmiProvider config={config} initialState={initialState}>
<QueryClientProvider client={queryClient}>
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
>
<AlertProvider>
{children}
</AlertProvider>
</QueryClientProvider>
</PersistQueryClientProvider>
</WagmiProvider>
);
}
30 changes: 30 additions & 0 deletions hooks/useMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { fetchJsonFromIpfs } from "@/utils/ipfs";
import { useQuery } from "@tanstack/react-query";
import { fromHex } from "viem";

type JsonValue = string | number | boolean;
type JsonObject = JsonValue | Record<string, JsonValue> | Array<JsonValue>;

export function useMetadata<T = JsonObject>(ipfsUri?: string) {
const { data, isLoading, isSuccess, error } = useQuery<T, Error>({
queryKey: [ipfsUri || ""],
queryFn: () => {
if (!ipfsUri || !fromHex(ipfsUri as any, "string")) {
return Promise.resolve("");
}
return fetchJsonFromIpfs(ipfsUri);
},
retry: true,
refetchOnMount: false,
refetchOnReconnect: false,
retryOnMount: true,
staleTime: Infinity,
});

return {
data,
isLoading,
isSuccess,
error,
};
}
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
"dependencies": {
"@aragon/ods": "1.0.15",
"@shazow/whatsabi": "0.11.0",
"@tanstack/react-query": "^5.18.1",
"@tanstack/query-async-storage-persister": "^5.25.0",
"@tanstack/react-query": "^5.25.0",
"@tanstack/react-query-persist-client": "^5.25.0",
"@typescript-eslint/eslint-plugin": "latest",
"@web3modal/wagmi": "^4.0.1",
"dayjs": "^1.11.10",
Expand All @@ -24,9 +26,8 @@
"react": "^18",
"react-blockies": "^1.4.1",
"react-dom": "^18",
"react-query": "^3.39.3",
"tailwindcss-fluid-type": "^2.0.3",
"viem": "^2.7.6",
"viem": "^2.7.22",
"wagmi": "^2.5.6"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion plugins/dualGovernance/components/proposal/description.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function ProposalDescription(proposal: Proposal) {
<p className="pt-2">The proposal has no actions</p>
</If>
{proposal.actions?.map?.((action, i) => (
<div className="mb-3" key={`${i}-${action.to}-${action.data}`}>
<div className="mb-3" key={i}>
<ActionCard action={action} idx={i} />
</div>
))}
Expand Down
42 changes: 21 additions & 21 deletions plugins/dualGovernance/components/proposal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import Link from "next/link";
import { usePublicClient } from "wagmi";
import { useProposal } from "@/plugins/dualGovernance/hooks/useProposal";
import { Card, Tag, TagVariant } from "@aragon/ods";
import * as DOMPurify from 'dompurify';
import * as DOMPurify from "dompurify";
import { PleaseWaitSpinner } from "@/components/please-wait";
import { useProposalVariantStatus } from "../../hooks/useProposalVariantStatus";
import { PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS } from "@/constants";

const DEFAULT_PROPOSAL_METADATA_TITLE = "(No proposal title)";
const DEFAULT_PROPOSAL_METADATA_SUMMARY =
Expand All @@ -16,12 +14,7 @@ type ProposalInputs = {
};

export default function ProposalCard(props: ProposalInputs) {
const publicClient = usePublicClient();
const { proposal, status } = useProposal(
publicClient!,
PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS,
props.proposalId.toString()
);
const { proposal, status } = useProposal(props.proposalId.toString());
const proposalVariant = useProposalVariantStatus(proposal!);

const showLoading = getShowProposalLoading(proposal, status);
Expand All @@ -36,17 +29,24 @@ export default function ProposalCard(props: ProposalInputs) {
</Card>
</section>
);
} else if (!proposal?.title && !proposal?.summary) {
// We have the proposal but no metadata yet
return (
<Link href={`#/proposals/${props.proposalId}`} className="w-full mb-4">
<Card className="p-4">
<span className="px-4 py-5 xs:px-10 md:px-6 lg:px-7">
<PleaseWaitSpinner fullMessage="Loading metadata..." />
</span>
</Card>
</Link>
);
} else if (status.metadataReady && !proposal?.title) {
return (
<Link
href={`/proposals/${props.proposalId}`}
className="w-full mb-4"
>
<Link href={`#/proposals/${props.proposalId}`} className="w-full mb-4">
<Card className="p-4">
<div className="md:w-7/12 lg:w-3/4 xl:4/5 pr-4 text-nowrap text-ellipsis overflow-hidden">
<h4 className="mb-1 text-lg text-neutral-300 line-clamp-1">
{Number(props.proposalId) + 1} -{" "}
{DEFAULT_PROPOSAL_METADATA_TITLE}
{Number(props.proposalId) + 1} - {DEFAULT_PROPOSAL_METADATA_TITLE}
</h4>
<p className="text-base text-neutral-300 line-clamp-3">
{DEFAULT_PROPOSAL_METADATA_SUMMARY}
Expand All @@ -63,12 +63,12 @@ export default function ProposalCard(props: ProposalInputs) {
className="w-full cursor-pointer mb-4"
>
<Card className="p-4">
<div className="flex mb-2">
<Tag
variant={proposalVariant.variant as TagVariant}
label={proposalVariant.label}
/>
</div>
<div className="flex mb-2">
<Tag
variant={proposalVariant.variant as TagVariant}
label={proposalVariant.label}
/>
</div>
<div className="text-ellipsis overflow-hidden">
<h4 className=" mb-1 text-lg font-semibold text-dark line-clamp-1">
{Number(props.proposalId) + 1} - {proposal.title}
Expand Down
48 changes: 21 additions & 27 deletions plugins/dualGovernance/hooks/useProposal.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { useState, useEffect } from "react";
import { Address } from "viem";
import { useBlockNumber, useReadContract } from "wagmi";
import { fetchJsonFromIpfs } from "@/utils/ipfs";
import { PublicClient, getAbiItem } from "viem";
import { useBlockNumber, usePublicClient, useReadContract } from "wagmi";
import { getAbiItem } from "viem";
import { OptimisticTokenVotingPluginAbi } from "@/plugins/dualGovernance/artifacts/OptimisticTokenVotingPlugin.sol";
import { Action } from "@/utils/types";
import {
Proposal,
ProposalMetadata,
ProposalParameters,
} from "@/plugins/dualGovernance/utils/types";
import { useQuery } from "@tanstack/react-query";
import { PUB_CHAIN } from "@/constants";
import { PUB_CHAIN, PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS } from "@/constants";
import { useMetadata } from "@/hooks/useMetadata";

type ProposalCreatedLogResponse = {
args: {
Expand All @@ -30,12 +28,8 @@ const ProposalCreatedEvent = getAbiItem({
name: "ProposalCreated",
});

export function useProposal(
publicClient: PublicClient,
address: Address,
proposalId: string,
autoRefresh = false
) {
export function useProposal(proposalId: string, autoRefresh = false) {
const publicClient = usePublicClient();
const [proposalCreationEvent, setProposalCreationEvent] =
useState<ProposalCreatedLogResponse["args"]>();
const [metadataUri, setMetadata] = useState<string>();
Expand All @@ -47,8 +41,12 @@ export function useProposal(
error: proposalError,
fetchStatus: proposalFetchStatus,
refetch: proposalRefetch,
} = useReadContract<typeof OptimisticTokenVotingPluginAbi, "getProposal", any[]>({
address,
} = useReadContract<
typeof OptimisticTokenVotingPluginAbi,
"getProposal",
any[]
>({
address: PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS,
abi: OptimisticTokenVotingPluginAbi,
functionName: "getProposal",
args: [proposalId],
Expand All @@ -57,15 +55,16 @@ export function useProposal(
const proposalData = decodeProposalResultData(proposalResult as any);

useEffect(() => {
if (autoRefresh) proposalRefetch()
}, [blockNumber])
if (autoRefresh) proposalRefetch();
}, [blockNumber]);

// Creation event
useEffect(() => {
if (!proposalData) return;
if (!proposalData || !publicClient) return;

publicClient
.getLogs({
address,
address: PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS,
event: ProposalCreatedEvent as any,
args: {
proposalId,
Expand All @@ -81,22 +80,17 @@ export function useProposal(
setMetadata(log.args.metadata);
})
.catch((err) => {
console.error("Could not fetch the proposal defailt", err);
console.error("Could not fetch the proposal details", err);
return null;
});
}, [proposalData?.vetoTally]);
}, [proposalData?.vetoTally, !!publicClient]);

// JSON metadata
const {
data: metadataContent,
isLoading: metadataLoading,
isSuccess: metadataReady,
error: metadataError,
} = useQuery<ProposalMetadata, Error>({
queryKey: [`dualGovernanceProposal-${address}-${proposalId}`, metadataUri!],
queryFn: () => metadataUri ? fetchJsonFromIpfs(metadataUri) : Promise.resolve(null),
enabled: !!metadataUri
});
} = useMetadata<ProposalMetadata>(metadataUri);

const proposal = arrangeProposalData(
proposalData,
Expand All @@ -110,7 +104,7 @@ export function useProposal(
proposalReady: proposalFetchStatus === "idle",
proposalLoading: proposalFetchStatus === "fetching",
proposalError,
metadataReady,
metadataReady: !metadataError && !metadataLoading && !!metadataContent,
metadataLoading,
metadataError: metadataError !== undefined,
},
Expand Down
2 changes: 0 additions & 2 deletions plugins/dualGovernance/hooks/useProposalVeto.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ export function useProposalVeto(proposalId: string) {
const publicClient = usePublicClient({ chainId: PUB_CHAIN.id });

const { proposal, status: proposalFetchStatus } = useProposal(
publicClient!,
PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS,
proposalId,
true
);
Expand Down
38 changes: 12 additions & 26 deletions plugins/dualGovernance/pages/proposal-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,18 @@ import { Else, ElseIf, If, Then } from "@/components/if";
import { PleaseWaitSpinner } from "@/components/please-wait";
import { useSkipFirstRender } from "@/hooks/useSkipFirstRender";
import { PUB_DUAL_GOVERNANCE_PLUGIN_ADDRESS, PUB_CHAIN } from "@/constants";
import { digestPagination } from "@/utils/pagination";
import { useWeb3Modal } from "@web3modal/wagmi/react";
import { useRouter } from "next/router";

const PROPOSALS_PER_PAGE = 10;

export default function Proposals() {
const { isConnected } = useAccount();
const { open } = useWeb3Modal();
const { push } = useRouter();
const [proposalCount, setProposalCount] = useState(0);

const { data: blockNumber } = useBlockNumber({ watch: true });
const canCreate = useCanCreateProposal();

const [currentPage, setCurrentPage] = useState(0);
const [paginatedProposals, setPaginatedProposals] = useState<number[]>([]);

useEffect(() => {
const start = currentPage * PROPOSALS_PER_PAGE;
const end = (currentPage + 1) * PROPOSALS_PER_PAGE;
const propIds = new Array(proposalCount).fill(0).map((_, i) => i);
setPaginatedProposals(propIds.slice(start, end));
}, [proposalCount, currentPage]);

const {
data: proposalCountResponse,
Expand All @@ -43,18 +33,19 @@ export default function Proposals() {
chainId: PUB_CHAIN.id,
});

useEffect(() => {
if (!proposalCountResponse) return;
setProposalCount(Number(proposalCountResponse));
}, [proposalCountResponse]);

useEffect(() => {
refetch();
}, [blockNumber]);

const skipRender = useSkipFirstRender();
if (skipRender) return <></>;

const proposalCount = Number(proposalCountResponse);
const { visibleProposalIds, showNext, showPrev } = digestPagination(
proposalCount,
currentPage
);

return (
<MainSection>
<SectionView>
Expand All @@ -73,19 +64,14 @@ export default function Proposals() {
</SectionView>
<If condition={proposalCount}>
<Then>
{paginatedProposals.map((_, i) => (
<ProposalCard
key={i}
proposalId={BigInt(
proposalCount! - 1 - currentPage * PROPOSALS_PER_PAGE - i
)}
/>
{visibleProposalIds.map((id) => (
<ProposalCard key={id} proposalId={BigInt(id)} />
))}
<div className="w-full flex flex-row justify-end gap-2 mt-4 mb-10">
<Button
variant="tertiary"
size="sm"
disabled={!currentPage}
disabled={!showPrev}
onClick={() => setCurrentPage((page) => Math.max(page - 1, 0))}
iconLeft={IconType.CHEVRON_LEFT}
>
Expand All @@ -94,7 +80,7 @@ export default function Proposals() {
<Button
variant="tertiary"
size="sm"
disabled={(currentPage + 1) * PROPOSALS_PER_PAGE >= proposalCount}
disabled={!showNext}
onClick={() => setCurrentPage((page) => page + 1)}
iconRight={IconType.CHEVRON_RIGHT}
>
Expand Down
Loading

0 comments on commit 7313cac

Please sign in to comment.