From 7152d848bb0ace2c9b0e202e72d419f5ab53a54a Mon Sep 17 00:00:00 2001 From: skuhlmann Date: Fri, 8 Sep 2023 15:24:28 -0600 Subject: [PATCH 1/2] all new proposals --- src/Routes.tsx | 2 + src/components/NewProposalList.tsx | 106 +++++++++++++++++++++++++++++ src/legos/fieldConfig.ts | 12 ---- src/legos/fields.ts | 26 +------ src/legos/forms.ts | 15 ++-- src/legos/legoConfig.ts | 38 +++++++++++ src/legos/tx.ts | 5 +- src/pages/FormTest.tsx | 2 +- src/pages/NewProposal.tsx | 63 +++++++++++++++++ src/pages/Proposals.tsx | 37 +++++++++- src/utils/formHelpers.ts | 22 ++++++ 11 files changed, 280 insertions(+), 48 deletions(-) create mode 100644 src/components/NewProposalList.tsx delete mode 100644 src/legos/fieldConfig.ts create mode 100644 src/legos/legoConfig.ts create mode 100644 src/pages/NewProposal.tsx create mode 100644 src/utils/formHelpers.ts diff --git a/src/Routes.tsx b/src/Routes.tsx index c7f4105..ea58bf8 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -22,6 +22,7 @@ import RageQuit from "./pages/RageQuit"; import { MULTI_DAO_ROUTER } from "@daohaus/moloch-v3-hooks"; import { HomeContainer } from "./components/layout/HomeContainer"; import { DaoContainer } from "./components/layout/DaoContainer"; +import NewProposal from "./pages/NewProposal"; export const Routes = ({ setDaoChainId, @@ -55,6 +56,7 @@ export const Routes = ({ } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/NewProposalList.tsx b/src/components/NewProposalList.tsx new file mode 100644 index 0000000..05fed8c --- /dev/null +++ b/src/components/NewProposalList.tsx @@ -0,0 +1,106 @@ +import { Link as RouterLink } from "react-router-dom"; +import { RiArrowRightSLine } from "react-icons/ri/index.js"; +import styled from "styled-components"; + +import { Bold, DataSm, ParMd, Tabs } from "@daohaus/ui"; + +import { CustomFormLego } from "../legos/legoConfig"; +import { useCurrentDao } from "@daohaus/moloch-v3-hooks"; + +const ListContainer = styled.div` + margin-top: 2.5rem; +`; + +const ListItemContainer = styled.div` + width: 100%; + padding: 1rem 0; + border-top: 1px ${({ theme }) => theme.secondary.step6} solid; +`; + +const ListItemLink = styled(RouterLink)` + text-decoration: none; + width: 100%; + color: unset; + &:hover { + text-decoration: none; + } +`; + +const ListItemHoverContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 1rem; + border-radius: ${({ theme }) => theme.card.radius}; + + &:hover { + background: 1px ${({ theme }) => theme.secondary.step3}; + } +`; + +const ListItem = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + word-wrap: break-word; + max-width: 39rem; +`; + +const StyledIcon = styled(RiArrowRightSLine)` + fill: ${({ theme }) => theme.primary.step9}; + font-size: 3rem; +`; + +type NewProposalListProps = { + basicProposals: CustomFormLego[]; + advancedProposals: CustomFormLego[]; +}; + +const ProposalList = ({ proposals }: { proposals: CustomFormLego[] }) => { + const { daoChain, daoId } = useCurrentDao(); + + return ( +
+ {proposals.map((proposalLego: CustomFormLego) => ( + + + + + + {proposalLego.title} + + {proposalLego.description} + + + + + + ))} +
+ ); +}; + +export const NewProposalList = ({ + basicProposals, + advancedProposals, +}: NewProposalListProps) => { + return ( + + , + }, + { + label: "Advanced", + Component: () => , + }, + ]} + /> + + ); +}; diff --git a/src/legos/fieldConfig.ts b/src/legos/fieldConfig.ts deleted file mode 100644 index bdde22f..0000000 --- a/src/legos/fieldConfig.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { CoreFieldLookup } from "@daohaus/form-builder"; -import { MolochFields } from "@daohaus/moloch-v3-fields"; -import { FieldLegoBase, FormLegoBase } from "@daohaus/utils"; -import { TestField } from "../components/customFields/fieldTest"; - -export const AppFieldLookup = { - ...MolochFields, - testField: TestField, -}; - -export type CustomFieldLego = FieldLegoBase; -export type CustomFormLego = FormLegoBase; diff --git a/src/legos/fields.ts b/src/legos/fields.ts index 0491208..d110bcb 100644 --- a/src/legos/fields.ts +++ b/src/legos/fields.ts @@ -1,30 +1,10 @@ -import { FieldLego } from "@daohaus/form-builder"; -import { CustomFieldLego } from "./fieldConfig"; +import { CustomFieldLego } from "./legoConfig"; export const APP_FIELD: Record = { - TITLE: { - id: "title", - type: "input", - label: "Proposal Title", - placeholder: "Enter title", - }, - DESCRIPTION: { - id: "description", - type: "textarea", - label: "Description", - placeholder: "Enter description", - }, - LINK: { - id: "link", - type: "input", - label: "Link", - placeholder: "http://", - expectType: "url", - }, TEST_FIELD: { id: "testField", type: "testField", - label: "Test Field", - placeholder: "Enter something", + label: "Enter Something Super", + placeholder: "Super Duper", }, }; diff --git a/src/legos/forms.ts b/src/legos/forms.ts index dd63cbe..ea1e234 100644 --- a/src/legos/forms.ts +++ b/src/legos/forms.ts @@ -1,20 +1,19 @@ -import { FormLego } from "@daohaus/form-builder"; import { FIELD } from "@daohaus/moloch-v3-legos"; -import { CustomFormLego } from "./fieldConfig"; +import { CustomFormLego } from "./legoConfig"; import { APP_FIELD } from "./fields"; import { APP_TX } from "./tx"; const PROPOSAL_SETTINGS_FIELDS = [FIELD.PROPOSAL_EXPIRY, FIELD.PROP_OFFERING]; export const APP_FORM: Record = { - SIGNAL: { - id: "SIGNAL", - title: "Signal Form", - subtitle: "Signal Proposal", + TEST_FORM: { + id: "TEST_FORM", + title: "Super Signal Form", + subtitle: "Super Signal Proposal", description: "Ratify on-chain using a DAO proposal.", - requiredFields: { title: true, description: true }, + requiredFields: { title: true, description: true, testField: true }, log: true, - tx: APP_TX.POST_SIGNAL, + tx: APP_TX.TEST_TX, fields: [ FIELD.TITLE, FIELD.DESCRIPTION, diff --git a/src/legos/legoConfig.ts b/src/legos/legoConfig.ts new file mode 100644 index 0000000..41f3440 --- /dev/null +++ b/src/legos/legoConfig.ts @@ -0,0 +1,38 @@ +import { MolochFields } from "@daohaus/moloch-v3-fields"; +import { FieldLegoBase, FormLegoBase } from "@daohaus/utils"; +import { COMMON_FORMS, PROPOSAL_FORMS } from "@daohaus/moloch-v3-legos"; + +import { APP_FORM } from "./forms"; +import { TestField } from "../components/customFields/fieldTest"; + +export const AppFieldLookup = { + ...MolochFields, + testField: TestField, +}; + +export type CustomFieldLego = FieldLegoBase; +export type CustomFormLego = FormLegoBase; + +export const BASIC_PROPOSAL_FORMS = { + TEST_FORM: APP_FORM.TEST_FORM, + SIGNAL: PROPOSAL_FORMS.SIGNAL, + ISSUE: PROPOSAL_FORMS.ISSUE, + TOKENS_FOR_SHARES: PROPOSAL_FORMS.TOKENS_FOR_SHARES, + TRANSFER_ERC20: PROPOSAL_FORMS.TRANSFER_ERC20, + TRANSFER_NETWORK_TOKEN: PROPOSAL_FORMS.TRANSFER_NETWORK_TOKEN, +}; + +export const ADVANCED_PROPOSAL_FORMS = { + WALLETCONNECT: PROPOSAL_FORMS.WALLETCONNECT, + UPDATE_GOV_SETTINGS: PROPOSAL_FORMS.UPDATE_GOV_SETTINGS, + TOKEN_SETTINGS: PROPOSAL_FORMS.TOKEN_SETTINGS, + ADD_SHAMAN: PROPOSAL_FORMS.ADD_SHAMAN, + GUILDKICK: PROPOSAL_FORMS.GUILDKICK, + MULTICALL_BUILDER: PROPOSAL_FORMS.MULTICALL_BUILDER, +}; + +export const ALL_APP_FORMS = { + ...APP_FORM, + ...PROPOSAL_FORMS, + ...COMMON_FORMS, +}; diff --git a/src/legos/tx.ts b/src/legos/tx.ts index 5e660b3..ffaa872 100644 --- a/src/legos/tx.ts +++ b/src/legos/tx.ts @@ -16,8 +16,8 @@ export enum ProposalTypeIds { } export const APP_TX = { - POST_SIGNAL: buildMultiCallTX({ - id: "POST_SIGNAL", + TEST_TX: buildMultiCallTX({ + id: "TEST_TX", JSONDetails: { type: "JSONDetails", jsonSchema: { @@ -38,6 +38,7 @@ export const APP_TX = { jsonSchema: { title: `.formValues.title`, description: `.formValues.description`, + superSignal: `.formValues.testField`, contentURI: `.formValues.link`, contentURIType: { type: "static", value: "url" }, proposalType: { type: "static", value: ProposalTypeIds.Signal }, diff --git a/src/pages/FormTest.tsx b/src/pages/FormTest.tsx index 5b7637f..7b2b476 100644 --- a/src/pages/FormTest.tsx +++ b/src/pages/FormTest.tsx @@ -2,7 +2,7 @@ import { FormBuilder } from "@daohaus/form-builder"; import { MolochFields } from "@daohaus/moloch-v3-fields"; import { APP_FORM } from "../legos/forms"; -import { AppFieldLookup } from "../legos/fieldConfig"; +import { AppFieldLookup } from "../legos/legoConfig"; import { useCurrentDao } from "@daohaus/moloch-v3-hooks"; export const FormTest = () => { diff --git a/src/pages/NewProposal.tsx b/src/pages/NewProposal.tsx new file mode 100644 index 0000000..1da9673 --- /dev/null +++ b/src/pages/NewProposal.tsx @@ -0,0 +1,63 @@ +import { useMemo } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; + +import { FormBuilder } from "@daohaus/form-builder"; +import { + useCurrentDao, + useDaoData, + useDaoProposals, +} from "@daohaus/moloch-v3-hooks"; + +import { ALL_APP_FORMS, AppFieldLookup } from "../legos/legoConfig"; +import { getFormLegoByIdApp } from "../utils/formHelpers"; + +export function NewProposal() { + const location = useLocation(); + const navigate = useNavigate(); + const { daoChain, daoId } = useCurrentDao(); + const { refetch } = useDaoData(); + const { refetch: refetchProposals } = useDaoProposals(); + + const onFormComplete = () => { + refetch?.(); + refetchProposals?.(); + navigate(`/molochV3/${daoChain}/${daoId}/proposals`); + }; + + const formLego = useMemo(() => { + const params = new URLSearchParams(location.search); + const legoId = params.get("formLego"); + + if (!legoId) return null; + return getFormLegoByIdApp(legoId, ALL_APP_FORMS); + }, [location]); + + const defaults = useMemo(() => { + if (formLego) { + const params = new URLSearchParams(location.search); + const defaultValues = params.get("defaultValues"); + + if (!defaultValues) return null; + return JSON.parse(defaultValues); + } + return null; + }, [location, formLego]); + + if (!formLego) return null; + + return ( + { + onFormComplete(); + }, + }} + targetNetwork={daoChain} + /> + ); +} + +export default NewProposal; diff --git a/src/pages/Proposals.tsx b/src/pages/Proposals.tsx index 083702d..303ac5f 100644 --- a/src/pages/Proposals.tsx +++ b/src/pages/Proposals.tsx @@ -1,10 +1,43 @@ +import { BsPlusLg } from "react-icons/bs"; + import { ProposalList } from "@daohaus/moloch-v3-macro-ui"; -import { SingleColumnLayout } from "@daohaus/ui"; +import { + Button, + Dialog, + DialogContent, + DialogTrigger, + SingleColumnLayout, +} from "@daohaus/ui"; +import { NewProposalList } from "../components/NewProposalList"; +import { prepareProposals } from "../utils/formHelpers"; +import { + BASIC_PROPOSAL_FORMS, + ADVANCED_PROPOSAL_FORMS, +} from "../legos/legoConfig"; export const Proposals = () => { + const basicProposals = prepareProposals(BASIC_PROPOSAL_FORMS); + const advancedProposals = prepareProposals(ADVANCED_PROPOSAL_FORMS); + return ( - + + + + + + + + + } + /> ); }; diff --git a/src/utils/formHelpers.ts b/src/utils/formHelpers.ts new file mode 100644 index 0000000..e4bc491 --- /dev/null +++ b/src/utils/formHelpers.ts @@ -0,0 +1,22 @@ +import { PROPOSAL_FORMS } from "@daohaus/moloch-v3-legos"; + +import { CustomFormLego } from "../legos/legoConfig"; +import { APP_FORM } from "../legos/forms"; +import { MolochFormLego } from "@daohaus/moloch-v3-fields"; + +export const getFormLegoByIdApp = ( + id: CustomFormLego["id"], + forms: { + [x: string]: CustomFormLego | MolochFormLego; + } +): CustomFormLego | undefined => { + const formKey = Object.keys(forms).find((key: string) => { + return forms[key].id === id; + }); + if (!formKey) return; + return forms[formKey]; +}; + +export const prepareProposals = (proposals: Record) => { + return Object.keys(proposals).map((key) => proposals[key]); +}; From 838e72f7ef43dcbdfd92b55d89b8f840aa7b31b4 Mon Sep 17 00:00:00 2001 From: skuhlmann Date: Fri, 8 Sep 2023 15:48:43 -0600 Subject: [PATCH 2/2] gets ragequite working and makes forms more configurable' --- src/Routes.tsx | 2 + src/components/ButtonRouterLink.tsx | 42 +++++++++++++++ src/components/layout/DaoContainer.tsx | 28 +++++++--- src/pages/Member.tsx | 13 ++--- src/pages/Members.tsx | 7 ++- src/pages/RageQuit.tsx | 34 +++++++----- src/pages/Settings.tsx | 6 ++- src/pages/UpdateSettings.tsx | 71 ++++++++++++++++++++++++++ 8 files changed, 173 insertions(+), 30 deletions(-) create mode 100644 src/components/ButtonRouterLink.tsx create mode 100644 src/pages/UpdateSettings.tsx diff --git a/src/Routes.tsx b/src/Routes.tsx index ea58bf8..2c16cbc 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -23,6 +23,7 @@ import { MULTI_DAO_ROUTER } from "@daohaus/moloch-v3-hooks"; import { HomeContainer } from "./components/layout/HomeContainer"; import { DaoContainer } from "./components/layout/DaoContainer"; import NewProposal from "./pages/NewProposal"; +import UpdateSettings from "./pages/UpdateSettings"; export const Routes = ({ setDaoChainId, @@ -54,6 +55,7 @@ export const Routes = ({ } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/ButtonRouterLink.tsx b/src/components/ButtonRouterLink.tsx new file mode 100644 index 0000000..d86bb23 --- /dev/null +++ b/src/components/ButtonRouterLink.tsx @@ -0,0 +1,42 @@ +import React, { ComponentProps } from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import styled from 'styled-components'; +import { Button } from '@daohaus/ui'; + +type ProfileLinkProps = { + href?: string; + to: string; + selected?: boolean; + disabled?: boolean; + linkType?: 'internal' | 'external' | 'no-icon-external'; + hideIcon?: boolean; + target?: string; + rel?: string; +} & Partial>; + +const StyledRouterLink = styled(RouterLink)` + text-decoration: none; + color: unset; + &:hover { + text-decoration: none; + } +`; + +export const ButtonRouterLink = ({ + to, + target, + disabled, + children, + linkType, + hideIcon, + rel, + ...buttonProps +}: ProfileLinkProps) => { + return ( + + + + ); +}; diff --git a/src/components/layout/DaoContainer.tsx b/src/components/layout/DaoContainer.tsx index 7e3806b..c2af893 100644 --- a/src/components/layout/DaoContainer.tsx +++ b/src/components/layout/DaoContainer.tsx @@ -5,6 +5,7 @@ import { TXBuilder } from "@daohaus/tx-builder"; import { H4 } from "@daohaus/ui"; import { ValidNetwork } from "@daohaus/keychain-utils"; import { CurrentDaoProvider, useDaoData } from "@daohaus/moloch-v3-hooks"; +import { useMemo } from "react"; export const DaoContainer = () => { const location = useLocation(); @@ -19,17 +20,28 @@ export const DaoContainer = () => { const routePath = `molochv3/${daoChain}/${daoId}`; + const navLinks = useMemo(() => { + let baseLinks = [ + { label: "Claim", href: `/${routePath}/claim` }, + { label: "DAO", href: `/${routePath}` }, + { label: "Safes", href: `/${routePath}/safes` }, + { label: "Proposals", href: `/${routePath}/proposals` }, + { label: "Members", href: `/${routePath}/members` }, + { label: "Settings", href: `/${routePath}/settings` }, + ]; + + return address + ? [ + ...baseLinks, + { label: "Profile", href: `/${routePath}/member/${address}` }, + ] + : baseLinks; + }, [daoChain, daoId, address]); + return ( {dao?.name}} > { {member && ( <> - {/* MEMBERS - */} + @@ -85,6 +80,8 @@ export const Member = () => { daoChain={daoChain} daoId={daoId} member={member} + allowLinks={true} + allowMemberMenu={true} /> )} diff --git a/src/pages/Members.tsx b/src/pages/Members.tsx index aa13135..00a576d 100644 --- a/src/pages/Members.tsx +++ b/src/pages/Members.tsx @@ -16,7 +16,12 @@ export const Members = () => { {!daoChain || !daoId ? ( ) : ( - + )} ); diff --git a/src/pages/RageQuit.tsx b/src/pages/RageQuit.tsx index d5c8d9b..49c0596 100644 --- a/src/pages/RageQuit.tsx +++ b/src/pages/RageQuit.tsx @@ -1,25 +1,33 @@ import { useMemo } from "react"; -import { useParams } from "react-router-dom"; import { FormBuilder } from "@daohaus/form-builder"; import { NETWORK_TOKEN_ETH_ADDRESS, TokenBalance } from "@daohaus/utils"; -import { sortTokensForRageQuit } from "@daohaus/moloch-v3-fields"; import { COMMON_FORMS } from "@daohaus/moloch-v3-legos"; -import { MolochFields } from "@daohaus/moloch-v3-fields"; -import { useConnectedMember, useDaoData } from "@daohaus/moloch-v3-hooks"; +import { sortTokensForRageQuit } from "@daohaus/moloch-v3-fields"; + +import { AppFieldLookup } from "../legos/legoConfig"; +import { + useCurrentDao, + useDaoData, + useDaoMember, + useDaoMembers, +} from "@daohaus/moloch-v3-hooks"; import { useDHConnect } from "@daohaus/connect"; export function RageQuit() { - const { daoid, daochain } = useParams(); const { dao, refetch } = useDaoData(); - + const { daoId, daoChain } = useCurrentDao(); const { address } = useDHConnect(); - const { connectedMember } = useConnectedMember({ - daoChain: daochain as string, - daoId: daoid as string, - memberAddress: address as string, + const { member: connectedMember, refetch: refetchMember } = useDaoMember({ + memberAddress: address, + // @ts-expect-error: need to fix in hooks package + daoId, + // @ts-expect-error: need to fix in hooks package + daoChain, }); + const { refetch: refetchMembers } = useDaoMembers(); + const defaultFields = useMemo(() => { if (connectedMember && dao) { const treasury = dao.vaults.find( @@ -44,6 +52,8 @@ export function RageQuit() { const onFormComplete = () => { refetch?.(); + refetchMember?.(); + refetchMembers?.(); }; if (!dao || !connectedMember) { @@ -54,13 +64,13 @@ export function RageQuit() { { onFormComplete(); }, }} - targetNetwork={daochain} + targetNetwork={daoChain} /> ); } diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index dce1616..2538a66 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -11,7 +11,11 @@ export const Settings = () => { return ( {dao && ( - + )} ); diff --git a/src/pages/UpdateSettings.tsx b/src/pages/UpdateSettings.tsx new file mode 100644 index 0000000..86959e2 --- /dev/null +++ b/src/pages/UpdateSettings.tsx @@ -0,0 +1,71 @@ +import { useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { FormBuilder } from '@daohaus/form-builder'; +import { COMMON_FORMS } from '@daohaus/moloch-v3-legos'; +import { MolochV3Dao } from '@daohaus/moloch-v3-data'; +import { useCurrentDao, useDaoData } from '@daohaus/moloch-v3-hooks'; + +import { AppFieldLookup } from '../legos/legoConfig'; + +export const formatDaoProfileForForm = (dao: MolochV3Dao) => { + const links = dao?.links || []; + + return { + name: dao?.name, + icon: dao?.avatarImg, + tags: dao?.tags?.join(', '), + description: dao?.description, + long_description: dao?.longDescription, + discord: links.find((link) => link.label === 'Discord')?.url, + github: links.find((link) => link.label === 'Github')?.url, + telegram: links.find((link) => link.label === 'Telegram')?.url, + twitter: links.find((link) => link.label === 'Twitter')?.url, + blog: links.find((link) => link.label === 'Blog')?.url, + web: links.find((link) => link.label === 'Web')?.url, + custom1: links[6]?.url, + custom1Label: links[6]?.label, + custom2: links[7]?.url, + custom2Label: links[7]?.label, + custom3: links[8]?.url, + custom3Label: links[8]?.label, + }; +}; + +export function UpdateSettings() { + const { dao, refetch } = useDaoData(); + const { daoId, daoChain } = useCurrentDao(); + const navigate = useNavigate(); + + const defaultFields = useMemo(() => { + if (dao) { + return formatDaoProfileForForm(dao); + } + return undefined; + }, [dao]); + + const onFormComplete = () => { + refetch?.(); + navigate(`/molochV3/${daoChain}/${daoId}/settings`); + }; + + if (!dao) { + return null; + } + + return ( + { + onFormComplete(); + }, + }} + /> + ); +} + +export default UpdateSettings;