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}&nbsp;<span className="id">#{id}</span>
+                </a>
+                <div className="proposals__row description">
+                    <div className="date">
+                        {formatDate(start)}&nbsp;-&nbsp;{formatDate(end)}&nbsp;(
+                        {durationInDays} {tt('proposals.days')})
+                    </div>
+                    <div className="amount">
+                        <span title={formatCurrency(totalPayout)}>
+                            {abbreviateNumber(totalPayout)} HBD
+                        </span>
+                        &nbsp;(
+                        {tt('proposals.daily')}&nbsp;
+                        {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')}&nbsp;{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',
+                            })}
+                        >
+                            &#x2191;
+                        </div>
+                        <div
+                            className={cx('direction', {
+                                active: orderDirection === 'descending',
+                            })}
+                        >
+                            &#x2193;
+                        </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