From b8021f35083146c4312e5595eba65b56840a6492 Mon Sep 17 00:00:00 2001 From: roienatan <34843014+roienatan@users.noreply.github.com> Date: Wed, 9 Dec 2020 12:09:11 +0200 Subject: [PATCH 01/33] Brand new Proposals Page (#2301) * init * init continue * init more * add getNetworkByDAO function * add getArcByDAOAddress + getArcByAddress * fix. + usage * more * . * added network in dao sidebar ; more fixed ; lint fixes * fix xGen/GEN ; better UI for DAO network label ; better documentaion for some new functions * metamask fixes ; account balances wrapper ; more bugs fixes * more * many more fixes ; added NETWORKS env var * generic way to handle DAOs from multiple networks ; fix ganache arc issue * more fixes and tests fixes * remove feed page * fix 3box issue * added select to switch between daos ; improvement to dao name styles * added member daos to the following daos list ; fix tests * fix logout ; better error message when wrong network * fix failling tests * specify which network to redeem all * fetch all daos in a loop fix undefined followed dao * init * init continue * init more * add getNetworkByDAO function * add getArcByDAOAddress + getArcByAddress * fix. + usage * more * . * added network in dao sidebar ; more fixed ; lint fixes * fix xGen/GEN ; better UI for DAO network label ; better documentaion for some new functions * metamask fixes ; account balances wrapper ; more bugs fixes * more * many more fixes ; added NETWORKS env var * generic way to handle DAOs from multiple networks ; fix ganache arc issue * more fixes and tests fixes * remove feed page * added select to switch between daos ; improvement to dao name styles * added member daos to the following daos list ; fix tests * fix logout ; better error message when wrong network * fix failling tests * specify which network to redeem all * fetch all daos in a loop fix undefined followed dao * fix search daos * exclude duplicate searched daos * don't auto logout when wrong network ; remove bubble around dao network * updated daocreator v1.0.14 ; show only current network holdings ; show current network in account menu ; can login or connect to any network (that in the Networks env var) from everywhere in Alchemy * better styles for DAO name: enable hover to reveal name when DAO name is long without new lines * same formatting to sections titles in the account menu * fix BreadcrumbsItem select position * listen to network changes in MetaMask * comments * check if window.ethereum exist before using it * more comments * update daocreator 1.0.17 * pass NETWORKS array to DAO creator ; fix crash when changing metamask to unsupported network * init * Proposals are now sorted ; Search by proposer address ; Better type to Proposal Status * Better styles for proposal row and tags ; remove Proposal History Row * sort proposal via the query itself * fix for proposals query Co-authored-by: Oren Sokolowsky --- docs/fetchingandsubscribingdata.md | 2 +- src/assets/styles/global-variables.scss | 3 +- src/components/Dao/DaoContainer.tsx | 16 +- src/components/Dao/DaoHistoryPage.scss | 80 ------ src/components/Dao/DaoHistoryPage.tsx | 223 ---------------- src/components/Dao/DaoLandingPage.scss | 65 ----- src/components/Dao/DaoLandingPage.tsx | 100 -------- src/components/Dao/DaoProposalsPage.scss | 104 ++++++++ src/components/Dao/DaoProposalsPage.tsx | 152 +++++++++++ src/components/Dao/DaoSchemesPage.tsx | 4 +- src/components/Notification/Notification.scss | 4 +- .../Proposal/ProposalHistoryRow.scss | 156 ------------ .../Proposal/ProposalHistoryRow.tsx | 239 ------------------ src/components/Proposal/ProposalRow.scss | 70 +++++ src/components/Proposal/ProposalRow.tsx | 43 ++++ .../Proposal/Staking/StakeButtons.tsx | 1 - .../rewardersProps.ts | 2 +- src/components/Scheme/SchemeProposalsPage.tsx | 2 +- src/components/Shared/withSubscription.tsx | 5 +- src/layouts/SidebarMenu.tsx | 29 +-- src/lib/proposalHelpers.ts | 84 +++++- src/lib/schemeUtils.ts | 2 +- src/pages.ts | 3 +- test/integration/history.e2e.ts | 17 -- 24 files changed, 471 insertions(+), 935 deletions(-) delete mode 100644 src/components/Dao/DaoHistoryPage.scss delete mode 100644 src/components/Dao/DaoHistoryPage.tsx delete mode 100644 src/components/Dao/DaoLandingPage.scss delete mode 100644 src/components/Dao/DaoLandingPage.tsx create mode 100644 src/components/Dao/DaoProposalsPage.scss create mode 100644 src/components/Dao/DaoProposalsPage.tsx delete mode 100644 src/components/Proposal/ProposalHistoryRow.scss delete mode 100644 src/components/Proposal/ProposalHistoryRow.tsx create mode 100644 src/components/Proposal/ProposalRow.scss create mode 100644 src/components/Proposal/ProposalRow.tsx delete mode 100644 test/integration/history.e2e.ts diff --git a/docs/fetchingandsubscribingdata.md b/docs/fetchingandsubscribingdata.md index 93a92e2eb..ac385105a 100644 --- a/docs/fetchingandsubscribingdata.md +++ b/docs/fetchingandsubscribingdata.md @@ -14,7 +14,7 @@ Executing these four steps for each React component is quite expensive (just thi So the approach we are taking in Alchemy is the following. *to avoid the cost of step 2.:* -Fetch the data that is used by many subcomponents in a single query, before those components are rendered. When those subcomponents are rendered, they will be able to get their data directly from the cache, and so will avoid a server request in step 2. An example of that pattern is found in the `DaoHistoryPage.tsx` +Fetch the data that is used by many subcomponents in a single query, before those components are rendered. When those subcomponents are rendered, they will be able to get their data directly from the cache, and so will avoid a server request in step 2. *To avoid the cost of step 4:* The line `arcSettings.graphqlSubscribeToQueries = false;` overrides arc.js default behavior to open a subscription for each query. So, by default, step 4 is skipped. Instead, we insert, in strategic places, subscriptions to relatively big queries, that will update the cache only for the data that we need. diff --git a/src/assets/styles/global-variables.scss b/src/assets/styles/global-variables.scss index 41b48c225..3332389ba 100644 --- a/src/assets/styles/global-variables.scss +++ b/src/assets/styles/global-variables.scss @@ -40,6 +40,7 @@ $small: 10px; $caption: 12px; $body: 14px; $subheader: 16px; +$sub-title: 18px; $title: 20px; $headline-2: 24px; $display: 36px; @@ -54,7 +55,6 @@ $max-width: 1080px; $navy: rgba(18, 46, 91, 1); $sky: rgba(49, 120, 202, 1); -$accent-0: rgba(3, 118, 255, 1); /* white blue */ $accent-1: rgba(3, 118, 255, 1); /* Bright blue */ $accent-2: rgba(246, 80, 80, 1); /* Red */ $accent-3: rgba(0, 190, 144, 1); /* Green */ @@ -70,5 +70,6 @@ $gray-2: rgba(191, 203, 213, 1); $gray-border-2: 1px solid $gray-2; $banner-background: #d5d8ba; $blue-link: #1977ca; +$light-blue: rgba(104, 155, 214, 1); $notifications-hover-white: rgba(233, 238, 244, 1); diff --git a/src/components/Dao/DaoContainer.tsx b/src/components/Dao/DaoContainer.tsx index 5ccd619a0..978d8869b 100644 --- a/src/components/Dao/DaoContainer.tsx +++ b/src/components/Dao/DaoContainer.tsx @@ -17,10 +17,9 @@ import { IProfileState } from "reducers/profilesReducer"; import DetailsPageRouter from "components/Scheme/ContributionRewardExtRewarders/DetailsPageRouter"; import { combineLatest, Subscription } from "rxjs"; import DaoSchemesPage from "./DaoSchemesPage"; -import DaoHistoryPage from "./DaoHistoryPage"; import DaoMembersPage from "./DaoMembersPage"; import * as css from "./Dao.scss"; -import DaoLandingPage from "components/Dao/DaoLandingPage"; +import DaoProposalsPage from "components/Dao/DaoProposalsPage"; import { standardPolling, targetedNetwork, getArcByDAOAddress, getDAONameByID } from "lib/util"; import gql from "graphql-tag"; import { getArcs } from "arc"; @@ -99,7 +98,6 @@ class DaoContainer extends React.Component { this.setState({ memberDaos: memberDaos }); } - private daoHistoryRoute = (routeProps: any) => ; private daoMembersRoute = (routeProps: any) => ; private daoProposalRoute = (routeProps: any) => { private schemeRoute = (routeProps: any) => ; private daoSchemesRoute = (routeProps: any) => ; - private daoLandingRoute = (_routeProps: any) => ; + private daoProposalsRoute = (routeProps: any) => ; private modalRoute = (route: any) => `/dao/${route.params.daoAvatarAddress}/scheme/${route.params.schemeId}/`; private onFollwingDaosListChange = (daoAddress: string, history: any) => history.push(`/dao/${daoAddress}`); @@ -164,15 +162,13 @@ class DaoContainer extends React.Component { - + render={this.daoProposalsRoute} /> + + render={this.daoProposalRoute} /> @@ -182,8 +178,6 @@ class DaoContainer extends React.Component { - - { - currentAccountAddress: Address; - daoState: IDAOState; -} - -interface IState { - filteredProposalSet: Array; - filtering: boolean; -} - -type SubscriptionData = Array; -type IProps = IExternalProps & IState & ISubscriptionProps; - -const ProposalsHTML = (props: { - proposals: Array; - history: any; - daoState: IDAOState; - currentAccountAddress: Address; -}): React.ReactElement => { - return <> { - props.proposals.map((proposal: Proposal) => { - return (); - }) - } - ; -}; - -const proposalsQuery = (dao: DAO, skip: number, titleSearch?: string): Observable> => { - const filter: any = { - }; - - if (titleSearch?.trim()) { - filter["title_contains"] = titleSearch; - } - - return dao.proposals({ - where: filter, - orderBy: "closingAt", - orderDirection: "desc", - first: PAGE_SIZE, - skip, - }, { fetchAllData: true }); -}; - -class DaoHistoryPage extends React.Component { - - private filterString = ""; - - constructor(props: IProps) { - super(props); - - this.state = { - filteredProposalSet: null, - filtering: false, - }; - } - - public componentDidMount() { - Analytics.track("Page View", { - "Page Name": Page.DAOHistory, - "DAO Address": this.props.daoState.address, - "DAO Name": this.props.daoState.name, - }); - } - - onSearchChange = (e: any) => { - this.filterString = e.target.value; - } - - onSearchExecute = async (e: any) => { - let foundProposals: Array; - if ((e.type === "blur") || (e.key === "Enter")) { - if (this.filterString?.length) { - this.setState({ filtering: true }); - foundProposals = await proposalsQuery(this.props.daoState.dao, 0, this.filterString).pipe(first()).toPromise(); - } - else { - foundProposals = null; - } - this.setState({ filteredProposalSet: foundProposals, filtering: false }); - } - } - - public render(): RenderOutput { - const { data, hasMoreToLoad, fetchMore, daoState, currentAccountAddress } = this.props; - - const proposals = this.state.filteredProposalSet ?? data; - - const result = ( -
- History - -
History
- -
- -
- - - {proposals.length === 0 ? - this.state.filteredProposalSet ? - No proposals found whose title contains the given text. Note the filter is case-sensitive. : - {this.props.daoState.name} hasn't created any proposals yet. Go to the DAO's installed plugins to create proposals. : - - - - - - - - - - - - - - - - - -
Proposed byEnd datePluginTitleVotesPredictionsStatusMy actions
- } -
- -
- ); - - return result; - } -} - -export default withSubscription({ - wrappedComponent: DaoHistoryPage, - loadingComponent: , - errorComponent: (props) =>
{props.error.message}
, - - checkForUpdate: [], - - createObservable: async (props: IExternalProps) => { - const arc = getArcByDAOAddress(props.daoState.address); - const dao = props.daoState.dao; - - // this query will fetch al data we need before rendering the page, so we avoid hitting the server - // with all separate queries for votes and stakes and stuff... - let voterClause = ""; - let stakerClause = ""; - - if (props.currentAccountAddress) { - voterClause = `(where: { voter: "${props.currentAccountAddress}"})`; - stakerClause = `(where: { staker: "${props.currentAccountAddress}"})`; - } - - const prefetchQuery = gql` - query prefetchProposalDataForDAOHistory { - proposals ( - first: ${PAGE_SIZE} - skip: 0 - orderBy: "closingAt" - orderDirection: "desc" - where: { - dao: "${dao.id}" - } - ){ - ...ProposalFields - votes ${voterClause} { - ...VoteFields - } - stakes ${stakerClause} { - ...StakeFields - } - } - } - ${Proposal.fragments.ProposalFields} - ${Vote.fragments.VoteFields} - ${Stake.fragments.StakeFields} - ${Scheme.fragments.SchemeFields} - `; - - await arc.getObservable(prefetchQuery, standardPolling()).pipe(first()).toPromise(); - - return proposalsQuery(dao, 0); - }, - - // used for hacky pagination tracking - pageSize: PAGE_SIZE, - - getFetchMoreObservable: (props: IExternalProps, data: SubscriptionData) => { - const dao = props.daoState.dao; - return proposalsQuery(dao, data.length); - }, -}); diff --git a/src/components/Dao/DaoLandingPage.scss b/src/components/Dao/DaoLandingPage.scss deleted file mode 100644 index d4a24f60d..000000000 --- a/src/components/Dao/DaoLandingPage.scss +++ /dev/null @@ -1,65 +0,0 @@ -.landingPage { - padding-top: 18px; - - .infoContainer { - margin-bottom: 40px; - } - - .infoContainer .titleContainer, - .wallContainer .headerText { - border-bottom: 1px solid rgba(45, 109, 181, 0.2); - margin-bottom: 30px; - padding-bottom: 5px; - } - - .headerText { - font-size: 28px; - font-weight: bold; - color: rgba(104, 155, 214, 1); - } - - .infoContainer .titleContainer .row { - display: flex; - align-items: center; - - .headerText { - flex-grow: 2; - } - div.editButton { - padding-right: 8px; - button { - height: 30px; - border-radius: 30px; - border-width: 1px; - background-color: #0071ff; - padding: 0 15px; - border-color: #0376ff; - color: white; - font-weight: bold; - font-size: 11px; - text-align: center; - cursor: pointer; - outline: none; - white-space: nowrap; - } - } - } - - .infoContainer { - div { - margin-bottom: 16px; - } - a { - color: $blue-link; - &:hover, - &:focus, - &:active { - color: $blue-link; - } - } - } - - .wallContainer { - padding-bottom: 5px; - } -} diff --git a/src/components/Dao/DaoLandingPage.tsx b/src/components/Dao/DaoLandingPage.tsx deleted file mode 100644 index 6551d3184..000000000 --- a/src/components/Dao/DaoLandingPage.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { IDAOState } from "@daostack/arc.js"; -import * as React from "react"; -import * as css from "./DaoLandingPage.scss"; -import { Page } from "pages"; -import Analytics from "lib/analytics"; -import { Link } from "react-router-dom"; -import { DiscussionEmbed } from "disqus-react"; -import { showSimpleMessage, targetedNetwork } from "lib/util"; -import customDaoInfo from "../../customDaoInfo"; - -type IExternalProps = { - daoState: IDAOState; -}; - -type IProps = IExternalProps; - -export default class DaoLandingPage extends React.Component { - private disqusConfig = { url: "", identifier: "", title: "" }; - - public componentDidMount() { - Analytics.track("Page View", { - "Page Name": Page.DAOLanding, - "DAO Address": this.props.daoState.id, - "DAO Name": this.props.daoState.name, - }); - } - - private handleEditContent = () => { - showSimpleMessage({ - title: "Edit Home Page", - body: ( - <> -
- Editing the content on this DAO’s home page will soon be possible - via proposal. Stay tuned! -
-
- For now, if you need a change made to a DAO’s home page content, - please contact us at{" "} - - support@daostack.zendesk.com - -
- - ), - }); - }; - - public render() { - const daoState = this.props.daoState; - const customData = - customDaoInfo[targetedNetwork()]?.[daoState.id.toLowerCase()]; - - this.disqusConfig.url = `${process.env.BASE_URL}/dao/${this.props.daoState.address}/discussion`; - this.disqusConfig.identifier = this.props.daoState.address; - this.disqusConfig.title = "Discuss " + this.props.daoState.name; - - return ( -
-
-
-
-
{daoState.name}
-
- -
-
-
- - {customData ? ( - <>{customData} - ) : ( - <> -
- Welcome to {daoState.name}, a decentralized organization built - on DAOstack. -
-
- Visit the{" "} - Proposals page{" "} - to make a proposal to the DAO or vote on existing proposals. -
- - )} -
-
-
Discuss {daoState.name}
- -
-
- ); - } -} diff --git a/src/components/Dao/DaoProposalsPage.scss b/src/components/Dao/DaoProposalsPage.scss new file mode 100644 index 000000000..8cd2e8f99 --- /dev/null +++ b/src/components/Dao/DaoProposalsPage.scss @@ -0,0 +1,104 @@ +.wrapper { + position: relative; + top: -30px; + .topBarWrapper { + display: flex; + flex-direction: column; + width: 100%; + position: sticky; + background-color: $accent-6; + z-index: 2; + padding-top: 30px; + top: 15px; + padding-bottom: 15px; + .top { + border-bottom: 1px solid $gray-2; + display: flex; + justify-content: space-between; + width: 100%; + align-items: center; + margin-bottom: 30px; + .title { + color: $light-blue; + } + .createProposalButton { + color: $white; + cursor: pointer; + background-color: $accent-1; + border-radius: 15px; + padding: 8px 15px; + + &:hover { + opacity: 0.8; + } + + &.disabled, + &.disabled:hover { + background-color: $gray-1; + cursor: not-allowed; + } + } + } + .searchBox { + margin-bottom: 20px; + width: fit-content; + border-radius: 20px; + background: url("/assets/images/Icon/search.svg") $white no-repeat left 6px center; + background-size: 18px; + padding-left: 30px; + + input:-webkit-autofill { + transition: all 5000s ease-in-out 0s; + } + + &.filtering { + background: url("/assets/images/Icon/buttonLoadingBlue.gif") $white no-repeat left 6px center; + } + + input { + border: none; + font-family: "AccordBold", sans-serif; + height: 30px; + width: 500px; + font-size: 15px; + color: #4f6176; + border-radius: 20px; + } + + input:focus { + outline: none; + } + + button { + height: 26px; + display: inline-block; + position: relative; + top: 2px; + border: none; + outline: none; + background-color: transparent; + + &:hover { + cursor: pointer; + } + } + } + } + + .tableContainer { + position: relative; + table { + text-align: left; + position: relative; + border-collapse: collapse; + width: 100%; + } + th { + background: $white; + position: sticky; + top: 220px; /* required for the stickiness */ + box-shadow: 0 2px 2px -1px $black-halftone; // change to global color + padding: 10px; + } + } +} diff --git a/src/components/Dao/DaoProposalsPage.tsx b/src/components/Dao/DaoProposalsPage.tsx new file mode 100644 index 000000000..8214f299d --- /dev/null +++ b/src/components/Dao/DaoProposalsPage.tsx @@ -0,0 +1,152 @@ +import * as React from "react"; +import * as css from "./DaoProposalsPage.scss"; +import { Address, IDAOState, Proposal } from "@daostack/arc.js"; +import { BreadcrumbsItem } from "react-breadcrumbs-dynamic"; +import withSubscription, { ISubscriptionProps } from "components/Shared/withSubscription"; +import Loading from "components/Shared/Loading"; +import InfiniteScroll from "react-infinite-scroll-component"; +import { Observable } from "rxjs"; +import ProposalRow from "components/Proposal/ProposalRow"; +import { RouteComponentProps } from "react-router-dom"; +import { first } from "rxjs/operators"; +import { isAddress } from "lib/util"; +import moment from "moment-timezone"; + +const PAGE_SIZE = 50; + +type SubscriptionData = Array; +type IProps = IExternalProps & ISubscriptionProps; +type IExternalProps = { + daoState: IDAOState; + currentAccountAddress: Address; +} & RouteComponentProps; + +type OrderDirection = "asc" | "desc"; + + +const proposalsQuery = (dao: IDAOState, skip: number, titleSearch?: string): Observable> => { + const filter: any = { + }; + + let orderDirection: OrderDirection = "asc"; + + if (!titleSearch) { + if (skip % PAGE_SIZE === 0) { + filter["closingAt_gt"] = moment().unix(); + } else { + skip -= dao.numberOfQueuedProposals + dao.numberOfPreBoostedProposals + dao.numberOfBoostedProposals; + filter["closingAt_lt"] = moment().unix(); + } + } + + if (titleSearch?.trim()) { + orderDirection = "desc"; + if (isAddress(titleSearch)) { + filter["proposer"] = titleSearch; + } else { + filter["title_contains"] = titleSearch; + } + } + + return dao.dao.proposals({ + where: filter, + orderBy: "closingAt", + orderDirection: orderDirection, + first: titleSearch ? undefined : PAGE_SIZE, // TEMPORARY UNTIL WE PASS "titleSearch" in line 143 + skip, + }, { fetchAllData: true }); +}; + +const DaoProposalsPage = (props: IProps) => { + const { data, hasMoreToLoad, fetchMore, daoState } = props; + const [filtering, setFiltering] = React.useState(false); + const [filterString, setFilterString] = React.useState(""); + const [filteredProposalSet, setFilteredProposalSet] = React.useState(null); + + const onSearchExecute = async (e: any) => { + let foundProposals: Array; + if ((e.type === "blur") || (e.key === "Enter")) { + if (filterString?.length) { + setFiltering(true); + foundProposals = await proposalsQuery(props.daoState, 0, filterString).pipe(first()).toPromise(); + } + else { + foundProposals = null; + } + setFilteredProposalSet(foundProposals); + setFiltering(false); + } + }; + + + + const proposals = (filteredProposalSet ?? data).map((proposal: Proposal) => { + return ; + }); + + return ( +
+ Proposals +
+
+

Proposals

+
+ + New Proposal (WIP) +
+
+ {data.length > 0 &&
+ setFilterString(e.target.value)} /> +
} +
+ +
+ { + data.length > 0 ? + filteredProposalSet?.length === 0 ? No proposals found whose title contains the given text or proposer address. Note the filter is case-sensitive. : + + + + + + + + + + + {proposals} + +
TitleTypeStatus
+ : {daoState.name} hasn't created any proposals yet. + } +
+
+
+ ); +}; + +export default withSubscription({ + wrappedComponent: DaoProposalsPage, + loadingComponent: , + errorComponent: (props) =>
{props.error.message}
, + checkForUpdate: ["daoState"], + createObservable: async (props: IProps) => { + return proposalsQuery(props.daoState as IDAOState, 0); + }, + pageSize: PAGE_SIZE, + getFetchMoreObservable: (props: IProps, data: SubscriptionData) => { + return proposalsQuery(props.daoState as IDAOState, data.length); + }, +}); diff --git a/src/components/Dao/DaoSchemesPage.tsx b/src/components/Dao/DaoSchemesPage.tsx index f9c484108..82928cbab 100644 --- a/src/components/Dao/DaoSchemesPage.tsx +++ b/src/components/Dao/DaoSchemesPage.tsx @@ -106,10 +106,10 @@ class DaoSchemesPage extends React.Component { return (
- Proposal Plugins + Plugins -

Proposal Plugins

+

Plugins

{ schemeManager ? ; - -const mapStateToProps = (state: IRootState, ownProps: IExternalProps & ISubscriptionProps): IExternalProps & ISubscriptionProps & IStateProps => { - const proposal = ownProps.data[0]; - - return { - ...ownProps, - creatorProfile: state.profiles[proposal.proposer], - }; -}; - -interface IState { - isMobile: boolean; -} - -class ProposalHistoryRow extends React.Component { - - constructor(props: IProps) { - super(props); - - this.state = { - isMobile: false, - }; - } - - public componentDidMount() { - window.addEventListener("resize", this.updateWindowDimensions); - } - - componentWillUnmount() { - window.removeEventListener("resize", this.updateWindowDimensions); - } - - private updateWindowDimensions = (_e: any) => { - const nowMobile = window.innerWidth <= 425; - if (nowMobile !== this.state.isMobile) { - this.setState({ isMobile: nowMobile }); - } - } - - private gotoProposal = (e: any) => { - const { daoState, history, proposal } = this.props; - const url = `/dao/${daoState.address}/proposal/${proposal.id}`; - if (e.ctrlKey || ((navigator.platform === "MacIntel") && e.metaKey)) { - window.open(url, "_blank"); - } else { - history.push(url); - } - } - - public render(): RenderOutput { - const { - creatorProfile, - currentAccountAddress, - data, daoState } = this.props; - const [proposalState, stakesOfCurrentUser, votesOfCurrentUser, currentMemberState] = data; - - const proposalClass = classNames({ - [css.wrapper]: true, - clearfix: true, - }); - - let currentAccountVote = 0; - let currentAccountPrediction = 0; - let currentAccountStakeAmount = new BN(0); - let currentAccountVoteAmount = new BN(0); - const passed = proposalPassed(proposalState); - const failed = proposalFailed(proposalState); - - let currentVote: Vote; - if (votesOfCurrentUser.length > 0) { - currentVote = votesOfCurrentUser[0]; - currentAccountVote = currentVote.staticState.outcome; - currentAccountVoteAmount = new BN(currentVote.staticState.amount); - } - - if (stakesOfCurrentUser.length > 0) { - currentAccountStakeAmount = stakesOfCurrentUser - .map((stake): BN => stake.staticState.amount) - .reduce((prev: BN, current: BN) => { return prev.add(current); }); - currentAccountPrediction = stakesOfCurrentUser[0].staticState.outcome; - } - - const myActionsClass = classNames({ - [css.myActions]: true, - [css.iVoted]: currentAccountVote !== 0, - [css.failVote]: currentAccountVote === IProposalOutcome.Fail, - [css.passVote]: currentAccountVote === IProposalOutcome.Pass, - [css.iStaked]: currentAccountPrediction !== 0, - [css.forStake]: currentAccountPrediction === IProposalOutcome.Pass, - [css.againstStake]: currentAccountPrediction === IProposalOutcome.Fail, - }); - - const closeReasonClass = classNames({ - [css.closeReason]: true, - [css.decisionPassed]: passed, - [css.decisionFailed]: failed, - }); - - let closeReason = "Time out"; - switch (proposalState.executionState) { - case IExecutionState.BoostedBarCrossed: - case IExecutionState.QueueBarCrossed: - case IExecutionState.PreBoostedBarCrossed: - closeReason = "Absolute Majority"; - break; - case IExecutionState.BoostedTimeOut: - closeReason = "Relative Majority"; - break; - } - - const voteControls = classNames({ - [css.voteControls]: true, - clearfix: true, - }); - - return ( - - - - - - - {closingTime(proposalState) ? closingTime(proposalState).format("MMM D, YYYY") : ""} - - - {schemeName(proposalState.scheme)} - - - {humanProposalTitle(proposalState)} - - -
- -
- - - - - - {(passed || failed) ? -
- - {passed ? "Passed" : "Failed"} -
- {closeReason} -
-
: -
- {(proposalState.stage === IProposalStage.Queued) ? "Queued" : - (proposalState.stage === IProposalStage.PreBoosted) ? "PreBoosted" : - ((proposalState.stage === IProposalStage.Boosted) || - (proposalState.stage === IProposalStage.QuietEndingPeriod)) ? "Boosted" : ""} -
- } - - -
- {formatTokens(currentAccountVoteAmount, "Rep")} - - -
-
- {formatTokens(currentAccountStakeAmount, "GEN")} - - -
- - - ); - } -} - -const ConnectedProposalHistoryRow = connect(mapStateToProps)(ProposalHistoryRow); - -// In this case we wrap the Connected component because mapStateToProps requires the subscribed proposal state -export default withSubscription({ - wrappedComponent: ConnectedProposalHistoryRow, - loadingComponent: (props) => Loading proposal {props.proposal.id.substr(0, 6)}..., - errorComponent: (props) => {props.error.message}, - checkForUpdate: ["currentAccountAddress"], - createObservable: (props: IExternalProps) => { - const proposal = props.proposal; - if (!props.currentAccountAddress) { - return combineLatest( - proposal.state(), - of([]), - of([]), - of(null), - ); - } else { - return combineLatest( - proposal.state(), - proposal.stakes({ where: { staker: props.currentAccountAddress } }), - proposal.votes({ where: { voter: props.currentAccountAddress } }), - // we set 'fetchPolicy' to 'cache-only' so as to not send queries for addresses that are not members. The cache is filled higher up. - props.daoState.dao.member(props.currentAccountAddress).state({ fetchPolicy: "cache-only" }), - ); - } - }, -}); diff --git a/src/components/Proposal/ProposalRow.scss b/src/components/Proposal/ProposalRow.scss new file mode 100644 index 000000000..af77b78b6 --- /dev/null +++ b/src/components/Proposal/ProposalRow.scss @@ -0,0 +1,70 @@ +.row { + background-color: $white; + border-bottom: 1px solid $gray-2; + height: 60px; + cursor: pointer; + &:hover { + opacity: 0.8; + } + td { + padding: 10px; + } + + .titleWrapper { + overflow: hidden; + text-overflow: ellipsis; + .title { + font-size: $sub-title; + } + .tagsWrapper { + display: flex; + margin-top: 5px; + .tag { + font-size: $body; + background-color: $gray-2; + border-radius: 20px; + width: fit-content; + padding: 5px 10px; + margin: 0px 5px; + line-height: 20px; + white-space: pre; + &:first-child { + margin-left: 0; + } + } + } + } + + .boostedWrapper { + display: flex; + align-items: center; + .boostedLabel { + margin-left: 5px; + } + } + + .statusWrapper { + .statusLabel { + background-color: $gray-1; + text-align: center; + border-radius: 20px; + padding: 5px 0px; + color: $white; + &.passing { + background-color: $accent-3; + } + &.failing { + background-color: $accent-2; + } + } + .statusTime { + text-align: center; + } + } + + + &:last-child { + border-bottom: none; + } +} + diff --git a/src/components/Proposal/ProposalRow.tsx b/src/components/Proposal/ProposalRow.tsx new file mode 100644 index 000000000..13662d381 --- /dev/null +++ b/src/components/Proposal/ProposalRow.tsx @@ -0,0 +1,43 @@ +import { Proposal, IProposalState } from "@daostack/arc.js"; +import ProposalCountdown from "components/Shared/ProposalCountdown"; +import { calculateProposalStatus, IProposalStatus } from "lib/proposalHelpers"; +import { schemeName } from "lib/schemeUtils"; +import * as React from "react"; +import * as css from "./ProposalRow.scss"; +import * as classNames from "classnames"; + +interface IProps { + data: Proposal; + history: any; +} + +const ProposalRow = (props: IProps) => { + const { id, dao, scheme, title, tags, boostedAt } = props.data.staticState as IProposalState; + const tagsLables = tags.map((tag, index) => { + return
{(tag as any).id}
; + }); + const status = calculateProposalStatus(props.data.staticState as IProposalState); + + const statusLabelClass = classNames({ + [css.statusLabel]: true, + [css.passing]: status === IProposalStatus.Passing, + [css.failing]: status === IProposalStatus.Failing, + }); + + return ( + window.open(`/dao/${dao.id}/proposal/${id}`)}> + +
{title}
+ {tagsLables.length > 0 &&
{tagsLables}
} + + {schemeName(scheme) ?? "Unknown"} + {boostedAt && (status === IProposalStatus.Passing || status === IProposalStatus.Failing) &&
Boosted
} + +
{status}
+
+ + + ); +}; + +export default ProposalRow; diff --git a/src/components/Proposal/Staking/StakeButtons.tsx b/src/components/Proposal/Staking/StakeButtons.tsx index a6dccfd7b..1723389c0 100644 --- a/src/components/Proposal/Staking/StakeButtons.tsx +++ b/src/components/Proposal/Staking/StakeButtons.tsx @@ -169,7 +169,6 @@ class StakeButtons extends React.Component { [css.predictions]: true, [css.detailView]: parentPage === Page.ProposalDetails, [css.contextMenu]: contextMenu, - [css.historyView]: parentPage === Page.DAOHistory, [css.unconfirmedPrediction]: isPredicting, }); diff --git a/src/components/Scheme/ContributionRewardExtRewarders/rewardersProps.ts b/src/components/Scheme/ContributionRewardExtRewarders/rewardersProps.ts index 5e0701283..e6e83ff31 100644 --- a/src/components/Scheme/ContributionRewardExtRewarders/rewardersProps.ts +++ b/src/components/Scheme/ContributionRewardExtRewarders/rewardersProps.ts @@ -7,7 +7,7 @@ export const hasRewarderContract = (schemeState: ISchemeState): boolean => { export const rewarderContractName = (schemeState: ISchemeState, useAlias = true): string => { if (hasRewarderContract(schemeState)) { - const contractInfo = getArcByDAOAddress(schemeState.dao).getContractInfo(schemeState.contributionRewardExtParams.rewarder); + const contractInfo = getArcByDAOAddress(schemeState.dao)?.getContractInfo(schemeState.contributionRewardExtParams.rewarder); if (contractInfo) { return (useAlias && !!contractInfo.alias) ? contractInfo.alias : splitCamelCase(contractInfo.name); } else { diff --git a/src/components/Scheme/SchemeProposalsPage.tsx b/src/components/Scheme/SchemeProposalsPage.tsx index dd6fa03c6..141541a97 100644 --- a/src/components/Scheme/SchemeProposalsPage.tsx +++ b/src/components/Scheme/SchemeProposalsPage.tsx @@ -281,7 +281,7 @@ class SchemeProposalsPage extends React.Component { return ( <> - Proposal Plugins + Plugins {schemeFriendlyName} {(allProposals.length === 0) diff --git a/src/components/Shared/withSubscription.tsx b/src/components/Shared/withSubscription.tsx index 60739a1bf..de85c7bab 100644 --- a/src/components/Shared/withSubscription.tsx +++ b/src/components/Shared/withSubscription.tsx @@ -209,10 +209,7 @@ const withSubscription = , Obse // Combine the previousState with the results of the new observable from fetchMore private _fetchMoreCombine(oldState: ObservableType, newData: any): ObservableType { - // Kind of hacky way of figuring out if there is more data to load - if (newData.length < options.pageSize) { - this.setState({ hasMoreToLoad: false }); - } + if (options.fetchMoreCombine) { return options.fetchMoreCombine(oldState, newData); diff --git a/src/layouts/SidebarMenu.tsx b/src/layouts/SidebarMenu.tsx index 2c3583009..34ce1ac1e 100644 --- a/src/layouts/SidebarMenu.tsx +++ b/src/layouts/SidebarMenu.tsx @@ -122,29 +122,29 @@ class SidebarMenu extends React.Component {
  • - + - - Home + + Proposals
  • - + - - Proposals + + Plugins
  • @@ -162,19 +162,6 @@ class SidebarMenu extends React.Component {
  • -
  • - - - - - History - -
diff --git a/src/lib/proposalHelpers.ts b/src/lib/proposalHelpers.ts index d62ed4276..cadaf2e7b 100644 --- a/src/lib/proposalHelpers.ts +++ b/src/lib/proposalHelpers.ts @@ -1,5 +1,4 @@ import * as moment from "moment"; - import { IProposalOutcome, IProposalStage, IProposalState } from "@daostack/arc.js"; export interface IRedemptionState { @@ -18,19 +17,90 @@ export interface IRedemptionState { voterReputation: number; } + +/** + * Proposal Status: + * - Passing: On track to pass if nothing changes + * - Failing: On track to fail if nothing changes + * - Executable: Passed and hasn't been executed yet + * - Executed: Passed and executed. + * - Failed: Failed due to votes or running out of time before reaching quorum/boosting + */ +export const enum IProposalStatus { + Passing = "Passing", + Failing = "Failing", + Executable = "Executable", + Executed = "Executed", + Failed = "Failed" +} + +/** + * This function converts Proposal Stage from it's string representation to it's number representation. + * @param {string} stage Proposal stage string representation + * @returns {IProposalStage} Proposal stage number representation + */ +export const castProposalStageToNumberRepresentation = (stage: string): IProposalStage => { + switch (stage) { + case "ExpiredInQueue": + return 0; + case "Executed": + return 1; + case "Queued": + return 2; + case "PreBoosted": + return 3; + case "Boosted": + return 4; + case "QuietEndingPeriod": + return 5; + } +}; + export const closingTime = (proposal: IProposalState) => { - switch (proposal.stage) { + let stage = proposal.stage; + if (typeof proposal.stage === "string") { + stage = castProposalStageToNumberRepresentation(proposal.stage); + } + switch (stage) { case IProposalStage.ExpiredInQueue: case IProposalStage.Queued: - return moment((proposal.createdAt + proposal.genesisProtocolParams.queuedVotePeriodLimit) * 1000); + return moment((Number(proposal.createdAt) + Number(proposal.genesisProtocolParams.queuedVotePeriodLimit)) * 1000); case IProposalStage.PreBoosted: - return moment((proposal.preBoostedAt + proposal.genesisProtocolParams.preBoostedVotePeriodLimit) * 1000); + return moment((Number(proposal.preBoostedAt) + Number(proposal.genesisProtocolParams.preBoostedVotePeriodLimit)) * 1000); case IProposalStage.Boosted: - return moment((proposal.boostedAt + proposal.genesisProtocolParams.boostedVotePeriodLimit) * 1000); + return moment((Number(proposal.boostedAt) + Number(proposal.genesisProtocolParams.boostedVotePeriodLimit)) * 1000); case IProposalStage.QuietEndingPeriod: - return moment((proposal.quietEndingPeriodBeganAt + proposal.genesisProtocolParams.quietEndingPeriod) * 1000); + return moment((Number(proposal.quietEndingPeriodBeganAt) + Number(proposal.genesisProtocolParams.quietEndingPeriod)) * 1000); case IProposalStage.Executed: - return moment(proposal.executedAt * 1000); + return moment(Number(proposal.executedAt) * 1000); + } +}; + +/** + * Given a proposal, calculates the proposal status. + * @param {IProposalState} proposal + * @returns {ProposalStatus} + */ +export const calculateProposalStatus = (proposal: IProposalState): IProposalStatus => { + const { winningOutcome, executedAt } = proposal; + const endDateMoment = moment(closingTime(proposal)); + const now = new Date(); + const complete = endDateMoment.diff(now) <= 0 ? true : false; + + if (String(winningOutcome) === "Pass") { + if (!complete) { + return IProposalStatus.Passing; + } + if (executedAt) { + return IProposalStatus.Executed; + } + return IProposalStatus.Executable; + + } else { + if (!complete) { + return IProposalStatus.Failing; + } + return IProposalStatus.Failed; } }; diff --git a/src/lib/schemeUtils.ts b/src/lib/schemeUtils.ts index 17f2e90a3..f6cc01a6e 100644 --- a/src/lib/schemeUtils.ts +++ b/src/lib/schemeUtils.ts @@ -87,7 +87,7 @@ export const PROPOSAL_SCHEME_NAMES = [ export function schemeName(scheme: ISchemeState|IContractInfo, fallback?: string) { let name: string; - const contractInfo = (scheme as IContractInfo).alias ? scheme as IContractInfo : getArcByAddress(scheme.address).getContractInfo(scheme.address); + const contractInfo = (scheme as IContractInfo).alias ? scheme as IContractInfo : getArcByAddress(scheme.address)?.getContractInfo(scheme.address); const alias = contractInfo?.alias; diff --git a/src/pages.ts b/src/pages.ts index 9dfa4072d..f61f8f42e 100644 --- a/src/pages.ts +++ b/src/pages.ts @@ -2,8 +2,7 @@ export enum Page { AccountProfile = "Account Profile", AllDAOs = "All Daos", CreateProposal = "Create Proposal", - DAOHistory = "DAO History", - DAOLanding = "DAO Landing", + DAOPropoasls = "DAO Proposals", DAOMembers = "DAO Members", DAOSchemes = "DAO Schemes", ProposalDetails = "Proposal Details", diff --git a/test/integration/history.e2e.ts b/test/integration/history.e2e.ts deleted file mode 100644 index 02a0363bd..000000000 --- a/test/integration/history.e2e.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { getContractAddresses } from "./utils"; - -describe("History page", () => { - let addresses; - let daoAddress: string; - - before(async () => { - addresses = getContractAddresses(); - daoAddress = addresses.dao.Avatar.toLowerCase(); - }); - - it("should exist", async () => { - await browser.url(`http://127.0.0.1:3000/dao/${daoAddress}/history`); - const title = await browser.getTitle(); - title.should.be.equal("Alchemy | DAOstack"); - }); -}); From db8c03a021d393e89d2c86a66875e06b187193f9 Mon Sep 17 00:00:00 2001 From: orenyodfat Date: Wed, 9 Dec 2020 19:12:20 +0200 Subject: [PATCH 02/33] temporary remove filter daoproposal page (#2319) --- src/components/Dao/DaoProposalsPage.tsx | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/components/Dao/DaoProposalsPage.tsx b/src/components/Dao/DaoProposalsPage.tsx index 8214f299d..3a0a278cc 100644 --- a/src/components/Dao/DaoProposalsPage.tsx +++ b/src/components/Dao/DaoProposalsPage.tsx @@ -10,7 +10,6 @@ import ProposalRow from "components/Proposal/ProposalRow"; import { RouteComponentProps } from "react-router-dom"; import { first } from "rxjs/operators"; import { isAddress } from "lib/util"; -import moment from "moment-timezone"; const PAGE_SIZE = 50; @@ -21,26 +20,11 @@ type IExternalProps = { currentAccountAddress: Address; } & RouteComponentProps; -type OrderDirection = "asc" | "desc"; - - const proposalsQuery = (dao: IDAOState, skip: number, titleSearch?: string): Observable> => { const filter: any = { }; - let orderDirection: OrderDirection = "asc"; - - if (!titleSearch) { - if (skip % PAGE_SIZE === 0) { - filter["closingAt_gt"] = moment().unix(); - } else { - skip -= dao.numberOfQueuedProposals + dao.numberOfPreBoostedProposals + dao.numberOfBoostedProposals; - filter["closingAt_lt"] = moment().unix(); - } - } - if (titleSearch?.trim()) { - orderDirection = "desc"; if (isAddress(titleSearch)) { filter["proposer"] = titleSearch; } else { @@ -51,7 +35,7 @@ const proposalsQuery = (dao: IDAOState, skip: number, titleSearch?: string): Obs return dao.dao.proposals({ where: filter, orderBy: "closingAt", - orderDirection: orderDirection, + orderDirection: "desc", first: titleSearch ? undefined : PAGE_SIZE, // TEMPORARY UNTIL WE PASS "titleSearch" in line 143 skip, }, { fetchAllData: true }); From 54b3a455848b1d54d26cbeb37b6ee4e65ca7c52a Mon Sep 17 00:00:00 2001 From: roienatan <34843014+roienatan@users.noreply.github.com> Date: Wed, 9 Dec 2020 23:55:13 +0200 Subject: [PATCH 03/33] Use arc.js 0.2.78 ; add execute calls button to multicall proposals (#2318) * use arc.js 0.2.78 ; add execute calls button to multicall proposals * fix button styles --- package-lock.json | 304 +--------------------- package.json | 2 +- src/actions/arcActions.ts | 14 + src/components/Proposal/ActionButton.scss | 6 +- src/components/Proposal/ActionButton.tsx | 20 +- 5 files changed, 39 insertions(+), 307 deletions(-) diff --git a/package-lock.json b/package-lock.json index 306b31ec9..ce77d8ae1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1689,9 +1689,9 @@ } }, "@daostack/arc.js": { - "version": "0.2.76", - "resolved": "https://registry.npmjs.org/@daostack/arc.js/-/arc.js-0.2.76.tgz", - "integrity": "sha512-ChOGQGGe040wCZAkWjXhBmp1q8pR5ev09dUQNpdzdVc0o7GCYhPdLzJ/T1HkFarpiRvpm4NqNxA0O+2YXltgZg==", + "version": "0.2.78", + "resolved": "https://registry.npmjs.org/@daostack/arc.js/-/arc.js-0.2.78.tgz", + "integrity": "sha512-Q2dRPvHmm8uBlUMKVGL+gkHeAcOVwt9Ey46NUCpsMWDG0wr4MNlKOigiOsTeHCA0xPMDNRIJ1AxlkVursMnmMw==", "requires": { "apollo-cache-inmemory": "^1.6.5", "apollo-client": "^2.6.8", @@ -1705,305 +1705,11 @@ "graphql-tag": "^2.10.1", "isomorphic-fetch": "^2.2.1", "isomorphic-ws": "^4.0.1", - "js-logger": "^1.6.0", + "js-logger": "1.6.0", "rxjs": "6.4.0", "subscriptions-transport-ws": "^0.9.16", - "web3": "1.2.4", + "web3": "1.3.0", "ws": "^6.2.1" - }, - "dependencies": { - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" - }, - "eth-lib": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.7.tgz", - "integrity": "sha1-L5Pxex4jrsN1nNSj/iDBKGo/wco=", - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - } - }, - "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "web3": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.2.4.tgz", - "integrity": "sha512-xPXGe+w0x0t88Wj+s/dmAdASr3O9wmA9mpZRtixGZxmBexAF0MjfqYM+MS4tVl5s11hMTN3AZb8cDD4VLfC57A==", - "requires": { - "@types/node": "^12.6.1", - "web3-bzz": "1.2.4", - "web3-core": "1.2.4", - "web3-eth": "1.2.4", - "web3-eth-personal": "1.2.4", - "web3-net": "1.2.4", - "web3-shh": "1.2.4", - "web3-utils": "1.2.4" - } - }, - "web3-bzz": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.2.4.tgz", - "integrity": "sha512-MqhAo/+0iQSMBtt3/QI1rU83uvF08sYq8r25+OUZ+4VtihnYsmkkca+rdU0QbRyrXY2/yGIpI46PFdh0khD53A==", - "requires": { - "@types/node": "^10.12.18", - "got": "9.6.0", - "swarm-js": "0.1.39", - "underscore": "1.9.1" - }, - "dependencies": { - "@types/node": { - "version": "10.17.44", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.44.tgz", - "integrity": "sha512-vHPAyBX1ffLcy4fQHmDyIUMUb42gHZjPHU66nhvbMzAWJqHnySGZ6STwN3rwrnSd1FHB0DI/RWgGELgKSYRDmw==" - } - } - }, - "web3-core": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.2.4.tgz", - "integrity": "sha512-CHc27sMuET2cs1IKrkz7xzmTdMfZpYswe7f0HcuyneTwS1yTlTnHyqjAaTy0ZygAb/x4iaVox+Gvr4oSAqSI+A==", - "requires": { - "@types/bignumber.js": "^5.0.0", - "@types/bn.js": "^4.11.4", - "@types/node": "^12.6.1", - "web3-core-helpers": "1.2.4", - "web3-core-method": "1.2.4", - "web3-core-requestmanager": "1.2.4", - "web3-utils": "1.2.4" - } - }, - "web3-core-helpers": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.2.4.tgz", - "integrity": "sha512-U7wbsK8IbZvF3B7S+QMSNP0tni/6VipnJkB0tZVEpHEIV2WWeBHYmZDnULWcsS/x/jn9yKhJlXIxWGsEAMkjiw==", - "requires": { - "underscore": "1.9.1", - "web3-eth-iban": "1.2.4", - "web3-utils": "1.2.4" - } - }, - "web3-core-method": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.2.4.tgz", - "integrity": "sha512-8p9kpL7di2qOVPWgcM08kb+yKom0rxRCMv6m/K+H+yLSxev9TgMbCgMSbPWAHlyiF3SJHw7APFKahK5Z+8XT5A==", - "requires": { - "underscore": "1.9.1", - "web3-core-helpers": "1.2.4", - "web3-core-promievent": "1.2.4", - "web3-core-subscriptions": "1.2.4", - "web3-utils": "1.2.4" - } - }, - "web3-core-promievent": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.2.4.tgz", - "integrity": "sha512-gEUlm27DewUsfUgC3T8AxkKi8Ecx+e+ZCaunB7X4Qk3i9F4C+5PSMGguolrShZ7Zb6717k79Y86f3A00O0VAZw==", - "requires": { - "any-promise": "1.3.0", - "eventemitter3": "3.1.2" - } - }, - "web3-core-requestmanager": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.2.4.tgz", - "integrity": "sha512-eZJDjyNTDtmSmzd3S488nR/SMJtNnn/GuwxnMh3AzYCqG3ZMfOylqTad2eYJPvc2PM5/Gj1wAMQcRpwOjjLuPg==", - "requires": { - "underscore": "1.9.1", - "web3-core-helpers": "1.2.4", - "web3-providers-http": "1.2.4", - "web3-providers-ipc": "1.2.4", - "web3-providers-ws": "1.2.4" - } - }, - "web3-core-subscriptions": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.2.4.tgz", - "integrity": "sha512-3D607J2M8ymY9V+/WZq4MLlBulwCkwEjjC2U+cXqgVO1rCyVqbxZNCmHyNYHjDDCxSEbks9Ju5xqJxDSxnyXEw==", - "requires": { - "eventemitter3": "3.1.2", - "underscore": "1.9.1", - "web3-core-helpers": "1.2.4" - } - }, - "web3-eth": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.2.4.tgz", - "integrity": "sha512-+j+kbfmZsbc3+KJpvHM16j1xRFHe2jBAniMo1BHKc3lho6A8Sn9Buyut6odubguX2AxoRArCdIDCkT9hjUERpA==", - "requires": { - "underscore": "1.9.1", - "web3-core": "1.2.4", - "web3-core-helpers": "1.2.4", - "web3-core-method": "1.2.4", - "web3-core-subscriptions": "1.2.4", - "web3-eth-abi": "1.2.4", - "web3-eth-accounts": "1.2.4", - "web3-eth-contract": "1.2.4", - "web3-eth-ens": "1.2.4", - "web3-eth-iban": "1.2.4", - "web3-eth-personal": "1.2.4", - "web3-net": "1.2.4", - "web3-utils": "1.2.4" - } - }, - "web3-eth-abi": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.2.4.tgz", - "integrity": "sha512-8eLIY4xZKoU3DSVu1pORluAw9Ru0/v4CGdw5so31nn+7fR8zgHMgwbFe0aOqWQ5VU42PzMMXeIJwt4AEi2buFg==", - "requires": { - "ethers": "4.0.0-beta.3", - "underscore": "1.9.1", - "web3-utils": "1.2.4" - } - }, - "web3-eth-accounts": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.2.4.tgz", - "integrity": "sha512-04LzT/UtWmRFmi4hHRewP5Zz43fWhuHiK5XimP86sUQodk/ByOkXQ3RoXyGXFMNoRxdcAeRNxSfA2DpIBc9xUw==", - "requires": { - "@web3-js/scrypt-shim": "^0.1.0", - "any-promise": "1.3.0", - "crypto-browserify": "3.12.0", - "eth-lib": "0.2.7", - "ethereumjs-common": "^1.3.2", - "ethereumjs-tx": "^2.1.1", - "underscore": "1.9.1", - "uuid": "3.3.2", - "web3-core": "1.2.4", - "web3-core-helpers": "1.2.4", - "web3-core-method": "1.2.4", - "web3-utils": "1.2.4" - } - }, - "web3-eth-contract": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.2.4.tgz", - "integrity": "sha512-b/9zC0qjVetEYnzRA1oZ8gF1OSSUkwSYi5LGr4GeckLkzXP7osEnp9lkO/AQcE4GpG+l+STnKPnASXJGZPgBRQ==", - "requires": { - "@types/bn.js": "^4.11.4", - "underscore": "1.9.1", - "web3-core": "1.2.4", - "web3-core-helpers": "1.2.4", - "web3-core-method": "1.2.4", - "web3-core-promievent": "1.2.4", - "web3-core-subscriptions": "1.2.4", - "web3-eth-abi": "1.2.4", - "web3-utils": "1.2.4" - } - }, - "web3-eth-ens": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.2.4.tgz", - "integrity": "sha512-g8+JxnZlhdsCzCS38Zm6R/ngXhXzvc3h7bXlxgKU4coTzLLoMpgOAEz71GxyIJinWTFbLXk/WjNY0dazi9NwVw==", - "requires": { - "eth-ens-namehash": "2.0.8", - "underscore": "1.9.1", - "web3-core": "1.2.4", - "web3-core-helpers": "1.2.4", - "web3-core-promievent": "1.2.4", - "web3-eth-abi": "1.2.4", - "web3-eth-contract": "1.2.4", - "web3-utils": "1.2.4" - } - }, - "web3-eth-iban": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.2.4.tgz", - "integrity": "sha512-D9HIyctru/FLRpXakRwmwdjb5bWU2O6UE/3AXvRm6DCOf2e+7Ve11qQrPtaubHfpdW3KWjDKvlxV9iaFv/oTMQ==", - "requires": { - "bn.js": "4.11.8", - "web3-utils": "1.2.4" - } - }, - "web3-eth-personal": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.2.4.tgz", - "integrity": "sha512-5Russ7ZECwHaZXcN3DLuLS7390Vzgrzepl4D87SD6Sn1DHsCZtvfdPIYwoTmKNp69LG3mORl7U23Ga5YxqkICw==", - "requires": { - "@types/node": "^12.6.1", - "web3-core": "1.2.4", - "web3-core-helpers": "1.2.4", - "web3-core-method": "1.2.4", - "web3-net": "1.2.4", - "web3-utils": "1.2.4" - } - }, - "web3-net": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.2.4.tgz", - "integrity": "sha512-wKOsqhyXWPSYTGbp7ofVvni17yfRptpqoUdp3SC8RAhDmGkX6irsiT9pON79m6b3HUHfLoBilFQyt/fTUZOf7A==", - "requires": { - "web3-core": "1.2.4", - "web3-core-method": "1.2.4", - "web3-utils": "1.2.4" - } - }, - "web3-providers-http": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.2.4.tgz", - "integrity": "sha512-dzVCkRrR/cqlIrcrWNiPt9gyt0AZTE0J+MfAu9rR6CyIgtnm1wFUVVGaxYRxuTGQRO4Dlo49gtoGwaGcyxqiTw==", - "requires": { - "web3-core-helpers": "1.2.4", - "xhr2-cookies": "1.1.0" - } - }, - "web3-providers-ipc": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.2.4.tgz", - "integrity": "sha512-8J3Dguffin51gckTaNrO3oMBo7g+j0UNk6hXmdmQMMNEtrYqw4ctT6t06YOf9GgtOMjSAc1YEh3LPrvgIsR7og==", - "requires": { - "oboe": "2.1.4", - "underscore": "1.9.1", - "web3-core-helpers": "1.2.4" - } - }, - "web3-providers-ws": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.2.4.tgz", - "integrity": "sha512-F/vQpDzeK+++oeeNROl1IVTufFCwCR2hpWe5yRXN0ApLwHqXrMI7UwQNdJ9iyibcWjJf/ECbauEEQ8CHgE+MYQ==", - "requires": { - "@web3-js/websocket": "^1.0.29", - "underscore": "1.9.1", - "web3-core-helpers": "1.2.4" - } - }, - "web3-shh": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.2.4.tgz", - "integrity": "sha512-z+9SCw0dE+69Z/Hv8809XDbLj7lTfEv9Sgu8eKEIdGntZf4v7ewj5rzN5bZZSz8aCvfK7Y6ovz1PBAu4QzS4IQ==", - "requires": { - "web3-core": "1.2.4", - "web3-core-method": "1.2.4", - "web3-core-subscriptions": "1.2.4", - "web3-net": "1.2.4" - } - }, - "web3-utils": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.2.4.tgz", - "integrity": "sha512-+S86Ip+jqfIPQWvw2N/xBQq5JNqCO0dyvukGdJm8fEWHZbckT4WxSpHbx+9KLEWY4H4x9pUwnoRkK87pYyHfgQ==", - "requires": { - "bn.js": "4.11.8", - "eth-lib": "0.2.7", - "ethereum-bloom-filters": "^1.0.6", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "underscore": "1.9.1", - "utf8": "3.0.0" - } - } } }, "@daostack/infra": { diff --git a/package.json b/package.json index b01873846..7982fa5c2 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "dependencies": { "3box": "1.20.2", "@burner-wallet/burner-connect-provider": "^0.1.1", - "@daostack/arc.js": "0.2.76", + "@daostack/arc.js": "0.2.78", "@dorgtech/daocreator-ui": "1.0.21", "@fortawesome/fontawesome-svg-core": "^1.2.10", "@fortawesome/free-brands-svg-icons": "^5.6.1", diff --git a/src/actions/arcActions.ts b/src/actions/arcActions.ts index ae689b6d4..6b6309f93 100644 --- a/src/actions/arcActions.ts +++ b/src/actions/arcActions.ts @@ -71,6 +71,20 @@ export function executeProposal(avatarAddress: string, proposalId: string, _acco }; } +export function executeCalls(avatarAddress: string, proposalId: string) { + return async (dispatch: Redux.Dispatch) => { + try { + const arc = getArcByDAOAddress(avatarAddress); + const observer = operationNotifierObserver(dispatch, "Execute calls"); + const proposalObj = await arc.dao(avatarAddress).proposal(proposalId); + return await proposalObj.executeCalls().subscribe(...observer); + } catch (err) { + console.error(err); + throw err; + } + }; +} + export type VoteAction = IAsyncAction<"ARC_VOTE", { avatarAddress: string; proposalId: string; diff --git a/src/components/Proposal/ActionButton.scss b/src/components/Proposal/ActionButton.scss index 319ba27dc..0d025462e 100644 --- a/src/components/Proposal/ActionButton.scss +++ b/src/components/Proposal/ActionButton.scss @@ -1,6 +1,6 @@ .wrapper { position: relative; - display: inline-block; + display: flex; button { background-color: rgba(0, 118, 255, 1); @@ -35,7 +35,6 @@ width: 67px; } - button.executeButton:hover, &.expanded button.executeButton { width: 86px; } @@ -106,8 +105,7 @@ div.tooltipWhenDisabledContainer { color: $white; line-height: 30px; padding: 0 15px; - margin-top: 0; - margin-bottom: 15px; + margin: 0px 5px; font-size: 13px; font-weight: bold; } diff --git a/src/components/Proposal/ActionButton.tsx b/src/components/Proposal/ActionButton.tsx index 7c5e23f53..023ba6c88 100644 --- a/src/components/Proposal/ActionButton.tsx +++ b/src/components/Proposal/ActionButton.tsx @@ -1,5 +1,5 @@ import { Address, IDAOState, IProposalOutcome, IProposalStage, IProposalState, IRewardState, Token } from "@daostack/arc.js"; -import { executeProposal, redeemProposal } from "actions/arcActions"; +import { executeProposal, redeemProposal, executeCalls } from "actions/arcActions"; import { enableWalletProvider } from "arc"; import classNames from "classnames"; import { ActionTypes, default as PreTransactionModal } from "components/Shared/PreTransactionModal"; @@ -16,7 +16,7 @@ import withSubscription, { ISubscriptionProps } from "components/Shared/withSubs import { of, combineLatest, Observable } from "rxjs"; import * as css from "./ActionButton.scss"; import RedemptionsTip from "./RedemptionsTip"; - +import { proposalPassed } from "lib/proposalHelpers"; import * as BN from "bn.js"; interface IExternalProps { @@ -43,6 +43,7 @@ interface IDispatchProps { executeProposal: typeof executeProposal; redeemProposal: typeof redeemProposal; showNotification: typeof showNotification; + executeCalls: typeof executeCalls; } type IProps = IExternalProps & IStateProps & IDispatchProps & ISubscriptionProps<[BN, BN]>; @@ -58,6 +59,7 @@ const mapDispatchToProps = { redeemProposal, executeProposal, showNotification, + executeCalls, }; interface IState { @@ -81,7 +83,11 @@ class ActionButton extends React.Component { const { currentAccountAddress, daoState, parentPage, proposalState } = this.props; - await this.props.executeProposal(daoState.address, proposalState.id, currentAccountAddress); + if (type === "ExecuteCalls") { + await this.props.executeCalls(daoState.address, proposalState.id); + } else { + await this.props.executeProposal(daoState.address, proposalState.id, currentAccountAddress); + } Analytics.track("Transition Proposal", { "DAO Address": daoState.address, @@ -209,6 +215,10 @@ class ActionButton extends React.Component { proposal: proposalState, }); + const showExecuteCallsButton = (proposalState.genericScheme && !proposalState.genericScheme.executed) || + (proposalState.genericSchemeMultiCall && !proposalState.genericSchemeMultiCall.executed) + && proposalPassed(proposalState); + const redeemButtonClass = classNames({ [css.redeemButton]: true, }); @@ -280,6 +290,10 @@ class ActionButton extends React.Component {
: "" } + {showExecuteCallsButton && }
); } From b5b83dfd8af3c520713b71cb0b5dcd11d97caee3 Mon Sep 17 00:00:00 2001 From: roienatan <34843014+roienatan@users.noreply.github.com> Date: Thu, 10 Dec 2020 09:42:55 +0200 Subject: [PATCH 04/33] update changelog (#2321) --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec784b29a..827e051e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,13 @@ - Better DAO name styles on sidebar - Loading speed and vulnerabilities fixes - Integration tests typescript supporting was added - - Use subgraph v41_1 and arc.js 0.2.77 + - Use subgraph v41_1 and arc.js 0.2.78 - Update BalancerPoolManager.json scheme - Bugs Fixed - Detect wallet network change in account menu - Show missing GP params in competition schemes + - Added missing functionality for multi-call execute calls - Features Removed - Feed Page From a944d51209770426a61ee8d6a3f871c7f8447c62 Mon Sep 17 00:00:00 2001 From: Doug Kent Date: Fri, 11 Dec 2020 04:04:57 -0500 Subject: [PATCH 05/33] whitelist PrimeDAO Token (#2322) --- data/tokens.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/tokens.json b/data/tokens.json index 0a55c463c..b4958b53b 100644 --- a/data/tokens.json +++ b/data/tokens.json @@ -83,6 +83,11 @@ "name": "Panvala pan", "symbol": "PAN" } + }, + "0xe59064a8185ed1fca1d17999621efedfab4425c9": { + "decimals": 18, + "name": "PrimeDAO Token", + "symbol": "PRIME" } }, "rinkeby": { From 56a5f702fbbf4c37645defd3b8ee6c240fd3f2f1 Mon Sep 17 00:00:00 2001 From: Razzwan Date: Sun, 13 Dec 2020 15:44:15 +0200 Subject: [PATCH 06/33] New proposal window with select (#2296) * move proposal window to separate file * resolve merge conflict * commit for removal * create first version of adding random proposal button * add styles for select proposal component * feat: final step of create proposal window * fix codereview issues * chore: change node.js version * supporting dynamic schemes * improve after codereview --- .travis/branch.yml | 2 +- .travis/pr.yml | 2 +- src/assets/styles/global-variables.scss | 1 + src/components/Dao/DaoContainer.tsx | 82 ++++++++++-- src/components/Dao/DaoProposalsPage.tsx | 8 +- .../Proposal/Create/CreateProposal.scss | 17 ++- .../Proposal/Create/CreateProposalPage.tsx | 117 ++++++++++-------- .../CreateContributionRewardProposal.tsx | 2 +- .../CreateGenericMultiCallProposal.tsx | 10 +- .../CreateKnownGenericSchemeProposal.tsx | 2 +- .../CreateSchemeRegistrarProposal.tsx | 2 +- .../CreateUnknownGenericSchemeProposal.tsx | 2 +- .../Create/SelectProposal/SelectProposal.scss | 16 +++ .../Create/SelectProposal/SelectProposal.tsx | 87 +++++++++++++ .../SelectProposalLabel.scss | 3 + .../SelectProposalLabel.tsx | 20 +++ .../SelectProposalLabel/index.ts | 1 + .../Proposal/Create/SelectProposal/index.ts | 21 ++++ src/components/Proposal/Create/index.ts | 48 +++++++ src/components/Proposal/ProposalCard.tsx | 2 +- src/components/Proposal/ProposalData.tsx | 2 +- .../Proposal/ProposalDetailsPage.tsx | 2 +- .../rewardersProps.ts | 2 +- src/components/Scheme/SchemeContainer.tsx | 7 +- src/components/Shared/withSubscription.tsx | 2 +- src/lib/schemeUtils.ts | 2 +- 26 files changed, 379 insertions(+), 83 deletions(-) create mode 100644 src/components/Proposal/Create/SelectProposal/SelectProposal.scss create mode 100644 src/components/Proposal/Create/SelectProposal/SelectProposal.tsx create mode 100644 src/components/Proposal/Create/SelectProposal/SelectProposalLabel/SelectProposalLabel.scss create mode 100644 src/components/Proposal/Create/SelectProposal/SelectProposalLabel/SelectProposalLabel.tsx create mode 100644 src/components/Proposal/Create/SelectProposal/SelectProposalLabel/index.ts create mode 100644 src/components/Proposal/Create/SelectProposal/index.ts create mode 100644 src/components/Proposal/Create/index.ts diff --git a/.travis/branch.yml b/.travis/branch.yml index 6aaf86b61..5e0dec513 100644 --- a/.travis/branch.yml +++ b/.travis/branch.yml @@ -1,5 +1,5 @@ language: node_js -node_js: 12.18.4 +node_js: 12.20.0 addons: apt: update: true diff --git a/.travis/pr.yml b/.travis/pr.yml index 6e734b299..6ffd4a7de 100644 --- a/.travis/pr.yml +++ b/.travis/pr.yml @@ -1,5 +1,5 @@ language: node_js -node_js: 12.18.4 +node_js: 12.20.0 addons: apt: update: true diff --git a/src/assets/styles/global-variables.scss b/src/assets/styles/global-variables.scss index 3332389ba..8236fefc8 100644 --- a/src/assets/styles/global-variables.scss +++ b/src/assets/styles/global-variables.scss @@ -23,6 +23,7 @@ $accent-3-halftone: rgba(255, 0, 72, 0.5); $accent-4: rgba(46, 168, 122, 1); $accent-4-halftone: rgba(46, 168, 122, 0.5); $accent-4-transparent: rgba(46, 168, 122, 0); +$gray-label: #506176; $body-font: "Open Sans"; $heading-1-font: "AccordBold"; diff --git a/src/components/Dao/DaoContainer.tsx b/src/components/Dao/DaoContainer.tsx index 978d8869b..7cea26c6e 100644 --- a/src/components/Dao/DaoContainer.tsx +++ b/src/components/Dao/DaoContainer.tsx @@ -1,6 +1,6 @@ import { IDAOState, Member } from "@daostack/arc.js"; import { getProfilesForAddresses } from "actions/profilesActions"; -import CreateProposalPage from "components/Proposal/Create/CreateProposalPage"; +import CreateProposalPage from "components/Proposal/Create"; import ProposalDetailsPage from "components/Proposal/ProposalDetailsPage"; import SchemeContainer from "components/Scheme/SchemeContainer"; import Loading from "components/Shared/Loading"; @@ -20,9 +20,9 @@ import DaoSchemesPage from "./DaoSchemesPage"; import DaoMembersPage from "./DaoMembersPage"; import * as css from "./Dao.scss"; import DaoProposalsPage from "components/Dao/DaoProposalsPage"; -import { standardPolling, targetedNetwork, getArcByDAOAddress, getDAONameByID } from "lib/util"; +import { standardPolling, targetedNetwork, getArcByDAOAddress, getDAONameByID, getNetworkByDAOAddress } from "lib/util"; import gql from "graphql-tag"; -import { getArcs } from "arc"; +import { enableWalletProvider, getArcs } from "arc"; type IExternalProps = RouteComponentProps; @@ -35,6 +35,8 @@ interface IStateProps { interface IState { memberDaos: Array; + modalParentPath: string; + schemeId: string|null; } interface IDispatchProps { @@ -64,6 +66,8 @@ class DaoContainer extends React.Component { super(props); this.state = { memberDaos: [], + modalParentPath: "/dao/:daoAvatarAddress", + schemeId: "", }; } public daoSubscription: any; @@ -98,6 +102,28 @@ class DaoContainer extends React.Component { this.setState({ memberDaos: memberDaos }); } + public handleNewProposal = async (schemeId?: string): Promise => { + const { showNotification, daoAvatarAddress } = this.props; + + if (!await enableWalletProvider({ showNotification }, getNetworkByDAOAddress(daoAvatarAddress))) { return; } + + if (typeof schemeId === "string") { + this.setState(() => ({ + modalParentPath: "/dao/:daoAvatarAddress/scheme/:schemeId", + schemeId, + }), () => { + this.props.history.push(`/dao/${daoAvatarAddress}/scheme/${schemeId}/proposals/create/`); + }); + } else { + this.setState(() => ({ + modalParentPath: "/dao/:daoAvatarAddress", + schemeId: "", + }), () => { + this.props.history.push(`/dao/${daoAvatarAddress}/proposals/create`); + }); + } + }; + private daoMembersRoute = (routeProps: any) => ; private daoProposalRoute = (routeProps: any) => { proposalId={routeProps.match.params.proposalId} />; - private schemeRoute = (routeProps: any) => ; + private schemeRoute = (routeProps: any) => ( + + ); private daoSchemesRoute = (routeProps: any) => ; - private daoProposalsRoute = (routeProps: any) => ; - private modalRoute = (route: any) => `/dao/${route.params.daoAvatarAddress}/scheme/${route.params.schemeId}/`; + private daoProposalsRoute = (routeProps: any) => ( + + ); + private createProposalRoute = (routeProps: any) => ( + + ); + private modalRouteCreateProposal = (route: any) => { + return this.state.modalParentPath + .replace(":daoAvatarAddress", route.params.daoAvatarAddress) + .replace(":schemeId", this.state.schemeId); + }; private onFollwingDaosListChange = (daoAddress: string, history: any) => history.push(`/dao/${daoAddress}`); public render(): RenderOutput { @@ -161,9 +212,6 @@ class DaoContainer extends React.Component { - - @@ -173,17 +221,27 @@ class DaoContainer extends React.Component { - + + + + diff --git a/src/components/Dao/DaoProposalsPage.tsx b/src/components/Dao/DaoProposalsPage.tsx index 3a0a278cc..73f90f68b 100644 --- a/src/components/Dao/DaoProposalsPage.tsx +++ b/src/components/Dao/DaoProposalsPage.tsx @@ -18,6 +18,7 @@ type IProps = IExternalProps & ISubscriptionProps; type IExternalProps = { daoState: IDAOState; currentAccountAddress: Address; + onCreateProposal: () => void; } & RouteComponentProps; const proposalsQuery = (dao: IDAOState, skip: number, titleSearch?: string): Observable> => { @@ -42,7 +43,7 @@ const proposalsQuery = (dao: IDAOState, skip: number, titleSearch?: string): Obs }; const DaoProposalsPage = (props: IProps) => { - const { data, hasMoreToLoad, fetchMore, daoState } = props; + const { data, hasMoreToLoad, fetchMore, daoState, onCreateProposal } = props; const [filtering, setFiltering] = React.useState(false); const [filterString, setFilterString] = React.useState(""); const [filteredProposalSet, setFilteredProposalSet] = React.useState(null); @@ -75,11 +76,10 @@ const DaoProposalsPage = (props: IProps) => {

Proposals

- + New Proposal (WIP) + + New Proposal
{data.length > 0 &&
diff --git a/src/components/Proposal/Create/CreateProposal.scss b/src/components/Proposal/Create/CreateProposal.scss index 73a56cb05..783ebac67 100644 --- a/src/components/Proposal/Create/CreateProposal.scss +++ b/src/components/Proposal/Create/CreateProposal.scss @@ -1,6 +1,6 @@ @import "./react-mde.css"; :global { - @import "../../../../node_modules/react-datetime/css/react-datetime"; + @import "../../../../node_modules/react-datetime/css/react-datetime.css"; } .hidden { @@ -29,6 +29,20 @@ fieldset { opacity: 0.7; } +.loadingWrap { + align-items: center; + justify-content: center; + display: flex; + width: 100%; + height: 356px; +} + +.createProposalContent { + min-height: 500px; + max-height: calc(100vh - 56px); + overflow: hidden; +} + .createProposalWrapper { border-radius: 15px 15px 0 0; background-color: $white; @@ -37,6 +51,7 @@ fieldset { left: 50%; transform: translate(-50%, -50%); box-shadow: $shadow-3; + min-width: 622px; .header { background-color: $navy; diff --git a/src/components/Proposal/Create/CreateProposalPage.tsx b/src/components/Proposal/Create/CreateProposalPage.tsx index 1f43276f3..9c7dc4dc8 100644 --- a/src/components/Proposal/Create/CreateProposalPage.tsx +++ b/src/components/Proposal/Create/CreateProposalPage.tsx @@ -1,32 +1,37 @@ -import { ISchemeState, Address } from "@daostack/arc.js"; -import CreateKnownGenericSchemeProposal from "components/Proposal/Create/SchemeForms/CreateKnownGenericSchemeProposal"; -import CreateSchemeRegistrarProposal from "components/Proposal/Create/SchemeForms/CreateSchemeRegistrarProposal"; -import CreateUnknownGenericSchemeProposal from "components/Proposal/Create/SchemeForms/CreateUnknownGenericSchemeProposal"; -import CreateGenericMultiCallProposal from "components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal"; -import Loading from "components/Shared/Loading"; -import withSubscription, { ISubscriptionProps } from "components/Shared/withSubscription"; -import { GenericSchemeRegistry } from "genericSchemeRegistry"; -import Analytics from "lib/analytics"; -import { History } from "history"; -import { Page } from "pages"; import * as React from "react"; + +import { History } from "history"; import { BreadcrumbsItem } from "react-breadcrumbs-dynamic"; -import { connect } from "react-redux"; -import { IRootState } from "reducers"; import { RouteComponentProps } from "react-router-dom"; -import { CrxRewarderComponentType, getCrxRewarderComponent, rewarderContractName } from "components/Scheme/ContributionRewardExtRewarders/rewardersProps"; -import CreateContributionRewardProposal from "components/Proposal/Create/SchemeForms/CreateContributionRewardProposal"; +import { Page } from "pages"; + +import { GenericSchemeRegistry } from "genericSchemeRegistry"; +import Analytics from "lib/analytics"; import { schemeName } from "lib/schemeUtils"; + +import { ISchemeState, Address, DAO } from "@daostack/arc.js"; +import { CrxRewarderComponentType, getCrxRewarderComponent, rewarderContractName } from "components/Scheme/ContributionRewardExtRewarders/rewardersProps"; +import { ISubscriptionProps } from "components/Shared/withSubscription"; +import Loading from "components/Shared/Loading"; + +import CreateKnownGenericSchemeProposal from "./SchemeForms/CreateKnownGenericSchemeProposal"; +import CreateSchemeRegistrarProposal from "./SchemeForms/CreateSchemeRegistrarProposal"; +import CreateUnknownGenericSchemeProposal from "./SchemeForms/CreateUnknownGenericSchemeProposal"; +import CreateGenericMultiCallProposal from "./SchemeForms/CreateGenericMultiCallProposal"; +import CreateContributionRewardProposal from "./SchemeForms/CreateContributionRewardProposal"; +import SelectProposal from "./SelectProposal"; + import * as css from "./CreateProposal.scss"; -import { getArcByDAOAddress } from "lib/util"; type IExternalProps = RouteComponentProps; interface IExternalStateProps { + dao: DAO; currentAccountAddress: Address; daoAvatarAddress: string; history: History; schemeId: string; + parentPath: string; } interface IStateProps { @@ -35,16 +40,7 @@ interface IStateProps { type IProps = IExternalProps & IExternalStateProps & ISubscriptionProps; -const mapStateToProps = (state: IRootState, ownProps: IExternalProps): IExternalProps & IExternalStateProps => { - return { - ...ownProps, - currentAccountAddress: state.web3.currentAccountAddress, - daoAvatarAddress: ownProps.match.params.daoAvatarAddress, - schemeId: ownProps.match.params.schemeId, - }; -}; - -class CreateProposalPage extends React.Component { +export class CreateProposalPage extends React.Component { constructor(props: IProps) { super(props); @@ -53,14 +49,17 @@ class CreateProposalPage extends React.Component { }; } - public handleClose = (e: any) => { - e.preventDefault(); + public handleClose = (e?: any) => { + if (e?.preventDefault) { + e.preventDefault(); + } this.doClose(); } public doClose = () => { - const { daoAvatarAddress, history, schemeId } = this.props; - history.push("/dao/" + daoAvatarAddress + "/scheme/" + schemeId); + const { history, parentPath } = this.props; + + history.push(parentPath); } public async componentDidMount() { @@ -78,12 +77,20 @@ class CreateProposalPage extends React.Component { * with this CrExt scheme (if it is a CrExt scheme -- very cheap if not a CrExt). */ if (!this.state.createCrxProposalComponent) { - Object.assign(newState, { createCrxProposalComponent: await getCrxRewarderComponent(this.props.data, CrxRewarderComponentType.CreateProposal) }); + const scheme = this.props.data; + Object.assign(newState, { createCrxProposalComponent: await getCrxRewarderComponent(scheme, CrxRewarderComponentType.CreateProposal) }); } this.setState(newState); } + public async componentDidUpdate(prevProps: Readonly) { + if (prevProps.data?.id !== this.props.data?.id) { + const scheme = this.props.data; + this.setState({ createCrxProposalComponent: await getCrxRewarderComponent(scheme, CrxRewarderComponentType.CreateProposal) }); + } + } + public componentWillUnmount(){ document.removeEventListener("keydown", this.handleKeyPress, false); } @@ -95,18 +102,24 @@ class CreateProposalPage extends React.Component { } } - public render(): RenderOutput { + private getCreateSchemeComponent = (): [JSX.Element, string] => { const { daoAvatarAddress, currentAccountAddress } = this.props; + const scheme = this.props.data; + if (!scheme) { + return [null, "select proposal type"]; + } + + const schemeTitle = this.state.createCrxProposalComponent ? rewarderContractName(scheme) : schemeName(scheme); + let createSchemeComponent =
; const props = { currentAccountAddress, daoAvatarAddress, - handleClose: this.doClose, + handleClose: this.handleClose, scheme, }; - const schemeTitle = this.state.createCrxProposalComponent ? rewarderContractName(scheme) : schemeName(scheme); if (this.state.createCrxProposalComponent) { createSchemeComponent = ; @@ -143,29 +156,33 @@ class CreateProposalPage extends React.Component { createSchemeComponent = ; } + return [createSchemeComponent, schemeTitle]; + } + + public render(): RenderOutput { + const { daoAvatarAddress, match, location, history, dao, data: scheme, parentPath, isLoading } = this.props; + const [createSchemeComponent, schemeTitle] = this.getCreateSchemeComponent(); + return (
- Create {schemeTitle} Proposal + Create {schemeTitle} Proposal

+ New proposal | {schemeTitle}

- { createSchemeComponent } +
+ + {Boolean(!createSchemeComponent || isLoading) &&
} + { createSchemeComponent } +
); } } - -const SubscribedCreateProposalPage = withSubscription({ - wrappedComponent: CreateProposalPage, - loadingComponent: , - errorComponent: null, - checkForUpdate: ["daoAvatarAddress"], - createObservable: (props: IExternalStateProps) => { - const arc = getArcByDAOAddress(props.daoAvatarAddress); - const scheme = arc.scheme(props.schemeId); - return scheme.state(); - }, -}); - -export default connect(mapStateToProps)(SubscribedCreateProposalPage); diff --git a/src/components/Proposal/Create/SchemeForms/CreateContributionRewardProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateContributionRewardProposal.tsx index 6f13927ba..9f79ac723 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateContributionRewardProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateContributionRewardProposal.tsx @@ -12,7 +12,7 @@ import Analytics from "lib/analytics"; import { baseTokenName, supportedTokens, toBaseUnit, tokenDetails, toWei, isValidUrl, isAddress, getArcByDAOAddress, getNetworkByDAOAddress } from "lib/util"; import { showNotification, NotificationStatus } from "reducers/notifications"; import { exportUrl, importUrlValues } from "lib/proposalUtils"; -import * as css from "../CreateProposal.scss"; +import * as css from "components/Proposal/Create/CreateProposal.scss"; import MarkdownField from "./MarkdownField"; import HelpButton from "components/Shared/HelpButton"; diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index 5b322c9f5..5b226a6b1 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -10,7 +10,7 @@ import { baseTokenName, isValidUrl, isAddress, linkToEtherScan, getContractName, import { exportUrl, importUrlValues } from "lib/proposalUtils"; import TagsSelector from "components/Proposal/Create/SchemeForms/TagsSelector"; import TrainingTooltip from "components/Shared/TrainingTooltip"; -import * as css from "../CreateProposal.scss"; +import * as css from "components/Proposal/Create/CreateProposal.scss"; import MarkdownField from "./MarkdownField"; import HelpButton from "components/Shared/HelpButton"; import { getABIByContract, extractABIMethods, encodeABI } from "./ABIService"; @@ -135,6 +135,14 @@ class CreateGenericMultiCallScheme extends React.Component }; } + componentDidUpdate(prevProps: Readonly) { + if (prevProps.whitelistedContracts !== this.props.whitelistedContracts) { + this.setState({ + whitelistedContracts: this.props.whitelistedContracts?.map(contract => { return contract.toLowerCase(); }) ?? [], + }); + } + } + public async handleSubmit(formValues: IFormValues, { setSubmitting }: any): Promise { if (!await enableWalletProvider({ showNotification: this.props.showNotification }, getNetworkByDAOAddress(this.props.daoAvatarAddress))) { return; } diff --git a/src/components/Proposal/Create/SchemeForms/CreateKnownGenericSchemeProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateKnownGenericSchemeProposal.tsx index 7238aa3f9..16abd393f 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateKnownGenericSchemeProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateKnownGenericSchemeProposal.tsx @@ -20,7 +20,7 @@ import { exportUrl, importUrlValues } from "lib/proposalUtils"; import TagsSelector from "components/Proposal/Create/SchemeForms/TagsSelector"; import TrainingTooltip from "components/Shared/TrainingTooltip"; -import * as css from "../CreateProposal.scss"; +import * as css from "components/Proposal/Create/CreateProposal.scss"; import MarkdownField from "./MarkdownField"; import HelpButton from "components/Shared/HelpButton"; diff --git a/src/components/Proposal/Create/SchemeForms/CreateSchemeRegistrarProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateSchemeRegistrarProposal.tsx index e74855f3a..446bdbbe2 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateSchemeRegistrarProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateSchemeRegistrarProposal.tsx @@ -17,7 +17,7 @@ import classNames from "classnames"; import { IProposalType, ISchemeState, Scheme } from "@daostack/arc.js"; import { connect } from "react-redux"; import * as React from "react"; -import * as css from "../CreateProposal.scss"; +import * as css from "components/Proposal/Create/CreateProposal.scss"; import MarkdownField from "./MarkdownField"; import HelpButton from "components/Shared/HelpButton"; diff --git a/src/components/Proposal/Create/SchemeForms/CreateUnknownGenericSchemeProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateUnknownGenericSchemeProposal.tsx index 2fecbc7d3..84c4c1532 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateUnknownGenericSchemeProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateUnknownGenericSchemeProposal.tsx @@ -10,7 +10,7 @@ import { baseTokenName, isValidUrl, getNetworkByDAOAddress, getArcByDAOAddress } import { exportUrl, importUrlValues } from "lib/proposalUtils"; import TagsSelector from "components/Proposal/Create/SchemeForms/TagsSelector"; import TrainingTooltip from "components/Shared/TrainingTooltip"; -import * as css from "../CreateProposal.scss"; +import * as css from "components/Proposal/Create/CreateProposal.scss"; import MarkdownField from "./MarkdownField"; import HelpButton from "components/Shared/HelpButton"; diff --git a/src/components/Proposal/Create/SelectProposal/SelectProposal.scss b/src/components/Proposal/Create/SelectProposal/SelectProposal.scss new file mode 100644 index 000000000..df61a6bc5 --- /dev/null +++ b/src/components/Proposal/Create/SelectProposal/SelectProposal.scss @@ -0,0 +1,16 @@ +.body { + width: 100%; +} + +.wrapper { + padding: 20px 30px; +} + +.label { + color: $gray-label; + display: block; + width: fit-content; + font-size: 13px; + font-weight: bold; + padding: 5px 6px 0 0; +} diff --git a/src/components/Proposal/Create/SelectProposal/SelectProposal.tsx b/src/components/Proposal/Create/SelectProposal/SelectProposal.tsx new file mode 100644 index 000000000..53b0e8db4 --- /dev/null +++ b/src/components/Proposal/Create/SelectProposal/SelectProposal.tsx @@ -0,0 +1,87 @@ +import * as React from "react"; +import { useCallback, useEffect, useMemo } from "react"; +import { RouteComponentProps } from "react-router-dom"; + +import { DAO, ISchemeState, Scheme } from "@daostack/arc.js"; + +import { KNOWN_SCHEME_NAMES } from "lib/schemeUtils"; +import Select from "react-select"; + +import SelectProposalLabel from "components/Proposal/Create/SelectProposal/SelectProposalLabel"; +import * as css from "components/Proposal/Create/SelectProposal/SelectProposal.scss"; +import { ISubscriptionProps } from "components/Shared/withSubscription"; + +export interface IExternalSelectProposalProps extends RouteComponentProps<{ daoAvatarAddress: string }>, ISubscriptionProps { + dao: DAO; + daoAvatarAddress: string; + scheme?: ISchemeState; +} + +interface IProps extends IExternalSelectProposalProps { + data: Scheme[]; +} + +export const SelectProposal: React.FC = ({ + scheme, + daoAvatarAddress, + data: schemes, + history, + match, +}) => { + const handleChange = useCallback((el) => { + const schemeId = el.value; + history.push(`/dao/${match.params.daoAvatarAddress}/scheme/${schemeId}/proposals/create`); + }, [daoAvatarAddress, history, match]); + + const knownSchemes = useMemo(() => { + return schemes.filter((scheme: Scheme) => KNOWN_SCHEME_NAMES.indexOf(scheme.staticState.name) >= 0); + }, [schemes]); + + useEffect(() => { + if (!scheme && knownSchemes?.length) { + handleChange({ value: knownSchemes[0].staticState.id }); + } + }, [handleChange, scheme, knownSchemes]); + + const unknownSchemes = useMemo(() => { + return schemes.filter((scheme: Scheme) => KNOWN_SCHEME_NAMES.indexOf(scheme.staticState.name) === -1); + }, [schemes]); + + const options = useMemo(() => { + return knownSchemes.map(({staticState}: Scheme) => ({ + label: staticState, + value: staticState.id, + })); + }, [knownSchemes]); + + const currentOption = useMemo(() => { + if (scheme?.id) { + return options.find(el => el.value === scheme.id); + } + + return undefined; + }, [options, scheme]); + + return ( +
+
+ + Loading...
}> + { const allSchemes = data[0]; const contributionReward = allSchemes.filter((scheme: Scheme) => scheme.staticState.name === "ContributionReward"); - const knownSchemes = allSchemes.filter((scheme: Scheme) => scheme.staticState.name !== "ContributionReward" && KNOWN_SCHEME_NAMES.indexOf(scheme.staticState.name) >= 0); - const unknownSchemes = allSchemes.filter((scheme: Scheme) => KNOWN_SCHEME_NAMES.indexOf(scheme.staticState.name) === -1 ); + const knownSchemes = getKnownSchemes(allSchemes); + const unknownSchemes = getUnknownSchemes(allSchemes); const allKnownSchemes = [...contributionReward, ...knownSchemes]; const schemeManager = data[1]; diff --git a/src/components/Proposal/Create/SelectProposal/SelectProposal.tsx b/src/components/Proposal/Create/SelectProposal/SelectProposal.tsx index 53b0e8db4..090f08a1a 100644 --- a/src/components/Proposal/Create/SelectProposal/SelectProposal.tsx +++ b/src/components/Proposal/Create/SelectProposal/SelectProposal.tsx @@ -4,7 +4,7 @@ import { RouteComponentProps } from "react-router-dom"; import { DAO, ISchemeState, Scheme } from "@daostack/arc.js"; -import { KNOWN_SCHEME_NAMES } from "lib/schemeUtils"; +import { getKnownSchemes, getUnknownSchemes } from "lib/schemeUtils"; import Select from "react-select"; import SelectProposalLabel from "components/Proposal/Create/SelectProposal/SelectProposalLabel"; @@ -34,7 +34,7 @@ export const SelectProposal: React.FC = ({ }, [daoAvatarAddress, history, match]); const knownSchemes = useMemo(() => { - return schemes.filter((scheme: Scheme) => KNOWN_SCHEME_NAMES.indexOf(scheme.staticState.name) >= 0); + return getKnownSchemes(schemes); }, [schemes]); useEffect(() => { @@ -44,7 +44,7 @@ export const SelectProposal: React.FC = ({ }, [handleChange, scheme, knownSchemes]); const unknownSchemes = useMemo(() => { - return schemes.filter((scheme: Scheme) => KNOWN_SCHEME_NAMES.indexOf(scheme.staticState.name) === -1); + return getUnknownSchemes(schemes); }, [schemes]); const options = useMemo(() => { diff --git a/src/lib/schemeUtils.ts b/src/lib/schemeUtils.ts index 305476ce4..f9bc842b5 100644 --- a/src/lib/schemeUtils.ts +++ b/src/lib/schemeUtils.ts @@ -1,7 +1,9 @@ /* eslint-disable no-bitwise */ import { // Address, IContractInfo, - ISchemeState} from "@daostack/arc.js"; + ISchemeState, + Scheme, +} from "@daostack/arc.js"; import { rewarderContractName } from "components/Scheme/ContributionRewardExtRewarders/rewardersProps"; import { GenericSchemeRegistry } from "genericSchemeRegistry"; @@ -51,6 +53,14 @@ export const KNOWN_SCHEME_NAMES = [ "GenericSchemeMultiCall", ]; +export const getKnownSchemes = (schemes: Scheme[]) => { + return (schemes || []).filter((scheme: Scheme) => scheme.staticState.name !== "ContributionReward" && KNOWN_SCHEME_NAMES.indexOf(scheme.staticState.name) >= 0); +}; + +export const getUnknownSchemes = (schemes: Scheme[]) => { + return (schemes || []).filter((scheme: Scheme) => scheme?.staticState?.name === "ContributionReward" || KNOWN_SCHEME_NAMES.indexOf(scheme.staticState.name) === -1); +}; + export const PROPOSAL_SCHEME_NAMES = [ "ContributionReward", "GenericScheme", From 6c9f53848cc2ec1ddc77ce28b9a7567480a6ead3 Mon Sep 17 00:00:00 2001 From: roienatan <34843014+roienatan@users.noreply.github.com> Date: Tue, 15 Dec 2020 16:09:32 +0200 Subject: [PATCH 16/33] add box shadow to schemes select (#2338) --- .../Proposal/Create/SelectProposal/SelectProposal.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Proposal/Create/SelectProposal/SelectProposal.scss b/src/components/Proposal/Create/SelectProposal/SelectProposal.scss index df61a6bc5..51936ecae 100644 --- a/src/components/Proposal/Create/SelectProposal/SelectProposal.scss +++ b/src/components/Proposal/Create/SelectProposal/SelectProposal.scss @@ -4,6 +4,7 @@ .wrapper { padding: 20px 30px; + box-shadow: 0 1px 8px 0 $gray-1; } .label { From d5c66f1f14a1d544108a35cdefd5a060da1db536 Mon Sep 17 00:00:00 2001 From: roienatan <34843014+roienatan@users.noreply.github.com> Date: Tue, 15 Dec 2020 16:42:35 +0200 Subject: [PATCH 17/33] bump version 1.0.4 (#2339) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7982fa5c2..7379e2580 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "alchemy-client", - "version": "1.0.3", + "version": "1.0.4", "description": "An app for collaborative networks (DAOs), based on the DAO stack.", "author": "DAOstack", "license": "GPL-3.0", From 11fb15787cf52fd1a8585666c79d9dec916fcd01 Mon Sep 17 00:00:00 2001 From: Razzwan Date: Tue, 15 Dec 2020 17:56:47 +0200 Subject: [PATCH 18/33] fix: show all known schemes (#2341) --- src/lib/schemeUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/schemeUtils.ts b/src/lib/schemeUtils.ts index f9bc842b5..6f7d6a8ac 100644 --- a/src/lib/schemeUtils.ts +++ b/src/lib/schemeUtils.ts @@ -54,11 +54,11 @@ export const KNOWN_SCHEME_NAMES = [ ]; export const getKnownSchemes = (schemes: Scheme[]) => { - return (schemes || []).filter((scheme: Scheme) => scheme.staticState.name !== "ContributionReward" && KNOWN_SCHEME_NAMES.indexOf(scheme.staticState.name) >= 0); + return (schemes || []).filter((scheme: Scheme) => KNOWN_SCHEME_NAMES.indexOf(scheme?.staticState?.name) >= 0); }; export const getUnknownSchemes = (schemes: Scheme[]) => { - return (schemes || []).filter((scheme: Scheme) => scheme?.staticState?.name === "ContributionReward" || KNOWN_SCHEME_NAMES.indexOf(scheme.staticState.name) === -1); + return (schemes || []).filter((scheme: Scheme) => KNOWN_SCHEME_NAMES.indexOf(scheme?.staticState?.name) === -1); }; export const PROPOSAL_SCHEME_NAMES = [ From 95d80408830cf52f340fc0369dea7e5c3b83b8c9 Mon Sep 17 00:00:00 2001 From: Razzwan Date: Tue, 15 Dec 2020 19:12:46 +0200 Subject: [PATCH 19/33] fix: rerender popup content when changing proposal type (#2342) * fix: rerender popup content when changing proposal type * fix: remove unnesessury code --- src/components/Proposal/Create/CreateProposalPage.tsx | 4 +++- .../Create/SchemeForms/CreateGenericMultiCallProposal.tsx | 8 -------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/components/Proposal/Create/CreateProposalPage.tsx b/src/components/Proposal/Create/CreateProposalPage.tsx index 9c7dc4dc8..54e892845 100644 --- a/src/components/Proposal/Create/CreateProposalPage.tsx +++ b/src/components/Proposal/Create/CreateProposalPage.tsx @@ -180,7 +180,9 @@ export class CreateProposalPage extends React.Component { daoAvatarAddress={daoAvatarAddress} /> {Boolean(!createSchemeComponent || isLoading) &&
} - { createSchemeComponent } +
+ { createSchemeComponent } +
); diff --git a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx index 5b226a6b1..cfd195736 100644 --- a/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx +++ b/src/components/Proposal/Create/SchemeForms/CreateGenericMultiCallProposal.tsx @@ -135,14 +135,6 @@ class CreateGenericMultiCallScheme extends React.Component }; } - componentDidUpdate(prevProps: Readonly) { - if (prevProps.whitelistedContracts !== this.props.whitelistedContracts) { - this.setState({ - whitelistedContracts: this.props.whitelistedContracts?.map(contract => { return contract.toLowerCase(); }) ?? [], - }); - } - } - public async handleSubmit(formValues: IFormValues, { setSubmitting }: any): Promise { if (!await enableWalletProvider({ showNotification: this.props.showNotification }, getNetworkByDAOAddress(this.props.daoAvatarAddress))) { return; } From 6546f341bbc76dab470dbe7c54bda9d8595cadd7 Mon Sep 17 00:00:00 2001 From: roienatan <34843014+roienatan@users.noreply.github.com> Date: Wed, 16 Dec 2020 17:44:16 +0200 Subject: [PATCH 20/33] Some release 1.0.4 issues (#2343) * some release 1.0.4 issues * better UI to reedem label and execute calls button * comment (3) * add polling to the new proposals page ; fix comment (9): updated condition to Passing and Failing statuses --- src/components/Account/Account.scss | 1 - src/components/Dao/Dao.scss | 52 ------------------- src/components/Dao/DaoContainer.tsx | 10 +--- src/components/Dao/DaoProposalsPage.scss | 2 +- src/components/Dao/DaoProposalsPage.tsx | 6 +-- src/components/Daos/Daos.scss | 13 ++--- src/components/Daos/DaosPage.tsx | 2 - src/components/Proposal/ActionButton.scss | 4 +- .../Proposal/Create/CreateProposalPage.tsx | 12 ----- .../Redemptions/RedemptionsMenu.tsx | 2 +- .../Redemptions/RedemptionsPage.tsx | 2 +- src/components/Shared/ModalPopup.scss | 6 +++ src/components/Shared/ModalPopup.tsx | 21 ++------ src/components/Shared/PreTransactionModal.tsx | 2 +- src/layouts/App.scss | 17 ------ src/layouts/AppContainer.tsx | 1 - src/layouts/Header.tsx | 6 +-- src/layouts/SidebarMenu.scss | 6 +-- src/lib/proposalHelpers.ts | 5 +- 19 files changed, 27 insertions(+), 143 deletions(-) diff --git a/src/components/Account/Account.scss b/src/components/Account/Account.scss index b26397363..f85f1d993 100644 --- a/src/components/Account/Account.scss +++ b/src/components/Account/Account.scss @@ -54,7 +54,6 @@ a.detailView { .accountInfo { position: absolute; - z-index: 0; opacity: 0; left: 50%; top: -150px; diff --git a/src/components/Dao/Dao.scss b/src/components/Dao/Dao.scss index 0fabec4ad..78a8a3efc 100644 --- a/src/components/Dao/Dao.scss +++ b/src/components/Dao/Dao.scss @@ -6,7 +6,6 @@ button:hover { flex-grow: 2; padding: 30px 50px 100px 50px; overflow: visible; - z-index: 0; position: relative; } @@ -109,44 +108,6 @@ button:hover { } } -.noticeWrapper { - width: 100%; - position: fixed; - display: flex; - height: 40px; - bottom: 0; - left: 0; - z-index: 100000; -} - -.noticeBuffer { - min-width: 250px; - max-width: 250px; - flex-grow: 1; - height: 100%; -} - -.notice { - flex-grow: 2; - background-color: white; - padding: 11px 8px; - color: $black; - font-size: 12px; - font-weight: 500; - text-align: center; - font-family: "Open Sans"; - width: 100%; - - a { - text-decoration: underline; - } - - .notice::after { - content: ""; - clear: both; - display: table; - } -} .membersContainer { margin: 10px auto; @@ -275,19 +236,6 @@ a { padding: 50px 0 100px 0; } - .noticeWrapper { - height: 52px; - - .noticeBuffer { - display: none; - } - - .notice { - font-size: 11px; - box-shadow: 0 2px 16px 0 rgba(133, 168, 208, 0.4); - } - } - .reputationAmounts { position: absolute; right: 10px; diff --git a/src/components/Dao/DaoContainer.tsx b/src/components/Dao/DaoContainer.tsx index b2bdd6721..481526551 100644 --- a/src/components/Dao/DaoContainer.tsx +++ b/src/components/Dao/DaoContainer.tsx @@ -20,7 +20,7 @@ import DaoSchemesPage from "./DaoSchemesPage"; import DaoMembersPage from "./DaoMembersPage"; import * as css from "./Dao.scss"; import DaoProposalsPage from "components/Dao/DaoProposalsPage"; -import { standardPolling, targetedNetwork, getArcByDAOAddress, getDAONameByID, getNetworkByDAOAddress } from "lib/util"; +import { standardPolling, getArcByDAOAddress, getDAONameByID, getNetworkByDAOAddress } from "lib/util"; import gql from "graphql-tag"; import { enableWalletProvider, getArcs } from "arc"; import { getKnownSchemes } from "lib/schemeUtils"; @@ -173,7 +173,6 @@ class DaoContainer extends React.Component { public render(): RenderOutput { const daoState = this.props.data[0]; - const network = targetedNetwork(); const { followingDaosAddresses } = this.props; const { memberDaos } = this.state; @@ -206,13 +205,6 @@ class DaoContainer extends React.Component {
- diff --git a/src/components/Dao/DaoProposalsPage.scss b/src/components/Dao/DaoProposalsPage.scss index 8cd2e8f99..151b7a592 100644 --- a/src/components/Dao/DaoProposalsPage.scss +++ b/src/components/Dao/DaoProposalsPage.scss @@ -59,7 +59,7 @@ border: none; font-family: "AccordBold", sans-serif; height: 30px; - width: 500px; + width: 400px; font-size: 15px; color: #4f6176; border-radius: 20px; diff --git a/src/components/Dao/DaoProposalsPage.tsx b/src/components/Dao/DaoProposalsPage.tsx index 72b29ed24..dabfe6242 100644 --- a/src/components/Dao/DaoProposalsPage.tsx +++ b/src/components/Dao/DaoProposalsPage.tsx @@ -9,7 +9,7 @@ import { Observable } from "rxjs"; import ProposalRow from "components/Proposal/ProposalRow"; import { RouteComponentProps } from "react-router-dom"; import { first } from "rxjs/operators"; -import { isAddress } from "lib/util"; +import { isAddress, standardPolling } from "lib/util"; const PAGE_SIZE = 50; @@ -40,7 +40,7 @@ const proposalsQuery = (dao: IDAOState, skip: number, titleSearch?: string): Obs orderDirection: "asc", first: titleSearch ? undefined : PAGE_SIZE, // TEMPORARY UNTIL WE PASS "titleSearch" in line 143 skip, - }, { fetchAllData: true }); + }, standardPolling(true)); }; const DaoProposalsPage = (props: IProps) => { @@ -86,7 +86,7 @@ const DaoProposalsPage = (props: IProps) => { )}
{data.length > 0 &&
- setFilterString(e.target.value)} /> diff --git a/src/components/Daos/Daos.scss b/src/components/Daos/Daos.scss index 57db0b360..ee14762ea 100644 --- a/src/components/Daos/Daos.scss +++ b/src/components/Daos/Daos.scss @@ -4,20 +4,13 @@ display: flex; flex-direction: column; - .paddingTop { - position: sticky; - top: 82px; - height: 35px; - z-index: 2; - background-color: #e5ebf2; - } - .topRow { display: flex; position: sticky; - top: 117px; + top: 50px; z-index: 2; background-color: rgba(229, 235, 242, 1); + padding-top: 30px; .searchBox { flex-grow: 2; @@ -56,7 +49,7 @@ .otherDaos { .headerWrapper { position: sticky; - top: 170px; + top: 132px; z-index: 1; } diff --git a/src/components/Daos/DaosPage.tsx b/src/components/Daos/DaosPage.tsx index fa1f4dd26..99d00aeb9 100644 --- a/src/components/Daos/DaosPage.tsx +++ b/src/components/Daos/DaosPage.tsx @@ -198,8 +198,6 @@ class DaosPage extends React.Component {
All DAOs -
 
-
diff --git a/src/components/Proposal/ActionButton.scss b/src/components/Proposal/ActionButton.scss index 0d025462e..3a9fcd9c0 100644 --- a/src/components/Proposal/ActionButton.scss +++ b/src/components/Proposal/ActionButton.scss @@ -35,9 +35,6 @@ width: 67px; } - &.expanded button.executeButton { - width: 86px; - } } .wrapper.detailView { @@ -67,6 +64,7 @@ div.tooltipWhenDisabledContainer { display: "inline-block"; cursor: "not-allowed"; + margin: 0px 5px; } .redeemButton { diff --git a/src/components/Proposal/Create/CreateProposalPage.tsx b/src/components/Proposal/Create/CreateProposalPage.tsx index 54e892845..2b91d1a48 100644 --- a/src/components/Proposal/Create/CreateProposalPage.tsx +++ b/src/components/Proposal/Create/CreateProposalPage.tsx @@ -63,7 +63,6 @@ export class CreateProposalPage extends React.Component { } public async componentDidMount() { - document.addEventListener("keydown", this.handleKeyPress, false); Analytics.track("Page View", { "Page Name": Page.CreateProposal, @@ -91,17 +90,6 @@ export class CreateProposalPage extends React.Component { } } - public componentWillUnmount(){ - document.removeEventListener("keydown", this.handleKeyPress, false); - } - - private handleKeyPress = (e: any) => { - // Close modal on ESC key press - if (e.keyCode === 27) { - this.doClose(); - } - } - private getCreateSchemeComponent = (): [JSX.Element, string] => { const { daoAvatarAddress, currentAccountAddress } = this.props; diff --git a/src/components/Redemptions/RedemptionsMenu.tsx b/src/components/Redemptions/RedemptionsMenu.tsx index d39738fd3..b3a1051c9 100644 --- a/src/components/Redemptions/RedemptionsMenu.tsx +++ b/src/components/Redemptions/RedemptionsMenu.tsx @@ -83,7 +83,7 @@ class RedemptionsMenu extends React.Component { disabled={redeemableProposals.length === 0} > - Redeem all from {this.props.network} {redeemableProposals.length > 0 ? redeemableProposals.length : ""} + Redeem {this.props.network && `all from ${this.props.network}`} ({redeemableProposals.length})
; diff --git a/src/components/Redemptions/RedemptionsPage.tsx b/src/components/Redemptions/RedemptionsPage.tsx index 516f20f6f..8a6588a38 100644 --- a/src/components/Redemptions/RedemptionsPage.tsx +++ b/src/components/Redemptions/RedemptionsPage.tsx @@ -90,7 +90,7 @@ class RedemptionsPage extends React.Component { onClick={this.redeemAll} > - Redeem all from {this.props.network} + Redeem {this.props.network && `all from ${this.props.network}`}
: "" diff --git a/src/components/Shared/ModalPopup.scss b/src/components/Shared/ModalPopup.scss index 184eb4fa1..e2ad31b4d 100644 --- a/src/components/Shared/ModalPopup.scss +++ b/src/components/Shared/ModalPopup.scss @@ -1,3 +1,9 @@ +:global { + body.react-router-modal__modal-open { + overflow: hidden; + } +} + .modalWindow { position: absolute; left: 50%; diff --git a/src/components/Shared/ModalPopup.tsx b/src/components/Shared/ModalPopup.tsx index a24069e06..fee5c8541 100644 --- a/src/components/Shared/ModalPopup.tsx +++ b/src/components/Shared/ModalPopup.tsx @@ -12,27 +12,12 @@ interface IProps { export default class ModalPopup extends React.Component { - public async componentDidMount() { - document.addEventListener("keydown", this.handleKeyPress, false); - } - - public componentWillUnmount() { - document.removeEventListener("keydown", this.handleKeyPress, false); - } - - private handleKeyPress = (e: any) => { - // Close modal on ESC key press - if (e.keyCode === 27) { - this.props.closeHandler(e); - } - } - public render(): RenderOutput { - const { closeHandler, body, footer, header, width } = this.props; + const { body, footer, header } = this.props; return ( - -
+ +
{header}
{body}
{footer ?
{footer}
: ""} diff --git a/src/components/Shared/PreTransactionModal.tsx b/src/components/Shared/PreTransactionModal.tsx index 37be9db23..90ec031fc 100644 --- a/src/components/Shared/PreTransactionModal.tsx +++ b/src/components/Shared/PreTransactionModal.tsx @@ -295,7 +295,7 @@ class PreTransactionModal extends React.Component { } return ( - +
diff --git a/src/layouts/App.scss b/src/layouts/App.scss index 2c5063a52..73e35478e 100644 --- a/src/layouts/App.scss +++ b/src/layouts/App.scss @@ -547,15 +547,6 @@ body { } } -.headerContainer.showBanner { - .banner { - display: block; - } - .header { - top: 32px; - } -} - .pendingTransactions { z-index: 1000000; position: fixed; @@ -576,14 +567,6 @@ body { left: 0; width: 100%; height: 100%; -} - -.backdrop { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; background-color: rgba(0, 0, 0, 0.4); } diff --git a/src/layouts/AppContainer.tsx b/src/layouts/AppContainer.tsx index ff24d144e..05eb27ea2 100644 --- a/src/layouts/AppContainer.tsx +++ b/src/layouts/AppContainer.tsx @@ -254,7 +254,6 @@ class AppContainer extends React.Component {
diff --git a/src/layouts/Header.tsx b/src/layouts/Header.tsx index 412ad3bc2..d82151c8a 100644 --- a/src/layouts/Header.tsx +++ b/src/layouts/Header.tsx @@ -179,13 +179,9 @@ class Header extends React.Component { const web3ProviderInfo = getWeb3ProviderInfo(); const web3Provider = getWeb3Provider(); const trainingTooltipsOn = this.getTrainingTooltipsEnabled(); - const inDAO = !!daoAvatarAddress; return ( -
-
Alchemy 2.0 has been released! Take a look here.
+