From 0910fc9ca8d1009465c5cd08fcc9e98202c70016 Mon Sep 17 00:00:00 2001 From: Efe <hbehar@syncad.com> Date: Wed, 27 May 2020 15:00:30 +0200 Subject: [PATCH 1/4] Modify proposals page - Add filters for proposals - Add sorters for proposals - Proposal page style tweaks --- .../modules/ProposalList/Proposal.jsx | 158 +++++------ .../modules/ProposalList/ProposalList.jsx | 154 +++++++--- .../ProposalList/ProposalListContainer.jsx | 7 +- .../modules/ProposalList/styles.scss | 264 +++++++++++------- src/app/components/pages/Proposals.jsx | 53 +++- src/app/locales/en.json | 39 ++- src/app/redux/ProposalSaga.js | 26 +- 7 files changed, 435 insertions(+), 266 deletions(-) diff --git a/src/app/components/modules/ProposalList/Proposal.jsx b/src/app/components/modules/ProposalList/Proposal.jsx index f0391861..2cf55671 100644 --- a/src/app/components/modules/ProposalList/Proposal.jsx +++ b/src/app/components/modules/ProposalList/Proposal.jsx @@ -1,16 +1,18 @@ +/* eslint-disable react/jsx-one-expression-per-line */ import React from 'react'; import PropTypes from 'prop-types'; import Moment from 'moment'; import NumAbbr from 'number-abbreviate'; import tt from 'counterpart'; -import Userpic from 'app/components/elements/Userpic'; +import cx from 'classnames'; +import Userpic, { SIZE_SMALL } from 'app/components/elements/Userpic'; import { numberWithCommas } from 'app/utils/StateFunctions'; import Icon from 'app/components/elements/Icon'; const numAbbr = new NumAbbr(); -export default function Proposal(props) { +export function Proposal(props) { const { id, start_date, @@ -33,25 +35,76 @@ export default function Proposal(props) { const start = new Date(start_date); const end = new Date(end_date); const durationInDays = Moment(end).diff(Moment(start), 'days'); - const totalPayout = durationInDays * daily_pay.split(' HBD')[0]; // ¯\_(ツ)_/¯ + const totalPayout = durationInDays * daily_pay.split(' ')[0]; // ¯\_(ツ)_/¯ + const votesToHP = simpleVotesToHp( + total_votes, + total_vesting_shares, + total_vesting_fund_hive + ); + + const classUp = cx('Voting__button', 'Voting__button-up', { + 'Voting__button--upvoted': isUpVoted, + 'Voting__button--downvoted': voteFailed, + votingUp: isVoting, + }); - const classUp = - 'Voting__button Voting__button-up' + - (isUpVoted ? ' Voting__button--upvoted' : '') + - (voteFailed ? ' Voting__button--downvoted' : '') + - (isVoting ? ' votingUp' : ''); return ( - <div className="proposals__row"> + <div className="proposals__item"> + <div className="proposals__content"> + <a + className="proposals__row title" + href={urlifyPermlink(creator, permlink)} + target="_blank" + rel="noopener noreferrer" + alt={startedOrFinishedInWordsLongVersion(start, end)} + title={startedOrFinishedInWordsLongVersion(start, end)} + > + {subject} <span className="id">#{id}</span> + </a> + <div className="proposals__row description"> + <div className="date"> + {formatDate(start)} - {formatDate(end)} ( + {durationInDays} {tt('proposals.days')}) + </div> + <div className="amount"> + <span title={formatCurrency(totalPayout)}> + {abbreviateNumber(totalPayout)} HBD + </span> + ( + {tt('proposals.daily')} + {abbreviateNumber(daily_pay.split(' ')[0])} HBD) + </div> + <span + className="status" + title={startedOrFinishedInWordsLongVersion(start, end)} + > + {startedOrFinished(start, end)} + </span> + </div> + <div className="proposals__row details"> + <Userpic account={creator} size={SIZE_SMALL} /> + <div className="creator"> + {tt('proposals.by')} {linkifyUsername(creator)} + {creator != receiver + ? ` ${tt('proposals.for')} ` + : null} + {creator != receiver + ? linkifyUsername( + checkIfSameUser( + creator, + receiver, + 'themselves.' + ), + receiver + ) + : null} + </div> + </div> + </div> <div className="proposals__votes"> - <span> - {abbreviateNumber( - simpleVotesToHp( - total_votes, - total_vesting_shares, - total_vesting_fund_hive - ) - )} - </span> + <div title={`${votesToHP} HP`}> + {abbreviateNumber(votesToHP)} + </div> <a onClick={onVote}> <span className={classUp}> <Icon @@ -61,69 +114,6 @@ export default function Proposal(props) { </span> </a> </div> - <div className="proposals__avatar"> - <Userpic account={creator} /> - </div> - <div className="proposals__description"> - <span> - <a - href={urlifyPermlink(creator, permlink)} - target="_blank" - alt={startedOrFinishedInWordsLongVersion(start, end)} - title={startedOrFinishedInWordsLongVersion(start, end)} - rel="noreferrer noopener" - > - {subject} - <span - className="proposals__statusTag" - title={startedOrFinishedInWordsLongVersion( - start, - end - )} - > - {startedOrFinished(start, end)} - </span> - </a> - </span> - <br /> - <small className="date"> - {tt('proposals.startEndDates', { - start: formatDate(start), - end: formatDate(end), - })} - </small> - <br /> - <small> - {tt('proposals.proposalId', { id })} {tt('proposals.by')}{' '} - {linkifyUsername(creator)} - {creator !== receiver && ( - <span> - {' '} - {tt('proposals.for')} {linkifyUsername(receiver)} - </span> - )} - </small> - </div> - <div className="proposals__amount"> - <span> - <a href="#" title={formatCurrency(totalPayout)}> - <em> - {tt('proposals.amountHbd', { - amount: abbreviateNumber(totalPayout), - })} - </em> - </a> - </span> - <small> - {tt('proposals.dailyAmount', { - amount: abbreviateNumber(daily_pay.split(' HBD')[0]), - })} - <br /> - {tt('proposals.duration', { - duration: durationInDays, - })} - </small> - </div> </div> ); } @@ -152,7 +142,7 @@ Proposal.propTypes = { * @returns {string} - return a fancy string */ function formatCurrency(amount = 0) { - return numberWithCommas(Number.parseFloat(amount).toFixed(2) + 'HBD'); + return numberWithCommas(Number.parseFloat(amount).toFixed(2) + ' HBD'); } /** @@ -284,8 +274,10 @@ function simpleVotesToHp( total_vesting_fund_hive ) { const total_vests = parseFloat(total_vesting_shares); - const total_vest_steem = parseFloat(total_vesting_fund_hive); + const total_vest_steem = parseFloat(total_vesting_fund_hive || 0); return (total_vest_steem * (total_votes / total_vests) * 0.000001).toFixed( 2 ); } + +export default Proposal; diff --git a/src/app/components/modules/ProposalList/ProposalList.jsx b/src/app/components/modules/ProposalList/ProposalList.jsx index 20f4b55e..586bc8d7 100644 --- a/src/app/components/modules/ProposalList/ProposalList.jsx +++ b/src/app/components/modules/ProposalList/ProposalList.jsx @@ -1,28 +1,107 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ import React from 'react'; import PropTypes from 'prop-types'; import tt from 'counterpart'; +import cx from 'classnames'; import LoadingIndicator from 'app/components/elements/LoadingIndicator'; import ProposalContainer from './ProposalContainer'; export default function ProposalList(props) { - const { proposals, voteOnProposal, loading } = props; - const proposalCount = proposals.length; + const { + proposals, + voteOnProposal, + loading, + onFilter, + onOrder, + onOrderDirection, + status, + orderBy, + orderDirection, + } = props; - if (!loading && proposalCount == 0) { - return ( - <div className="ProposalsList"> - <center> - <h5> - {tt('proposals.sorry_cannot_display')} - <br /> - <small>{tt('proposals.no_proposal_matching')}</small> - </h5> - </center> + return ( + <div className="ProposalsList"> + <div className="proposals__header"> + <div className="proposals__title">{tt('proposals.title')}</div> + <div className="proposals__filters"> + <label className="proposals__select"> + {tt('proposals.status')} + <select + defaultValue={status} + value={status} + onChange={(e) => { + onFilter(e.target.value); + }} + > + <option value="all"> + {tt('proposals.status_options.all')} + </option> + <option value="active"> + {tt('proposals.status_options.active')} + </option> + <option value="inactive"> + {tt('proposals.status_options.inactive')} + </option> + <option value="expired"> + {tt('proposals.status_options.expired')} + </option> + <option value="votable"> + {tt('proposals.status_options.votable')} + </option> + </select> + </label> + <label className="proposals__select"> + {tt('proposals.order')} + <select + defaultValue={orderBy} + value={orderBy} + onChange={(e) => { + onOrder(e.target.value); + }} + > + <option value="by_creator"> + {tt('proposals.order_options.creator')} + </option> + <option value="by_start_date"> + {tt('proposals.order_options.start_date')} + </option> + <option value="by_end_date"> + {tt('proposals.order_options.end_date')} + </option> + <option value="by_total_votes"> + {tt('proposals.order_options.total_votes')} + </option> + </select> + </label> + <div + role="button" + className="proposals__order_direction" + onClick={() => { + const d = + orderDirection === 'ascending' + ? 'descending' + : 'ascending'; + onOrderDirection(d); + }} + > + <div + className={cx('direction', { + active: orderDirection === 'ascending', + })} + > + ↑ + </div> + <div + className={cx('direction', { + active: orderDirection === 'descending', + })} + > + ↓ + </div> + </div> + </div> </div> - ); - } else if (loading && proposals.length == 0) { - return ( - <div className="ProposalsList"> + {loading ? ( <center> <span> <LoadingIndicator type="circle" /> @@ -33,25 +112,16 @@ export default function ProposalList(props) { <small>{tt('proposals.wait_for_proposal_load')}</small> </h5> </center> - </div> - ); - } - - return ( - <div className="ProposalsList"> - <div className="proposals__header"> - <div className="proposals__votes"> - {tt('proposals.vote_hp')} - </div> - <div className="proposals__avatar"> </div> - <div className="proposals__description"> - {tt('proposals.proposal')} - </div> - <div className="proposals__amount"> - {tt('proposals.amount')} - </div> - </div> - {proposals.map(proposal => ( + ) : proposals.length == 0 ? ( + <center> + <h5> + {tt('proposals.sorry_cannot_display')} + <br /> + <small>{tt('proposals.no_proposal_matching')}</small> + </h5> + </center> + ) : null} + {proposals.map((proposal) => ( <ProposalContainer key={proposal.id} voteOnProposal={voteOnProposal} @@ -66,4 +136,18 @@ ProposalList.propTypes = { proposals: PropTypes.array.isRequired, //TODO: Specify Shape voteOnProposal: PropTypes.func.isRequired, loading: PropTypes.bool.isRequired, + onFilter: PropTypes.func.isRequired, + onOrder: PropTypes.func.isRequired, + onOrderDirection: PropTypes.func.isRequired, + onToggleFilters: PropTypes.func.isRequired, + showFilters: PropTypes.bool.isRequired, + status: PropTypes.oneOf(['all', 'active', 'inactive', 'expired', 'votable']) + .isRequired, + orderBy: PropTypes.oneOf([ + 'by_creator', + 'by_start_date', + 'by_end_date', + 'by_total_votes', + ]).isRequired, + orderDirection: PropTypes.oneOf(['ascending', 'descending']).isRequired, }; diff --git a/src/app/components/modules/ProposalList/ProposalListContainer.jsx b/src/app/components/modules/ProposalList/ProposalListContainer.jsx index 6427976a..1cc03f5b 100644 --- a/src/app/components/modules/ProposalList/ProposalListContainer.jsx +++ b/src/app/components/modules/ProposalList/ProposalListContainer.jsx @@ -4,10 +4,6 @@ import PropTypes from 'prop-types'; import ProposalList from './ProposalList'; class ProposalListContainer extends React.Component { - constructor(props) { - super(props); - } - render() { return <ProposalList {...this.props} />; } @@ -17,6 +13,9 @@ ProposalList.propTypes = { proposals: PropTypes.array.isRequired, // TODO: Specify shape. voteOnProposal: PropTypes.func.isRequired, loading: PropTypes.bool.isRequired, + onFilter: PropTypes.func.isRequired, + onOrder: PropTypes.func.isRequired, + onOrderDirection: PropTypes.func.isRequired, }; export default ProposalListContainer; diff --git a/src/app/components/modules/ProposalList/styles.scss b/src/app/components/modules/ProposalList/styles.scss index 7c0702c6..e39797bd 100644 --- a/src/app/components/modules/ProposalList/styles.scss +++ b/src/app/components/modules/ProposalList/styles.scss @@ -1,4 +1,6 @@ .ProposalsList { + max-width: 1000px; + width: 100%; flex-direction: column; display: flex; padding-top: 20px; @@ -8,156 +10,214 @@ } @media only screen and (max-width: 768px) { width: 98%; + padding: 0.5rem; } margin-left: auto; margin-right: auto; .proposals__header { - flex-direction: row; - justify-content: center; display: flex; + justify-content: space-between; + align-items: flex-end; + margin-bottom: 1em; - @media only screen and (max-width: 768px) { - padding-left: 1em; + .proposals__title { + font-size: 1.5rem !important; + font-weight: bold; } - .proposals__votes { + .proposals__filters { display: flex; - align-items: center; - max-width: 200px; - width: 15%; - @media only screen and (max-width: 768px) { - justify-content: center; - width: 20%; + justify-content: flex-end; + + .proposals__select { + display: flex; + flex-direction: column; + font-size: 0.7rem; + font-weight: bold; + padding-top: 3px; + margin-right: 0.5rem; + select { + width: fit-content; + + &:not(:last-child) { + margin-right: 0.5em; + } + + @media only screen and (max-width: 768px) { + height: 2rem; + padding: 0.3rem; + } + } } - } - .proposals__amount { - display: flex; - align-items: center; - max-width: 200px; - width: 20%; - flex-direction: row; - justify-content: flex-start; - } - .proposals__description { - display: flex; - align-items: center; - max-width: 800px; - width: 60%; - } - .proposals__avatar { - width: 65px; + .proposals__order_direction { + cursor: pointer; + display: flex; + align-items: flex-end; + + &:hover { + opacity: 0.8; + } + + .direction { + font-size: 1.7rem; + + &.active { + color: $color-red; + } + } + } } } - .proposals__row { + + .proposals__item { @include themify($themes) { border-radius: themed('roundedCorners'); - border: themed('border'); background-color: themed('moduleBackgroundColor'); } - padding: 10px; + box-shadow: 1px 1px 5px 1px rgba(0, 0, 0, 0.05); margin-bottom: 15px; - flex-direction: row; - justify-content: center; display: flex; @media only screen and (max-width: 768px) { + flex-direction: column; padding-top: 0.7em; padding-bottom: 0.7em; } - small { - @media only screen and (max-width: 768px) { - font-size: 0.7em; + .proposals__content, + .proposals__votes { + display: flex; + padding: 10px; + } + + .proposals__content { + flex: 0.9; + flex-direction: column; + + .title { + font-size: 1.4em; + display: block; + + &:hover { + opacity: 0.7; + } + + .id { + font-weight: bold; + @include themify($themes) { + color: themed('textColorSecondary'); + } + } } - @include themify($themes) { - color: themed('textColorSecondary'); + + .description { + display: flex; + margin: 0.5rem 0; + + @include themify($themes) { + color: themed('textColorSecondary'); + } + + .date { + margin-right: 1rem; + min-width: 250px; + } + + .amount { + span { + font-weight: bold; + @include themify($themes) { + color: themed('colorAccent'); + } + } + } + + .status { + font-weight: 400; + display: flex; + align-items: center; + padding: 0 0.2rem; + margin: 0 1rem 0 0.5rem; + border-radius: 0.3rem; + font-size: 0.7rem; + @include themify($themes) { + border: 1px solid themed('colorAccent'); + color: themed('colorAccent'); + } + } + + @media only screen and (max-width: 768px) { + flex-direction: column; + align-items: flex-start; + + .amount { + margin: 0.5rem 0; + } + + .status { + margin: 0; + margin-bottom: 0.5rem; + } + } } - } - .proposals__avatar { - overflow: hidden; - width: 65px; - height: 65px; - padding-left: 15px; + .details { + display: flex; + align-items: center; - @media only screen and (max-width: 768px) { - width: 90px; + .Userpic { + height: 30px; + width: 30px; + margin-right: 0.5rem; + } } } .proposals__votes { + flex: 0.1; + position: relative; display: flex; + flex-direction: column; + justify-content: space-around; align-items: center; - max-width: 200px; - @include themify($themes) { - border-right: themed('border'); + + &::before { + position: absolute; + content: ''; + background: rgba($color: #000000, $alpha: 0.15); + left: 0; + height: 90%; + width: 1px; + top: 50%; + transform: translateY(-50%); } - @media only screen and (min-width: 768px) { + div { font-size: 1.5em; - flex-direction: column; - justify-content: flex-start; - width: 15%; - } - @media only screen and (max-width: 768px) { - font-size: 0.8em; - flex-direction: row-reverse; - justify-content: center; - align-items: flex-start; - padding-top: 0.1em; - width: 20%; } a { - padding-right: 10px; - } - } - .proposals__amount { - display: flex; - max-width: 200px; - width: 20%; - flex-direction: column; - align-items: flex-start; - padding-left: 15px; - @media only screen and (max-width: 768px) { - padding-left: 7px; + span, svg { + height: 1.5rem !important; + width: 1.5rem !important; + } } - } - .proposals__description { - max-width: 800px; - width: 60%; - padding-left: 15px; @media only screen and (max-width: 768px) { - padding-left: 7px; - } - @include themify($themes) { - border-right: themed('border'); - } - .date { - @include themify($themes) { - color: themed('textColorSecondary'); - } - @media only screen and (max-width: 768px) { - font-size: 0.7em; + flex-direction: row; + justify-content: space-between; + + &::before { + height: 1px; + width: calc(100% - 20px); + top: 0; + left: 50%; + transform: translateX(-50%); } } } } - .proposals__statusTag { - font-weight: 400; - display: inline; - padding: 0.1rem 0.2rem; - margin: 0 1rem 0 0.5rem; - border-radius: 0.3rem; - font-size: 0.7rem; - vertical-align: 15%; - @include themify($themes) { - background-color: themed('colorAccent'); - color: themed('buttonText'); - } - } } diff --git a/src/app/components/pages/Proposals.jsx b/src/app/components/pages/Proposals.jsx index ced6e8cd..0295448b 100644 --- a/src/app/components/pages/Proposals.jsx +++ b/src/app/components/pages/Proposals.jsx @@ -16,24 +16,31 @@ class Proposals extends React.Component { limit: 10, last_proposal: false, status: 'all', + order_by: 'by_total_votes', + order_direction: 'descending', }; } async componentWillMount() { await this.load(); } - async load(quiet = false) { + async load(quiet = false, options = {}) { if (quiet) { this.setState({ loading: true }); } + // eslint-disable-next-line react/destructuring-assignment + const { status, order_by, order_direction } = options; + + console.log(options); + const proposals = (await this.getAllProposals( this.state.last_proposal, - 'by_total_votes', - 'descending', + order_by || this.state.order_by, + order_direction || this.state.order_direction, this.state.limit + this.state.proposals.length, - this.state.status + status || this.state.status )) || []; let last_proposal = false; @@ -48,6 +55,21 @@ class Proposals extends React.Component { }); } + onFilterProposals = async (status) => { + this.setState({ status }); + await this.load(false, { status }); + }; + + onOrderProposals = async (order_by) => { + this.setState({ order_by }); + await this.load(false, { order_by }); + }; + + onOrderDirection = async (order_direction) => { + this.setState({ order_direction }); + await this.load(false, { order_direction }); + }; + getAllProposals(last_proposal, order_by, order_direction, limit, status) { return this.props.listProposals({ voter_id: this.props.currentUser, @@ -73,13 +95,19 @@ class Proposals extends React.Component { ); }; - onClickLoadMoreProposals = e => { + onClickLoadMoreProposals = (e) => { e.preventDefault(); this.load(); }; render() { - const { proposals, loading } = this.state; + const { + proposals, + loading, + status, + order_by, + order_direction, + } = this.state; let showBottomLoading = false; if (loading && proposals && proposals.length > 0) { showBottomLoading = true; @@ -90,6 +118,12 @@ class Proposals extends React.Component { voteOnProposal={this.voteOnProposal} proposals={proposals} loading={loading} + onFilter={this.onFilterProposals} + onOrder={this.onOrderProposals} + onOrderDirection={this.onOrderDirection} + status={status} + orderBy={order_by} + orderDirection={order_direction} /> <center style={{ paddingTop: '1em', paddingBottom: '1em' }}> {!loading ? ( @@ -115,7 +149,7 @@ Proposals.propTypes = { module.exports = { path: 'proposals', component: connect( - state => { + (state) => { const user = state.user.get('current'); const currentUser = user && user.get('username'); const proposals = state.proposal.get('proposals', List()); @@ -131,7 +165,7 @@ module.exports = { last_id, }; }, - dispatch => { + (dispatch) => { return { voteOnProposal: ( voter, @@ -195,7 +229,8 @@ module.exports = { }) ); }, - listProposals: payload => { + listProposals: (payload) => { + console.log(payload); return new Promise((resolve, reject) => { dispatch( proposalActions.listProposals({ diff --git a/src/app/locales/en.json b/src/app/locales/en.json index cd5b9f9e..e2a514c1 100644 --- a/src/app/locales/en.json +++ b/src/app/locales/en.json @@ -768,22 +768,6 @@ "hbd_symbol": "HBD", "hbd_description": "Seeks price stability with USD" }, - "hive_proposals": { - "top_sps": "Hive Proposals", - "votes_update_info": "* Only updated once per hour", - "table": { - "id": "Id", - "creator": "Creator", - "receiver": "Receiver", - "start_date": "Start Date", - "end_date": "End Date", - "daily_pay": "Daily Pay", - "total_votes": "Total Votes*", - "permlink": "", - "subject": "Subject" - }, - "confirm_remove_proposal_description": "Proposal removal is permanent and the fee paid for creating the proposal is not refundable." - }, "proposals": { "wait_for_proposal_load": "The proposals are now being loaded, it will be worth the wait!", "sorry_cannot_display": "Sorry, I can't show you any proposals right now.", @@ -794,9 +778,24 @@ "amountHbd": "Amount %(amount)s HBD", "by": "by", "for": "for", - "proposalId": "Proposal #%(id)s", - "duration": "Duration: %(duration)s days", - "dailyAmount": "Daily: %(amount)s HBD", - "startEndDates": "%(start)s through %(end)s" + "daily": "Daily", + "duration": "Duration", + "days": "days", + "title": "Proposals", + "status": "Status", + "order": "Order by", + "status_options": { + "all": "All", + "active": "Active", + "inactive": "Inactive", + "expired": "Expired", + "votable": "Votable" + }, + "order_options": { + "creator": "Creator", + "start_date": "Start Date", + "end_date": "End Date", + "total_votes": "Total Votes" + } } } diff --git a/src/app/redux/ProposalSaga.js b/src/app/redux/ProposalSaga.js index d32867c0..fb9fa0d1 100644 --- a/src/app/redux/ProposalSaga.js +++ b/src/app/redux/ProposalSaga.js @@ -29,7 +29,7 @@ export function* listProposals({ resolve, reject, }) { - const start = [-1, 0]; + const start = []; const proposals = yield call( [api, api.listProposalsAsync], @@ -40,14 +40,14 @@ export function* listProposals({ status ); - const proposalIds = proposals.map(p => { + const proposalIds = proposals.map((p) => { return p.id; }); let proposalVotesIds = []; if (voter_id) { - let proposalVotes = yield proposalIds.map(function*(pId) { + let proposalVotes = yield proposalIds.map(function* (pId) { let votes = []; let nextVotes = []; let lastVoter = ''; @@ -59,15 +59,15 @@ export function* listProposals({ [api, api.listProposalVotesAsync], [pId, lastVoter], maxVotes, - 'by_proposal_voter', - 'ascending', - 'all' + order_by, + order_direction, + status ); votes = votes.concat(nextVotes); lastVoter = nextVotes[nextVotes.length - 1].voter; if (nextVotes.length < maxVotes) return votes; beyondThisProposal = false; - nextVotes.map(d => { + nextVotes.map((d) => { if (d.proposal.proposal_id != pId) beyondThisProposal = true; }); @@ -78,14 +78,14 @@ export function* listProposals({ proposalVotes = proposalVotes.reduce((a, b) => a.concat(b), []); proposalVotesIds = proposalVotes - .filter(d => { + .filter((d) => { return d.voter == voter_id; }) - .map(p => { + .map((p) => { return p.proposal.id; }); } - const mungedProposals = proposals.map(p => { + const mungedProposals = proposals.map((p) => { if (proposalVotesIds.indexOf(p.proposal_id) != -1) { p.upVoted = true; } else { @@ -123,7 +123,7 @@ export function* listVotedOnProposals({ order_direction, status ); - const proposals = data.filter(d => { + const proposals = data.filter((d) => { return d.voter == voter_id; }); yield put( @@ -143,12 +143,12 @@ export function* listVotedOnProposals({ // Action creators export const actions = { - listProposals: payload => ({ + listProposals: (payload) => ({ type: LIST_PROPOSALS, payload, }), - listVotedOnProposals: payload => ({ + listVotedOnProposals: (payload) => ({ type: LIST_VOTED_ON_PROPOSALS, payload, }), -- GitLab From a0ef5cf4e8cfe778eea42514cd3ba68b6888d9c6 Mon Sep 17 00:00:00 2001 From: Efe <hbehar@syncad.com> Date: Thu, 28 May 2020 09:44:02 +0200 Subject: [PATCH 2/4] Remove hardcoded app url, use from config --- src/app/components/modules/ProposalList/Proposal.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/components/modules/ProposalList/Proposal.jsx b/src/app/components/modules/ProposalList/Proposal.jsx index 2cf55671..f2594170 100644 --- a/src/app/components/modules/ProposalList/Proposal.jsx +++ b/src/app/components/modules/ProposalList/Proposal.jsx @@ -7,6 +7,7 @@ import tt from 'counterpart'; import cx from 'classnames'; import Userpic, { SIZE_SMALL } from 'app/components/elements/Userpic'; import { numberWithCommas } from 'app/utils/StateFunctions'; +import { APP_URL } from 'app/client_config'; import Icon from 'app/components/elements/Icon'; @@ -245,7 +246,7 @@ function durationInWords(duration) { function linkifyUsername(linkText, username = '') { if (username == '') username = linkText; return ( - <a href={`https://hive.blog/@${username}`} target="_blank"> + <a href={`${APP_URL}/@${username}`} target="_blank"> {linkText} </a> ); @@ -258,7 +259,7 @@ function linkifyUsername(linkText, username = '') { * @returns {string} - return a URL string */ function urlifyPermlink(username, permlink) { - return `https://hive.blog/@${username}/${permlink}`; + return `${APP_URL}/@${username}/${permlink}`; } /** -- GitLab From 8f4a2c4c0a0387c1c165e3bb8259689a1fcbc87d Mon Sep 17 00:00:00 2001 From: Efe <hbehar@syncad.com> Date: Thu, 28 May 2020 15:39:57 +0200 Subject: [PATCH 3/4] Fix load more, remove debug code --- src/app/components/pages/Proposals.jsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/app/components/pages/Proposals.jsx b/src/app/components/pages/Proposals.jsx index 0295448b..303a5c24 100644 --- a/src/app/components/pages/Proposals.jsx +++ b/src/app/components/pages/Proposals.jsx @@ -32,14 +32,22 @@ class Proposals extends React.Component { // eslint-disable-next-line react/destructuring-assignment const { status, order_by, order_direction } = options; - console.log(options); + const isFiltering = !!(status || order_by || order_direction); + + let limit; + + if (isFiltering) { + limit = this.state.limit; + } else { + limit = this.state.limit + this.state.proposals.length; + } const proposals = (await this.getAllProposals( this.state.last_proposal, order_by || this.state.order_by, order_direction || this.state.order_direction, - this.state.limit + this.state.proposals.length, + limit, status || this.state.status )) || []; @@ -52,6 +60,7 @@ class Proposals extends React.Component { proposals, loading: false, last_proposal, + limit, }); } @@ -230,7 +239,6 @@ module.exports = { ); }, listProposals: (payload) => { - console.log(payload); return new Promise((resolve, reject) => { dispatch( proposalActions.listProposals({ -- GitLab From 0eb995c12f36ca0b4da4a9dc2a92663b235d97f7 Mon Sep 17 00:00:00 2001 From: Efe <hbehar@syncad.com> Date: Mon, 19 Oct 2020 12:08:20 +0200 Subject: [PATCH 4/4] Update proposals page, fix list_proposal_vote call --- .../modules/ProposalList/Proposal.jsx | 10 ------ .../modules/ProposalList/ProposalList.jsx | 2 -- src/app/components/pages/Proposals.jsx | 36 +++++++++++++++++-- src/app/redux/ProposalSaga.js | 11 +++--- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/app/components/modules/ProposalList/Proposal.jsx b/src/app/components/modules/ProposalList/Proposal.jsx index f2594170..e75c2ad7 100644 --- a/src/app/components/modules/ProposalList/Proposal.jsx +++ b/src/app/components/modules/ProposalList/Proposal.jsx @@ -89,16 +89,6 @@ export function Proposal(props) { {creator != receiver ? ` ${tt('proposals.for')} ` : null} - {creator != receiver - ? linkifyUsername( - checkIfSameUser( - creator, - receiver, - 'themselves.' - ), - receiver - ) - : null} </div> </div> </div> diff --git a/src/app/components/modules/ProposalList/ProposalList.jsx b/src/app/components/modules/ProposalList/ProposalList.jsx index 586bc8d7..dd5ff225 100644 --- a/src/app/components/modules/ProposalList/ProposalList.jsx +++ b/src/app/components/modules/ProposalList/ProposalList.jsx @@ -27,7 +27,6 @@ export default function ProposalList(props) { <label className="proposals__select"> {tt('proposals.status')} <select - defaultValue={status} value={status} onChange={(e) => { onFilter(e.target.value); @@ -53,7 +52,6 @@ export default function ProposalList(props) { <label className="proposals__select"> {tt('proposals.order')} <select - defaultValue={orderBy} value={orderBy} onChange={(e) => { onOrder(e.target.value); diff --git a/src/app/components/pages/Proposals.jsx b/src/app/components/pages/Proposals.jsx index 303a5c24..2fcadb83 100644 --- a/src/app/components/pages/Proposals.jsx +++ b/src/app/components/pages/Proposals.jsx @@ -8,6 +8,25 @@ import PropTypes from 'prop-types'; import ProposalListContainer from 'app/components/modules/ProposalList/ProposalListContainer'; class Proposals extends React.Component { + startValueByOrderType = { + by_total_votes: { + ascending: [0], + descending: [], + }, + by_creator: { + ascending: [''], + descending: [], + }, + by_start_date: { + ascending: [''], + descending: [''], + }, + by_end_date: { + ascending: [''], + descending: [''], + }, + }; + constructor(props) { super(props); this.state = { @@ -42,13 +61,18 @@ class Proposals extends React.Component { limit = this.state.limit + this.state.proposals.length; } + const start = this.startValueByOrderType[ + order_by || this.state.order_by + ][order_direction || this.state.order_direction]; + const proposals = (await this.getAllProposals( this.state.last_proposal, order_by || this.state.order_by, order_direction || this.state.order_direction, limit, - status || this.state.status + status || this.state.status, + start )) || []; let last_proposal = false; @@ -79,7 +103,14 @@ class Proposals extends React.Component { await this.load(false, { order_direction }); }; - getAllProposals(last_proposal, order_by, order_direction, limit, status) { + getAllProposals( + last_proposal, + order_by, + order_direction, + limit, + status, + start + ) { return this.props.listProposals({ voter_id: this.props.currentUser, last_proposal, @@ -87,6 +118,7 @@ class Proposals extends React.Component { order_direction, limit, status, + start, }); } diff --git a/src/app/redux/ProposalSaga.js b/src/app/redux/ProposalSaga.js index fb9fa0d1..b65941e3 100644 --- a/src/app/redux/ProposalSaga.js +++ b/src/app/redux/ProposalSaga.js @@ -26,11 +26,10 @@ export function* listProposals({ order_direction, limit, status, + start, resolve, reject, }) { - const start = []; - const proposals = yield call( [api, api.listProposalsAsync], start, @@ -57,11 +56,11 @@ export function* listProposals({ while (true) { nextVotes = yield call( [api, api.listProposalVotesAsync], - [pId, lastVoter], + [voter_id], maxVotes, - order_by, - order_direction, - status + 'by_voter_proposal', + 'ascending', + 'active' ); votes = votes.concat(nextVotes); lastVoter = nextVotes[nextVotes.length - 1].voter; -- GitLab