Skip to content

Commit

Permalink
Improvements to #26 - replace context for explicit props, remove dupl…
Browse files Browse the repository at this point in the history
…icate state, fix creation of a new proposal (#27)

* Better naming around provider updating/namely: `addAction` and `replaceProposal`
* Replace ProviderContext with explicit prop passing; remove duplicate `proposal` state and just have query string be source of truth
* Fix some type failing issues
* When there is no current proposal, and a user is to be added/removed from the safe, redirect to a `new` url so that the url can be properly shared
  • Loading branch information
oveddan authored Jun 21, 2024
1 parent 938bde8 commit f5dada2
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 92 deletions.
89 changes: 25 additions & 64 deletions src/app/NewSafeProposal.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,26 @@
import { Field, FieldArray, Formik } from "formik";
import { SafeInformation } from "../components/SafeInformation";
import { Card, View, Text, Button, useToast } from "reshaped";
import {
SyntheticEvent,
createContext,
useCallback,
useEffect,
useState,
} from "react";
import { SyntheticEvent, useCallback, useEffect, useState } from "react";
import { Address, Hex, formatEther } from "viem";
import { validateAddress, validateETH } from "../utils/validators";
import { GenericField } from "../components/GenericField";
import { DataActionPreview } from "../components/DataActionPreview";
import Safe, { EthersAdapter } from "@safe-global/protocol-kit";
import { ethers } from "ethers";
import { contractNetworks } from "../chains";
import Safe from "@safe-global/protocol-kit";
import {
DEFAULT_ACTION_ITEM,
DEFAULT_PROPOSAL,
Proposal,
proposalSchema,
} from "../schemas/proposal";
import { useRedirectToProposalWithNewParams } from "../hooks/useSetParamsFromQuery";
import { useLoadProposalFromQuery } from "../hooks/useLoadProposalFromQuery";
import {
transformValuesFromWei,
transformValuesToWei,
} from "../utils/etherFormatting";
import { BrowserProvider } from "ethers";
import { useOutletContext } from "react-router-dom";
import { NetworkContext, SafeContext } from "../components/Contexts";
import { useUpdateProposalInQuery } from "../hooks/useUpdateProposalViaQuery";

const FormActionItem = ({
name,
Expand Down Expand Up @@ -62,24 +53,6 @@ const FormActionItem = ({
);
};

const createSafeAdapter = async ({
provider,
safeAddress,
}: {
provider: BrowserProvider;
safeAddress: Address;
}) => {
const ethAdapter = new EthersAdapter({
ethers,
signerOrProvider: await provider!.getSigner(),
});
return await Safe.create({
ethAdapter,
safeAddress,
contractNetworks,
});
};

const createSafeTransaction = async ({
proposal,
safe,
Expand Down Expand Up @@ -335,20 +308,19 @@ const ViewProposal = ({

const EditProposal = ({
proposal,
setProposal,
}: {
proposal: Proposal | undefined;
setProposal: (result: Proposal) => void;
setIsEditing: (editing: boolean) => void;
setProposal: (proposal: Proposal) => void;
}) => {
const redirectToEditedProposal = useRedirectToProposalWithNewParams();
const onSubmit = useCallback(
(result: Proposal) => {
const proposal = transformValuesToWei(result);
if (proposal) {
redirectToEditedProposal(proposal);
setProposal(proposal);
}
},
[redirectToEditedProposal]
[setProposal]
);

const defaultActions = proposal || DEFAULT_PROPOSAL;
Expand Down Expand Up @@ -410,50 +382,39 @@ const EditProposal = ({
);
};

export const ProposalContext = createContext<Proposal | undefined>(undefined);

export const NewSafeProposal = () => {
const [proposal, setProposal] = useState<undefined | Proposal>(
DEFAULT_PROPOSAL,
);
const [isEditing, setIsEditing] = useState(true);

const proposalFromQuery = useLoadProposalFromQuery();
const proposal = useLoadProposalFromQuery();
const { addAction, replace } = useUpdateProposalInQuery({ proposal });

useEffect(() => {
if (proposalFromQuery) {
setProposal(proposalFromQuery);
if (proposal) {
setIsEditing(false);
}
}, [proposalFromQuery]);
}, [proposal]);

const handleEditClicked = useCallback(
(evt: SyntheticEvent) => {
setIsEditing(true);
evt.preventDefault();
},
[setIsEditing],
[setIsEditing]
);

return (
<ProposalContext.Provider value={proposal}>
<View paddingTop={4} paddingBottom={8} gap={8}>
<SafeInformation>
{isEditing && (
<EditProposal
proposal={proposal}
setProposal={setProposal}
setIsEditing={setIsEditing}
/>
)}
{!isEditing && proposal && (
<ViewProposal
proposal={proposal}
handleEditClicked={handleEditClicked}
/>
)}
</SafeInformation>
</View>
</ProposalContext.Provider>
<View paddingTop={4} paddingBottom={8} gap={8}>
<SafeInformation addAction={addAction}>
{isEditing && (
<EditProposal proposal={proposal} setProposal={replace} />
)}
{!isEditing && proposal && (
<ViewProposal
proposal={proposal}
handleEditClicked={handleEditClicked}
/>
)}
</SafeInformation>
</View>
);
};
5 changes: 4 additions & 1 deletion src/app/SafeInformationPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Button, View } from "reshaped";
import { SafeInformation } from "../components/SafeInformation";
import { useNavigate, useParams } from "react-router-dom";
import { useUpdateProposalInQuery } from "../hooks/useUpdateProposalViaQuery";

export const SafeInformationPage = () => {
const { networkId, safeAddress } = useParams();
Expand All @@ -10,9 +11,11 @@ export const SafeInformationPage = () => {
navigate(`/safe/${networkId}/${safeAddress}/new`);
};

const { addAction } = useUpdateProposalInQuery({ proposal: undefined });

return (
<View gap={4}>
<SafeInformation />
<SafeInformation addAction={addAction} />
<Button onClick={onNewProposalClick}>New Proposal</Button>
</View>
);
Expand Down
4 changes: 4 additions & 0 deletions src/components/SafeInformation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AddressView } from "../components/AddressView";
import { OwnerAction, SetOwnerModal } from "../components/SetOwnerModal";
import { useOutletContext } from "react-router-dom";
import { SafeContext } from "./Contexts";
import { AddAction } from "../hooks/useUpdateProposalViaQuery";

const SafeInformationItem = ({
title,
Expand All @@ -27,8 +28,10 @@ const SafeInformationItem = ({

export const SafeInformation = ({
children,
addAction,
}: {
children?: React.ReactNode;
addAction: AddAction;
}) => {
const [ownerAction, setOwnerAction] = useState<OwnerAction>();

Expand All @@ -42,6 +45,7 @@ export const SafeInformation = ({
setOwnerAction(undefined);
}}
action={ownerAction}
addAction={addAction}
/>
)}
<Card>
Expand Down
36 changes: 24 additions & 12 deletions src/components/SetOwnerModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SyntheticEvent, useContext } from "react";
import { SyntheticEvent } from "react";
import { Button, Modal, Text, View, useToast } from "reshaped";
import { AddressView } from "./AddressView";
import { Address } from "viem";
Expand All @@ -8,8 +8,7 @@ import { yupAddress } from "../utils/validators";
import { number, object } from "yup";
import { useOutletContext } from "react-router-dom";
import { SafeContext } from "./Contexts";
import { ProposalContext } from "../app/NewSafeProposal";
import { useUpdateProposalViaQuery } from "../hooks/useUpdateProposalViaQuery";
import { AddAction } from "../hooks/useUpdateProposalViaQuery";

export type OwnerAction =
| undefined
Expand Down Expand Up @@ -42,11 +41,15 @@ const ButtonPanel = ({
</View>
);

const AddOwnerModalContent = ({ onClose }: { onClose: () => void }) => {
const AddOwnerModalContent = ({
onClose,
addAction,
}: {
onClose: () => void;
addAction: AddAction;
}) => {
const { safeInformation } = useOutletContext<SafeContext>();
const toast = useToast();
const updateProposalQuery = useUpdateProposalViaQuery();
const currentProposal = useContext(ProposalContext);

return (
<Formik
Expand All @@ -62,7 +65,7 @@ const AddOwnerModalContent = ({ onClose }: { onClose: () => void }) => {
ownerAddress: address,
threshold: threshold,
});
updateProposalQuery({
addAction({
data: addOwnerTx.data.data,
value: "0",
to: safeInformation.address,
Expand Down Expand Up @@ -96,12 +99,13 @@ const AddOwnerModalContent = ({ onClose }: { onClose: () => void }) => {
const RemoveOwnerModalContent = ({
onClose,
target,
addAction,
}: {
onClose: () => void;
target: string;
addAction: AddAction;
}) => {
const { safeInformation } = useOutletContext<SafeContext>();
const updateProposalViaQuery = useUpdateProposalViaQuery();
const toaster = useToast();

const onSubmitClick = async ({ threshold }: any) => {
Expand All @@ -110,13 +114,13 @@ const RemoveOwnerModalContent = ({
{
ownerAddress: target,
threshold: threshold,
},
}
);
if (!removeOwnerTx || !safeInformation) {
return;
}

updateProposalViaQuery({
addAction({
data: removeOwnerTx.data.data,
value: "0",
to: safeInformation.address,
Expand Down Expand Up @@ -158,16 +162,24 @@ const RemoveOwnerModalContent = ({
export const SetOwnerModal = ({
action,
onClose,
addAction,
}: {
action: OwnerAction;
onClose: () => void;
addAction: AddAction;
}) => {
return (
<Modal active={!!action} onClose={onClose}>
{action?.type === "remove" && (
<RemoveOwnerModalContent onClose={onClose} target={action.address} />
<RemoveOwnerModalContent
onClose={onClose}
target={action.address}
addAction={addAction}
/>
)}
{action?.type === "add" && (
<AddOwnerModalContent onClose={onClose} addAction={addAction} />
)}
{action?.type === "add" && <AddOwnerModalContent onClose={onClose} />}
</Modal>
);
};
7 changes: 6 additions & 1 deletion src/hooks/useLoadProposalFromQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ export const useLoadProposalFromQuery = () => {

console.log({ actions, txt: "setting proposal" });

setProposal({ actions, ...(nonce ? { [queryKeys.nonce]: nonce } : {}) });
const proposal: Proposal = {
actions,
...(nonce ? { nonce: parseInt(nonce) } : {}),
};

setProposal(proposal);
}
}, [params, setProposal]);

Expand Down
40 changes: 32 additions & 8 deletions src/hooks/useSetParamsFromQuery.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
import { useCallback } from "react";
import { useSearchParams } from "react-router-dom";
import {
useLocation,
useNavigate,
useParams,
useSearchParams,
} from "react-router-dom";
import { Proposal } from "../schemas/proposal";
import { queryKeys } from "./useLoadProposalFromQuery";

export const useRedirectToProposalWithNewParams = () => {
const [_, setParams] = useSearchParams();
const [, setParams] = useSearchParams();
const { networkId, safeAddress } = useParams();
const navigate = useNavigate();
const { pathname } = useLocation();

return useCallback(
(proposal: Proposal) => {
if (!proposal.actions?.length) {
return;
}
setParams({
[queryKeys.targets]: proposal.actions!.map((action) => action.to).join("|"),
[queryKeys.calldatas]: proposal.actions!.map((action) => action.data).join("|"),
[queryKeys.values]: proposal.actions!.map((action) => action.value).join("|"),
const params = {
[queryKeys.targets]: proposal
.actions!.map((action) => action.to)
.join("|"),
[queryKeys.calldatas]: proposal
.actions!.map((action) => action.data)
.join("|"),
[queryKeys.values]: proposal
.actions!.map((action) => action.value)
.join("|"),
...(proposal.nonce
? {
[queryKeys.nonce]: proposal.nonce.toString(),
}
: {}),
});
};
// if we are not in a new safe proposal, we need to redirect to new safe proposal
// so that it shows up in the url, and renders correctly
if (!pathname.includes("new")) {
const newPath = `/safe/${networkId}/${safeAddress}/new`;

navigate(newPath, {
replace: true,
});
}
setParams(params);
},
[setParams],
[setParams, networkId, safeAddress, pathname, navigate]
);
};
Loading

0 comments on commit f5dada2

Please sign in to comment.