diff --git a/app/actions/ClientActions.js b/app/actions/ClientActions.js
index a85f0f6245..64e20eccb5 100644
--- a/app/actions/ClientActions.js
+++ b/app/actions/ClientActions.js
@@ -655,6 +655,7 @@ export const setVoteChoicesAttempt = (agendaId, choiceId, passphrase) => (
for (let i = 0; i < stakePools.length; i++) {
dispatch(getVoteChoicesAttempt(stakePools[i]));
}
+ dispatch(getVoteChoicesAttempt());
})
.catch((error) => dispatch({ error, type: SETVOTECHOICES_FAILED }));
};
diff --git a/app/actions/GovernanceActions.js b/app/actions/GovernanceActions.js
index 7a115b7d9d..8d7aad66d5 100644
--- a/app/actions/GovernanceActions.js
+++ b/app/actions/GovernanceActions.js
@@ -616,9 +616,11 @@ export const getProposalDetails = (token) => async (dispatch, getState) => {
}
};
-export const viewProposalDetails = (token) => (dispatch) => {
+export const viewProposalDetails = (token) => (dispatch) =>
dispatch(pushHistory(`/proposal/details/${token}`));
-};
+
+export const viewAgendaDetails = (name) => (dispatch) =>
+ dispatch(pushHistory(`/agenda/details/${name}`));
export const UPDATEVOTECHOICE_ATTEMPT = "UPDATEVOTECHOICE_ATTEMPT";
export const UPDATEVOTECHOICE_SUCCESS = "UPDATEVOTECHOICE_SUCCESS";
diff --git a/app/components/buttons/EyeFilterMenu/EyeFilterMenu.jsx b/app/components/buttons/EyeFilterMenu/EyeFilterMenu.jsx
index 9d659bd7b8..c23f4d9454 100644
--- a/app/components/buttons/EyeFilterMenu/EyeFilterMenu.jsx
+++ b/app/components/buttons/EyeFilterMenu/EyeFilterMenu.jsx
@@ -63,6 +63,7 @@ const EyeFilterMenu = ({
ref={wrapperRef}>
diff --git a/app/components/layout/StandalonePageBody/StandalonePageBody.module.css b/app/components/layout/StandalonePageBody/StandalonePageBody.module.css
index d3e619bde4..46b1aa6f3e 100644
--- a/app/components/layout/StandalonePageBody/StandalonePageBody.module.css
+++ b/app/components/layout/StandalonePageBody/StandalonePageBody.module.css
@@ -8,5 +8,6 @@
@media screen and (max-width: 1179px) {
.body {
padding-left: 20px;
+ padding-right: 20px;
}
}
diff --git a/app/components/layout/TabbedPage/TabbedPage.jsx b/app/components/layout/TabbedPage/TabbedPage.jsx
index 44b2428bb8..40828db433 100644
--- a/app/components/layout/TabbedPage/TabbedPage.jsx
+++ b/app/components/layout/TabbedPage/TabbedPage.jsx
@@ -69,7 +69,8 @@ const TabbedPage = ({
tabsClassName,
tabContentClassName,
onChange,
- caret
+ caret,
+ activeCaretClassName
}) => {
const location = useSelector(sel.location);
const uiAnimations = useSelector(sel.uiAnimations);
@@ -133,6 +134,7 @@ const TabbedPage = ({
tabs={tabHeaders}
tabsClassName={tabsClassName}
caret={caret}
+ activeCaretClassName={activeCaretClassName}
/>
diff --git a/app/components/shared/PoliteiaLink.jsx b/app/components/shared/PoliteiaLink.jsx
index 26a1c6ed97..8bf84570dc 100644
--- a/app/components/shared/PoliteiaLink.jsx
+++ b/app/components/shared/PoliteiaLink.jsx
@@ -7,14 +7,17 @@ const PoliteiaLink = ({
path,
className,
isTestnet,
- CustomComponent
+ CustomComponent,
+ hrefProp
}) => {
const href = useMemo(
() =>
- `https://${isTestnet ? "test-proposals" : "proposals"}.decred.org${
- path || ""
- }`,
- [isTestnet, path]
+ hrefProp
+ ? hrefProp
+ : `https://${isTestnet ? "test-proposals" : "proposals"}.decred.org${
+ path || ""
+ }`,
+ [isTestnet, path, hrefProp]
);
const onClickHandler = useCallback(() => wallet.openExternalURL(href), [
href
diff --git a/app/components/shared/RoutedTabsHeader/RoutedTabsHeader.jsx b/app/components/shared/RoutedTabsHeader/RoutedTabsHeader.jsx
index 16f744903b..006aadcf36 100644
--- a/app/components/shared/RoutedTabsHeader/RoutedTabsHeader.jsx
+++ b/app/components/shared/RoutedTabsHeader/RoutedTabsHeader.jsx
@@ -13,7 +13,12 @@ export const RoutedTab = (path, link, className, activeClassName) => ({
activeClassName
});
-const RoutedTabsHeader = ({ tabs, tabsClassName, caret }) => {
+const RoutedTabsHeader = ({
+ tabs,
+ tabsClassName,
+ caret,
+ activeCaretClassName
+}) => {
const { uiAnimations, caretLeft, caretWidth, nodes } = useRoutedTabsHeader();
const getAnimatedCaret = useCallback(() => {
@@ -26,12 +31,15 @@ const RoutedTabsHeader = ({ tabs, tabsClassName, caret }) => {
{(style) => (
)}
);
- }, [caretLeft, caretWidth]);
+ }, [caretLeft, caretWidth, activeCaretClassName]);
const getStaticCaret = useCallback(() => {
const style = {
diff --git a/app/components/shared/RoutedTabsHeader/RoutedTabsHeader.module.css b/app/components/shared/RoutedTabsHeader/RoutedTabsHeader.module.css
index 49dce642f3..8ee3f458c1 100644
--- a/app/components/shared/RoutedTabsHeader/RoutedTabsHeader.module.css
+++ b/app/components/shared/RoutedTabsHeader/RoutedTabsHeader.module.css
@@ -12,7 +12,7 @@
}
.tab a {
- margin: 0 5px 0 5px;
+ margin: 0;
color: var(--grey-7);
text-decoration: none;
font-size: 16px;
@@ -30,13 +30,13 @@
.tabCaret {
position: absolute;
bottom: 0;
- height: 5px;
+ height: 4px;
}
.tabCaret .active {
background-color: var(--accent-color);
position: absolute;
- height: 5px;
+ height: 4px;
}
@media screen and (max-width: 768px) {
diff --git a/app/components/views/AgendaDetailsPage/AgendaDetails.jsx b/app/components/views/AgendaDetailsPage/AgendaDetails.jsx
new file mode 100644
index 0000000000..48680e5a59
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/AgendaDetails.jsx
@@ -0,0 +1,48 @@
+import { classNames } from "pi-ui";
+import styles from "./AgendaDetails.module.css";
+import { FormattedMessage as T } from "react-intl";
+import { useAgendaDetails } from "./hooks";
+import { VoteSection, AgendaCard } from "./helpers";
+
+const AgendaDetails = () => {
+ const {
+ agenda,
+ selectedChoice,
+ newSelectedChoice,
+ setNewSelectedChoice,
+ choices,
+ isLoading,
+ goBackHistory,
+ updatePreferences
+ } = useAgendaDetails();
+ return (
+
+ );
+};
+
+export default AgendaDetails;
diff --git a/app/components/views/AgendaDetailsPage/AgendaDetails.module.css b/app/components/views/AgendaDetailsPage/AgendaDetails.module.css
new file mode 100644
index 0000000000..8f49b2da0c
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/AgendaDetails.module.css
@@ -0,0 +1,74 @@
+.cardWrapper {
+ display: flex;
+}
+
+.backButton {
+ width: 40px;
+ background-color: var(--governance-nav-button-bg);
+ cursor: pointer;
+}
+
+.backButton:hover {
+ background-color: var(--grey-5);
+}
+
+.backArrow {
+ height: 10px;
+ width: 10px;
+ background-image: var(--menu-arrow-up);
+ background-position: 50% 5px;
+ background-repeat: no-repeat;
+ transform: rotate(-90deg);
+}
+
+.backButton:hover .backArrow {
+ background-image: var(--arrow-left-white);
+ transform: rotate(0);
+ background-position: 50% 0;
+}
+.column {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+}
+
+.detailsText {
+ font-family: var(--font-family-regular-semi-bold);
+ padding: 30px 40px;
+ background-color: var(--background-back-color);
+ margin: 5px 0 20px 0;
+ border-top: 1px solid var(--tabbed-page-header-bg);
+ width: 764px;
+ font-size: 16px;
+}
+
+.piButtonWrapper {
+ display: flex;
+ justify-content: flex-end;
+ width: 740px;
+}
+
+.loadingPage {
+ width: 100%;
+ text-align: center;
+}
+
+.piButton {
+ border: 0 !important;
+ text-decoration: none !important;
+ color: var(--color-white) !important;
+ font-weight: var(--font-weight-semi-bold) !important;
+}
+
+@media screen and (max-width: 1179px) {
+ .detailsText {
+ width: 674px;
+ }
+}
+
+@media screen and (max-width: 768px) {
+ .detailsText {
+ padding-left: 20px;
+ width: 355px;
+ }
+}
diff --git a/app/components/views/AgendaDetailsPage/AgendaDetailsPage.jsx b/app/components/views/AgendaDetailsPage/AgendaDetailsPage.jsx
new file mode 100644
index 0000000000..74ae3b34f4
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/AgendaDetailsPage.jsx
@@ -0,0 +1,13 @@
+import AgendaDetails from "./AgendaDetails";
+import { Header } from "./helpers";
+import { StandalonePage } from "layout";
+
+const AgendaDetailsPage = () => {
+ return (
+ }>
+
+
+ );
+};
+
+export default AgendaDetailsPage;
diff --git a/app/components/views/AgendaDetailsPage/helpers/AgendaCard/AgendaCard.jsx b/app/components/views/AgendaDetailsPage/helpers/AgendaCard/AgendaCard.jsx
new file mode 100644
index 0000000000..aefc328055
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/AgendaCard/AgendaCard.jsx
@@ -0,0 +1,72 @@
+import styles from "./AgendaCard.module.css";
+import { classNames, Tooltip, StatusTag } from "pi-ui";
+import { FormattedMessage as T } from "react-intl";
+
+const AgendaCard = ({ agenda, selectedChoice, className }) => {
+ const inProgress = !agenda.finished;
+
+ return (
+
+
+
+
{agenda.name}
+
+ {agenda.name}
+ }}
+ />
+
+
{`${agenda.description} `}
+
+
+ {!inProgress ? (
+
+ }>
+
+
+ ) : (
+
+ }>
+
+
+ )}
+
+ {selectedChoice}
+ }}
+ />
+
+
+
+
+ );
+};
+
+export default AgendaCard;
diff --git a/app/components/views/AgendaDetailsPage/helpers/AgendaCard/AgendaCard.module.css b/app/components/views/AgendaDetailsPage/helpers/AgendaCard/AgendaCard.module.css
new file mode 100644
index 0000000000..2a6938c8a7
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/AgendaCard/AgendaCard.module.css
@@ -0,0 +1,171 @@
+.overview {
+ background-color: var(--background-back-color);
+ width: 724px;
+ padding: 20px 40px 20px 20px;
+}
+
+.row {
+ display: flex;
+ justify-content: space-between;
+}
+
+.titleText {
+ font-size: 18px;
+ line-height: 23px;
+ font-weight: 600;
+ color: var(--tutorial-header) !important;
+ letter-spacing: 0em;
+}
+
+.title {
+ cursor: pointer;
+}
+
+.subTitle {
+ font-size: 16px;
+}
+
+.creator {
+ font-weight: 600;
+}
+
+.tooltipTitle {
+ width: max-content;
+ max-width: max-content !important;
+}
+
+.updatedEvent,
+.version {
+ color: var(--grey-5);
+}
+
+.token {
+ padding: 4px 10px;
+ background-color: var(--color-blue-lighter);
+ border-radius: 5px;
+ font-size: 16px;
+}
+
+.token.dark {
+ color: var(--grey-2);
+}
+
+.proposedToRfp {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ color: var(--stroke-color-default);
+}
+
+.proposedToRfp a {
+ font-size: 13px;
+ line-height: 19px;
+ color: var(--overview-balance-label);
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.proposedToRfp span {
+ font-size: 13px;
+ line-height: 19px;
+ color: var(--overview-balance-label);
+ text-decoration: none;
+}
+
+.voteStatusBar {
+ flex-basis: 75%;
+ margin-right: 10px;
+}
+
+.statusBarRow {
+ padding: 0 20px 20px;
+}
+
+.voteEnd {
+ align-items: flex-end;
+ flex-basis: 25%;
+ margin-top: -0.3rem;
+ color: var(--grey-5);
+ font-size: 13px;
+}
+
+.quorumTooltip {
+ white-space: nowrap;
+ font-size: var(--font-size-small);
+ color: var(--color-gray);
+}
+
+.darkQuorumTooltip {
+ color: var(--text-color);
+}
+
+.votesQuorum {
+ color: var(--text-secondary-color) !important;
+}
+
+.votesReceived {
+ color: var(--tab-text-active-color) !important;
+}
+
+.tooltipContent {
+ width: max-content;
+}
+
+.preference {
+ color: var(--main-dark-blue);
+ display: flex;
+ flex-direction: row;
+ margin-top: 8px;
+ align-items: center;
+}
+
+.preference span {
+ margin-left: 10px;
+ padding: 4px 10px;
+ border-radius: 5px;
+ color: var(--agenda-preference);
+ background-color: var(--color-blue-lighter);
+ font-size: 16px;
+ text-transform: capitalize;
+}
+
+.agendaId {
+ font-size: 16px;
+ color: var(--main-dark-blue);
+ margin: 10px 0 20px 0;
+}
+
+.description {
+ font-size: 16px;
+ color: var(--main-dark-blue);
+}
+
+@media screen and (max-width: 1179px) {
+ .overview {
+ width: 634px;
+ }
+
+ .overviewInfo {
+ padding: 20px;
+ }
+}
+
+@media screen and (max-width: 768px) {
+ .overview {
+ width: 315px;
+ padding: 10px !important;
+ }
+
+ .votingProgress {
+ width: 315px;
+ padding-left: 20px;
+ }
+
+ .overviewVoting {
+ width: 315px;
+ }
+
+ .row {
+ flex-direction: column;
+ }
+}
diff --git a/app/components/views/AgendaDetailsPage/helpers/AgendaCard/index.js b/app/components/views/AgendaDetailsPage/helpers/AgendaCard/index.js
new file mode 100644
index 0000000000..0bef3d4fd1
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/AgendaCard/index.js
@@ -0,0 +1 @@
+export { default } from "./AgendaCard";
diff --git a/app/components/views/AgendaDetailsPage/helpers/Header/Header.jsx b/app/components/views/AgendaDetailsPage/helpers/Header/Header.jsx
new file mode 100644
index 0000000000..9d6cef42c0
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/Header/Header.jsx
@@ -0,0 +1,19 @@
+import { FormattedMessage as T } from "react-intl";
+import styles from "./Header.module.css";
+import { StandaloneHeader } from "layout";
+import { GOVERNANCE_ICON } from "constants";
+import TabHeader from "../../../GovernancePage/TabHeader/TabHeader";
+
+const Header = React.memo(function Header() {
+ return (
+ }
+ description={
+
+ }
+ iconType={GOVERNANCE_ICON}
+ />
+ );
+});
+
+export default Header;
diff --git a/app/components/views/AgendaDetailsPage/helpers/Header/Header.module.css b/app/components/views/AgendaDetailsPage/helpers/Header/Header.module.css
new file mode 100644
index 0000000000..269227b864
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/Header/Header.module.css
@@ -0,0 +1,3 @@
+.descriptionHeader {
+ margin-left: 0;
+}
diff --git a/app/components/views/AgendaDetailsPage/helpers/Header/index.js b/app/components/views/AgendaDetailsPage/helpers/Header/index.js
new file mode 100644
index 0000000000..2764567d96
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/Header/index.js
@@ -0,0 +1 @@
+export { default } from "./Header";
diff --git a/app/components/views/AgendaDetailsPage/helpers/VoteSection/CastVoteModalButton/CastVoteModalButton.jsx b/app/components/views/AgendaDetailsPage/helpers/VoteSection/CastVoteModalButton/CastVoteModalButton.jsx
new file mode 100644
index 0000000000..5d27a57d03
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/VoteSection/CastVoteModalButton/CastVoteModalButton.jsx
@@ -0,0 +1,24 @@
+import { FormattedMessage as T } from "react-intl";
+import { PassphraseModalButton } from "buttons";
+import styles from "./CastVoteModalButton.module.css";
+
+const CastVoteModalButton = ({ onSubmit, newVoteChoice, isLoading }) => (
+
+
+
+ >
+ }
+ disabled={isLoading}
+ loading={isLoading}
+ onSubmit={onSubmit}
+ className={styles.voteButton}
+ buttonLabel={}
+ />
+);
+
+export default CastVoteModalButton;
diff --git a/app/components/views/AgendaDetailsPage/helpers/VoteSection/CastVoteModalButton/CastVoteModalButton.module.css b/app/components/views/AgendaDetailsPage/helpers/VoteSection/CastVoteModalButton/CastVoteModalButton.module.css
new file mode 100644
index 0000000000..70ccf8d71d
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/VoteSection/CastVoteModalButton/CastVoteModalButton.module.css
@@ -0,0 +1,34 @@
+.voteConfirmation {
+ background-color: var(--background-container);
+ font-size: 27px;
+ line-height: 34px;
+ margin-left: 10px;
+ padding-left: 10px;
+ padding-right: 10px;
+ display: flex;
+ flex-direction: row;
+ text-transform: capitalize;
+}
+
+.yesProposal {
+ width: 12px;
+ height: 12px;
+ border-radius: 100px;
+ background-color: var(--vote-yes-color);
+ margin-top: 13px;
+ margin-right: 5px;
+}
+
+.noProposal {
+ width: 12px;
+ height: 12px;
+ border-radius: 100px;
+ background-color: var(--vote-no-color);
+ margin-top: 13px;
+ margin-right: 5px;
+}
+
+.voteButton {
+ padding: 5px 8px;
+ margin-left: 5px;
+}
diff --git a/app/components/views/AgendaDetailsPage/helpers/VoteSection/CastVoteModalButton/index.js b/app/components/views/AgendaDetailsPage/helpers/VoteSection/CastVoteModalButton/index.js
new file mode 100644
index 0000000000..5737838e6a
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/VoteSection/CastVoteModalButton/index.js
@@ -0,0 +1 @@
+export { default } from "./CastVoteModalButton";
diff --git a/app/components/views/AgendaDetailsPage/helpers/VoteSection/VoteSection.jsx b/app/components/views/AgendaDetailsPage/helpers/VoteSection/VoteSection.jsx
new file mode 100644
index 0000000000..f68f79d753
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/VoteSection/VoteSection.jsx
@@ -0,0 +1,56 @@
+import styles from "./VoteSection.module.css";
+import { RadioButtonGroup } from "pi-ui";
+import { FormattedMessage as T } from "react-intl";
+import CastVoteModalButton from "./CastVoteModalButton";
+
+const VoteSection = ({
+ choices,
+ selected,
+ setSelected,
+ finished,
+ updatePreferences,
+ isLoading
+}) => {
+ const options = choices
+ .map(({ choiceId }) => ({
+ label: choiceId,
+ value: choiceId
+ }))
+ .reverse();
+ const handleChange = (option) => setSelected(option.value);
+ const showVotingOptions = !!options.length && (!finished || selected);
+ return (
+ showVotingOptions && (
+
+
+
+ {finished && selected ? (
+
+ ) : (
+
+ )}
+
+
styles[o.value])}
+ />
+ {!finished && (
+
+ )}
+
+
+ )
+ );
+};
+
+export default VoteSection;
diff --git a/app/components/views/AgendaDetailsPage/helpers/VoteSection/VoteSection.module.css b/app/components/views/AgendaDetailsPage/helpers/VoteSection/VoteSection.module.css
new file mode 100644
index 0000000000..85b318005b
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/VoteSection/VoteSection.module.css
@@ -0,0 +1,97 @@
+.voteSection {
+ background-color: var(--background-back-color);
+ padding: 10px 40px;
+ width: 764px;
+ margin-top: 5px;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.total {
+ font-weight: 600;
+}
+
+.votePreference {
+ line-height: 0;
+ flex-basis: 73%;
+ display: flex;
+ align-items: center;
+ height: 35px;
+}
+
+.preferenceTitle {
+ font-size: 13px;
+}
+
+.voteRadioButtons {
+ margin-left: 10px;
+}
+
+.yes,
+.abstain,
+.no {
+ margin-top: 0 !important;
+}
+
+.yes > label,
+.abstain > label,
+.no > label {
+ display: flex;
+ align-items: center;
+}
+
+.yes > label > span:nth-child(1) {
+ border-color: var(--vote-yes-color);
+ top: 0 !important;
+}
+
+.yes > label > span:nth-child(2) {
+ background-color: var(--vote-yes-color);
+ top: 0.4rem !important;
+}
+
+.no > label > span:nth-child(1) {
+ border-color: var(--vote-no-color);
+ top: 0 !important;
+}
+
+.no > label > span:nth-child(2) {
+ background-color: var(--vote-no-color);
+ top: 0.4rem !important;
+}
+
+.abstain > label > span:nth-child(1) {
+ border-color: var(--vote-abstain-color);
+ top: 0 !important;
+}
+
+.abstain > label > span:nth-child(2) {
+ background-color: var(--vote-abstain-color);
+ top: 0.4rem !important;
+}
+
+@media screen and (max-width: 1179px) {
+ .votePreference {
+ flex-basis: 70%;
+ }
+
+ .voteButton {
+ left: 123px;
+ }
+
+ .voteSection {
+ width: 674px;
+ }
+}
+
+@media screen and (max-width: 768px) {
+ .voteSection {
+ width: 355px;
+ padding: 10px 10px 10px 20px;
+ }
+ .votePreference {
+ flex-basis: initial;
+ }
+}
diff --git a/app/components/views/AgendaDetailsPage/helpers/VoteSection/index.js b/app/components/views/AgendaDetailsPage/helpers/VoteSection/index.js
new file mode 100644
index 0000000000..642c3b4692
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/VoteSection/index.js
@@ -0,0 +1 @@
+export { default } from "./VoteSection";
diff --git a/app/components/views/AgendaDetailsPage/helpers/index.js b/app/components/views/AgendaDetailsPage/helpers/index.js
new file mode 100644
index 0000000000..9e2d027aae
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/helpers/index.js
@@ -0,0 +1,3 @@
+export { default as Header } from "./Header";
+export { default as VoteSection } from "./VoteSection";
+export { default as AgendaCard } from "./AgendaCard";
diff --git a/app/components/views/AgendaDetailsPage/hooks.js b/app/components/views/AgendaDetailsPage/hooks.js
new file mode 100644
index 0000000000..e96219baae
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/hooks.js
@@ -0,0 +1,54 @@
+import { useCallback, useMemo, useState } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import * as sel from "selectors";
+import * as cli from "actions/ClientActions";
+import { useParams } from "react-router-dom";
+import { find, compose, eq, get } from "fp";
+
+export const useAgendaDetails = () => {
+ const getAgendaSelectedChoice = (agenda, voteChoices) =>
+ get(
+ ["choiceId"],
+ find(compose(eq(agenda.name), get(["agendaId"])), voteChoices)
+ ) || "abstain";
+ const { name } = useParams();
+ const allAgendas = useSelector(sel.allAgendas);
+ const agenda = allAgendas.find((agenda) => agenda.name === name);
+ const voteChoices = useSelector(sel.voteChoices);
+
+ const agendaChoices = agenda.choices;
+ const choices = useMemo(
+ () =>
+ agendaChoices.map((choice) => ({
+ choiceId: choice.getId()
+ })),
+ [agendaChoices]
+ );
+ const selectedChoice = getAgendaSelectedChoice(agenda, voteChoices);
+
+ const [newSelectedChoice, setNewSelectedChoice] = useState(selectedChoice);
+ const settingVoteChoices = useSelector(sel.setVoteChoicesAttempt);
+ const settingVspdVoteChoices = useSelector(sel.setVspdVoteChoicesAttempt);
+ const isLoading = settingVoteChoices || settingVspdVoteChoices;
+
+ const dispatch = useDispatch();
+ const goBackHistory = useCallback(() => dispatch(cli.goBackHistory()), [
+ dispatch
+ ]);
+ const onUpdateVotePreference = (agendaId, choiceId, passphrase) =>
+ dispatch(cli.setVoteChoicesAttempt(agendaId, choiceId, passphrase));
+ const updatePreferences = async (passphrase) => {
+ await onUpdateVotePreference(agenda.name, newSelectedChoice, passphrase);
+ };
+
+ return {
+ agenda,
+ selectedChoice,
+ newSelectedChoice,
+ setNewSelectedChoice,
+ choices,
+ isLoading,
+ goBackHistory,
+ updatePreferences
+ };
+};
diff --git a/app/components/views/AgendaDetailsPage/index.js b/app/components/views/AgendaDetailsPage/index.js
new file mode 100644
index 0000000000..01f54bdd4f
--- /dev/null
+++ b/app/components/views/AgendaDetailsPage/index.js
@@ -0,0 +1 @@
+export { default } from "./AgendaDetailsPage";
diff --git a/app/components/views/GovernancePage/Blockchain/AgendaOverview/AgendaCard.jsx b/app/components/views/GovernancePage/Blockchain/AgendaOverview/AgendaCard.jsx
deleted file mode 100644
index 0913dfa394..0000000000
--- a/app/components/views/GovernancePage/Blockchain/AgendaOverview/AgendaCard.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { classNames } from "pi-ui";
-import { FormattedMessage as T } from "react-intl";
-import AgendaProgressIndicator from "./ProgressIndicator";
-import styles from "./Overview.module.css";
-
-const AgendaCard = ({ agenda, onClick, selectedChoice }) => (
-
-
{agenda.name}
-
- Preference:{" "}
- {selectedChoice}
-
-
- {`${agenda.description} `}
-
- :
- {agenda.name}
-
-
-
-
-);
-
-export default AgendaCard;
diff --git a/app/components/views/GovernancePage/Blockchain/AgendaOverview/AgendaOverview.jsx b/app/components/views/GovernancePage/Blockchain/AgendaOverview/AgendaOverview.jsx
index c05bd26de0..a6afbe5e30 100644
--- a/app/components/views/GovernancePage/Blockchain/AgendaOverview/AgendaOverview.jsx
+++ b/app/components/views/GovernancePage/Blockchain/AgendaOverview/AgendaOverview.jsx
@@ -1,56 +1,20 @@
-import Overview from "./Overview";
-import AgendaCard from "./AgendaCard";
-import { useState, useEffect, useMemo } from "react";
+import AgendaCard from "../../../AgendaDetailsPage/helpers/AgendaCard";
+import styles from "./AgendaOverview.module.css";
+import { classNames } from "pi-ui";
const AgendaOverview = ({
selectedChoice,
- disabled,
agenda,
- onCloseAgenda,
- showVoteChoice,
- onClick,
- onUpdateVotePreference,
- isLoading
-}) => {
- const [selectedChoiceId, setSelectedChoiceId] = useState(selectedChoice);
- useEffect(() => {
- if (selectedChoice !== selectedChoiceId) {
- setSelectedChoiceId(selectedChoiceId);
- }
- }, [selectedChoice, selectedChoiceId]);
-
- const updatePreferences = async (passphrase) => {
- await onUpdateVotePreference(agenda.name, selectedChoiceId, passphrase);
- };
-
- const agendaChoices = agenda.choices;
- const choices = useMemo(
- () =>
- agendaChoices.map((choice) => ({
- choiceId: choice.getId()
- })),
- [agendaChoices]
- );
- return showVoteChoice ? (
-
- ) : (
-
- );
-};
+ viewAgendaDetailsHandler
+}) => (
+ viewAgendaDetailsHandler(agenda.name)}
+ className={classNames(styles.cardWrapper)}>
+
+
+
+);
export default AgendaOverview;
diff --git a/app/components/views/GovernancePage/Blockchain/AgendaOverview/AgendaOverview.module.css b/app/components/views/GovernancePage/Blockchain/AgendaOverview/AgendaOverview.module.css
new file mode 100644
index 0000000000..e2300d7d5f
--- /dev/null
+++ b/app/components/views/GovernancePage/Blockchain/AgendaOverview/AgendaOverview.module.css
@@ -0,0 +1,38 @@
+.cardWrapper {
+ display: flex;
+ cursor: pointer;
+ margin-bottom: 5px;
+}
+
+.cardWrapper:hover {
+ filter: drop-shadow(0px 2px 4px rgba(0, 0, 0, 0.16));
+}
+
+.continueButton {
+ width: 40px;
+ background-color: var(--governance-nav-button-bg);
+ cursor: pointer;
+}
+
+.cardWrapper:hover .continueButton {
+ background-color: var(--grey-5);
+}
+
+.continueArrow {
+ height: 10px;
+ width: 10px;
+ background-image: var(--menu-arrow-up);
+ background-position: 50% 5px;
+ background-repeat: no-repeat;
+ transform: rotate(90deg);
+}
+
+.cardWrapper:hover .continueArrow {
+ background-image: var(--arrow-left-white);
+ background-position: 50% 0;
+ transform: rotate(180deg);
+}
+
+.overview {
+ padding-left: 35px !important;
+}
diff --git a/app/components/views/GovernancePage/Blockchain/AgendaOverview/Overview.jsx b/app/components/views/GovernancePage/Blockchain/AgendaOverview/Overview.jsx
deleted file mode 100644
index eb46c71e32..0000000000
--- a/app/components/views/GovernancePage/Blockchain/AgendaOverview/Overview.jsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import { RadioButtonGroup, classNames } from "pi-ui";
-import { PassphraseModalButton } from "buttons";
-import { FormattedMessage as T } from "react-intl";
-import ProgressIndicator from "./ProgressIndicator";
-import styles from "./Overview.module.css";
-
-const AgendaDetails = ({ name, onClose, description }) => (
-
-
-
-
{description}
-
- :{" "}
- {name}
-
-
-
-
-
-
-);
-
-const AgendaVotingOptions = ({
- choices,
- selected,
- setSelected,
- finished,
- disabled
-}) => {
- const options = choices.map(({ choiceId }) => ({
- label: choiceId,
- value: choiceId
- }));
- const handleChange = (option) => setSelected(option.value);
- const showVotingOptions = !!options.length && (!finished || selected);
-
- return (
- showVotingOptions && (
-
- {finished && selected ? (
-
- ) : (
-
- )}
- :
-
-
-
-
- )
- );
-};
-
-const Overview = ({
- isFinished,
- agendaId,
- agendaDescription,
- choices,
- selectedChoiceId,
- closeCurrentAgenda,
- setSelectedChoiceId,
- updatePreferences,
- disabled,
- passed,
- isLoading
-}) => (
-
-
-
-
-
-
}
- modalClassName={styles.passphraseModal}
- onSubmit={updatePreferences}
- className={styles.updatePreferencesButton}
- disabled={disabled || isLoading}
- buttonLabel={
- isLoading ? (
-
- ) : (
-
- )
- }
- />
-
-
-
-
-);
-
-export default Overview;
diff --git a/app/components/views/GovernancePage/Blockchain/AgendaOverview/Overview.module.css b/app/components/views/GovernancePage/Blockchain/AgendaOverview/Overview.module.css
deleted file mode 100644
index 60dd40380f..0000000000
--- a/app/components/views/GovernancePage/Blockchain/AgendaOverview/Overview.module.css
+++ /dev/null
@@ -1,248 +0,0 @@
-.overview {
- position: relative;
- margin-bottom: 15px;
-}
-
-.titleArea {
- margin-bottom: 29px;
- position: relative;
-}
-
-.titleName {
- padding-right: 10px;
- float: left;
- font-size: 19px;
-}
-
-.agenda {
- width: 100%;
- padding: 20px;
- display: flex;
- flex-direction: column;
- margin-bottom: 20px;
- border-left: 1px solid var(--text-color-light);
- background-color: var(--background-back-color);
-}
-
-.text {
- position: relative;
- line-height: 18px;
- color: var(--stroke-color-hovered);
- font-size: 13px;
-}
-
-.idCt {
- color: var(--input-color-default);
-}
-
-.id {
- font-weight: 600;
-}
-
-.optionsArea {
- flex-direction: column;
- align-items: stretch;
- font-size: 19px;
- margin: 15px 0px;
-}
-
-.optionsGroup {
- font-size: 15px;
- text-transform: capitalize;
-}
-
-.bottom {
- padding-top: 15px;
- display: flex;
-}
-
-.bottomOverview {
- width: 100%;
- position: relative;
-}
-
-.bottomOptions {
- min-width: 240px;
-}
-
-.updatePreferencesButton {
- width: 134px;
-}
-
-.overviewTitleClose {
- width: 11px;
- height: 11px;
- float: right;
- background-image: var(--agenda-close-icon);
- background-position: 50% 50%;
- background-size: 11px;
- background-repeat: no-repeat;
- max-width: 100%;
- background-color: transparent;
- cursor: pointer;
-}
-
-.overviewTitleClose:hover {
- opacity: 0.7;
-}
-
-.agendaCard {
- width: 225px;
- height: 225px;
- margin-bottom: 20px;
- margin-right: 20px;
- padding: 20px;
- background-color: var(--background-back-color);
- background-image: var(--agenda-card-kebab);
- background-position: 93% 27px;
- background-size: 10px;
- background-repeat: no-repeat;
- color: var(--input-color-default);
- cursor: pointer;
-}
-
-.agendaCard:hover {
- background-image: var(--agenda-card-kebab-hover);
- background-size: 10px;
-}
-
-.disabled {
- position: relative;
- overflow: hidden;
- width: 180px;
- height: 190px;
- margin-bottom: 20px;
- margin-right: 20px;
- padding: 20px;
- float: left;
- background-color: var(--disabled-background-color);
- background-image: var(--agenda-card-kebab-disabled);
- background-size: 10px;
- background-position: 93% 27px;
- background-repeat: no-repeat;
- color: var(--disabled-color);
- cursor: default;
-}
-
-.indicatorPending {
- float: right;
- padding: 5px 8px 5px 20px;
- border-style: solid;
- border-width: 1px;
- border-radius: 3px;
- font-size: 12px;
- line-height: 8px;
- text-align: right;
- text-transform: capitalize;
- border-color: var(--input-color);
- background-image: var(--agenda-indicator-pending);
- background-position: 6px 50%;
- background-size: 10px;
- background-repeat: no-repeat;
- color: var(--input-color);
- margin-right: 20px;
- margin-bottom: 20px;
-}
-
-.tooltip {
- float: right;
-}
-
-.tooltipContent {
- width: 22rem;
-}
-
-.progressTooltipContent {
- width: 15rem;
-}
-
-.bottomCfg {
- margin: 10px 0;
- float: left;
- color: var(--agenda-card-bottom-cfg);
-}
-
-.bottomCfgLast {
- float: left;
- color: var(--input-color-default);
-}
-
-.bottomCfgDisabled {
- float: left;
- color: var(--disabled-color);
-}
-
-.bottomCfgLastDisabled {
- color: var(--disabled-color);
-}
-
-.bottomCfgLastBold {
- font-weight: 600;
-}
-
-.name {
- padding-right: 10px;
- float: left;
- font-size: 19px;
-}
-
-.topPreference {
- margin-top: 6px;
- float: left;
- width: 186px;
- color: var(--header-desc-lighter-color);
-}
-
-.topPreference span {
- color: var(--input-color-default);
-}
-
-.textHighlightSmall {
- padding-right: 2px;
- padding-left: 2px;
- border-radius: 3px;
- background-color: var(--blue-highlight-background);
- font-size: 13px;
- font-weight: 600;
- text-transform: capitalize;
-}
-
-.finishedIndicator {
- margin-right: 20px;
- margin-bottom: 20px;
- border-color: var(--disabled-color);
- background-image: var(--agenda-indicator-finished);
- background-position: 6px 50%;
- background-size: 10px;
- background-repeat: no-repeat;
- color: var(--disabled-color);
- display: inline-block;
- padding: 5px 8px 5px 20px;
- border-style: solid;
- border-width: 1px;
- border-radius: 3px;
- font-size: 12px;
- line-height: 8px;
- text-align: right;
- text-transform: capitalize;
-}
-
-.inProgressIndicator {
- margin-right: 20px;
- margin-bottom: 20px;
- border-color: var(--color-blue-alt);
- background-image: var(--agenda-indicator-pending);
- background-position: 6px 50%;
- background-size: 10px;
- background-repeat: no-repeat;
- color: var(--color-blue-alt);
- display: inline-block;
- padding: 5px 8px 5px 20px;
- border-style: solid;
- border-width: 1px;
- border-radius: 3px;
- font-size: 12px;
- line-height: 8px;
- text-align: right;
- text-transform: capitalize;
-}
diff --git a/app/components/views/GovernancePage/Blockchain/AgendaOverview/ProgressIndicator.jsx b/app/components/views/GovernancePage/Blockchain/AgendaOverview/ProgressIndicator.jsx
deleted file mode 100644
index 8a5b339d69..0000000000
--- a/app/components/views/GovernancePage/Blockchain/AgendaOverview/ProgressIndicator.jsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { FormattedMessage as T } from "react-intl";
-import { Tooltip } from "pi-ui";
-import styles from "./Overview.module.css";
-
-// TODO: we should improve this component and use the StatusTag component
-// from pi-ui. But since the component is not able to receive React
-// nodes as the `text` prop, we should keep this in order to keep the
-// internationalization
-const ProgressIndicator = ({ passed, inProgress }) =>
- !inProgress ? (
-
- }>
-
-
-
-
- ) : (
-
- }>
-
-
-
-
- );
-
-export default ProgressIndicator;
diff --git a/app/components/views/GovernancePage/Blockchain/AgendaOverview/index.js b/app/components/views/GovernancePage/Blockchain/AgendaOverview/index.js
new file mode 100644
index 0000000000..670360e2e1
--- /dev/null
+++ b/app/components/views/GovernancePage/Blockchain/AgendaOverview/index.js
@@ -0,0 +1 @@
+export { default } from "./AgendaOverview";
diff --git a/app/components/views/GovernancePage/Blockchain/Blockchain.jsx b/app/components/views/GovernancePage/Blockchain/Blockchain.jsx
index 372d64c773..f0daaf2601 100644
--- a/app/components/views/GovernancePage/Blockchain/Blockchain.jsx
+++ b/app/components/views/GovernancePage/Blockchain/Blockchain.jsx
@@ -1,28 +1,56 @@
-import { useState } from "react";
-import VotingPrefs from "./Page";
import { find, compose, eq, get } from "fp";
-import { useVotingPrefs } from "./hooks";
-
-// TODO this agenda component needs some love.
-const VotingPrefsTab = () => {
- const [selectedAgenda, setSelectedAgenda] = useState(null);
- const {
- configuredStakePools,
- defaultStakePool,
- stakePool,
- allAgendas,
- onUpdateVotePreference,
- onChangeStakePool,
- isLoading,
- voteChoices
- } = useVotingPrefs();
-
- const getStakePool = () => {
- const pool = onChangeStakePool && stakePool;
- return pool
- ? configuredStakePools.find(compose(eq(pool.Host), get("Host")))
- : null;
- };
+import { useBlockchain } from "./hooks";
+import AgendaOverview from "./AgendaOverview";
+import { PoliteiaLink as PiLink } from "shared";
+import { FormattedMessage as T, defineMessages } from "react-intl";
+import PageHeader from "../PageHeader";
+import styles from "./Blockchain.module.css";
+import { Button, Tooltip } from "pi-ui";
+import { TextInput } from "inputs";
+import { EyeFilterMenu } from "buttons";
+import { useIntl } from "react-intl";
+import { useState, useEffect } from "react";
+
+const messages = defineMessages({
+ filterByNamePlaceholder: {
+ id: "blockchain.filterByNamePlaceholder",
+ defaultMessage: "Filter by Name"
+ }
+});
+
+const sortOptions = [
+ {
+ key: "desc",
+ value: "desc",
+ label:
+ },
+ {
+ key: "asc",
+ value: "asc",
+ label:
+ }
+];
+
+const Blockchain = () => {
+ const { allAgendas, voteChoices, viewAgendaDetailsHandler } = useBlockchain();
+ const [filterByName, setFilterByName] = useState("");
+ const [sortBy, setSortBy] = useState(sortOptions[0]);
+ const [agendas, setAgendas] = useState(allAgendas);
+ const intl = useIntl();
+ const sortByKey = sortBy.key;
+
+ useEffect(() => {
+ let newAgendas =
+ sortByKey === "desc" ? [...allAgendas] : [...allAgendas].reverse();
+
+ if (filterByName.trim() !== "") {
+ newAgendas = newAgendas.filter(
+ (agenda) => agenda.name.search(filterByName.trim()) !== -1
+ );
+ }
+
+ setAgendas(newAgendas);
+ }, [allAgendas, filterByName, sortByKey]);
const getAgendaSelectedChoice = (agenda) =>
get(
@@ -30,25 +58,94 @@ const VotingPrefsTab = () => {
find(compose(eq(agenda.name), get(["agendaId"])), voteChoices)
) || "abstain";
- const onShowAgenda = (index) => setSelectedAgenda(index);
-
- const onCloseAgenda = () => setSelectedAgenda(null);
-
return (
-
+ <>
+
+
}
+ description={
+
+ docs.decred.org
+
+ )
+ }}
+ />
+ }
+ optionalButton={
+
+ }
+ />
+
+
+
+ setFilterByName(e.target.value)}
+ />
+
+
+ }>
+ setSortBy(v)}
+ />
+
+
+
+
+ {agendas.length > 0 ? (
+ agendas.map((agenda) => (
+
+ ))
+ ) : filterByName ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ >
);
};
-export default VotingPrefsTab;
+export default Blockchain;
diff --git a/app/components/views/GovernancePage/Blockchain/Blockchain.module.css b/app/components/views/GovernancePage/Blockchain/Blockchain.module.css
new file mode 100644
index 0000000000..4dc680f411
--- /dev/null
+++ b/app/components/views/GovernancePage/Blockchain/Blockchain.module.css
@@ -0,0 +1,71 @@
+.headerWrapper {
+ background-color: var(--governance-tab-bg);
+}
+
+.agendaWrapper {
+ margin: 20px 60px;
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.politeiaButton {
+ font-size: 13px !important;
+ line-height: 17px !important;
+ border-radius: 5px !important;
+ padding: 6px 10px !important;
+ margin: 15px 0 0 0 !important;
+ color: var(--info-modal-button-text) !important;
+ background-color: var(--politeia-button-bg) !important;
+ border: 0 !important;
+ text-decoration: none !important;
+ font-weight: 600 !important;
+}
+
+.politeiaButton:hover {
+ opacity: 0.85;
+}
+
+.proposalsLink {
+ color: var(--link-color) !important;
+ cursor: pointer;
+}
+
+.searchByNameInput {
+ display: inline-block;
+ width: 150px;
+}
+
+.searchByNameInput input::placeholder,
+.searchByNameInput input {
+ font-size: 16px;
+}
+
+.filters {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ width: 825px;
+ margin-top: 22px;
+}
+
+.sortByTooltip {
+ width: max-content;
+}
+
+@media screen and (max-width: 1179px) {
+ .agendaWrapper {
+ margin-left: 35px;
+ }
+ .filters {
+ width: 704px;
+ }
+}
+
+@media screen and (max-width: 768px) {
+ .agendaWrapper {
+ margin: 20px;
+ }
+ .filters {
+ width: 367px;
+ }
+}
diff --git a/app/components/views/GovernancePage/Blockchain/Page.jsx b/app/components/views/GovernancePage/Blockchain/Page.jsx
deleted file mode 100644
index 2248a02a93..0000000000
--- a/app/components/views/GovernancePage/Blockchain/Page.jsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import AgendaOverview from "./AgendaOverview/AgendaOverview";
-import { ExternalLink } from "shared";
-import { FormattedMessage as T } from "react-intl";
-import { classNames } from "pi-ui";
-import styles from "./VotingPrefs.module.css";
-
-const VotingPrefs = ({
- stakePool,
- selectedAgenda,
- getAgendaSelectedChoice,
- onShowAgenda,
- onCloseAgenda,
- onUpdateVotePreference,
- allAgendas,
- settingVoteChoices,
- isLoading
-}) => (
- <>
-
-
- {allAgendas.length > 0 ? (
- allAgendas.map((agenda, index) => (
-
onShowAgenda(index)}
- />
- ))
- ) : (
-
-
-
- )}
-
- >
-);
-
-export default VotingPrefs;
diff --git a/app/components/views/GovernancePage/Blockchain/VotingPrefs.module.css b/app/components/views/GovernancePage/Blockchain/VotingPrefs.module.css
deleted file mode 100644
index 99902215f3..0000000000
--- a/app/components/views/GovernancePage/Blockchain/VotingPrefs.module.css
+++ /dev/null
@@ -1,73 +0,0 @@
-.agendaWrapper {
- margin: 20px 100px;
- display: flex;
- flex-wrap: wrap;
-}
-
-.header {
- background-color: var(--disabled-background-color);
- color: var(--modal-text);
- padding-left: 103px;
- padding-top: 25px;
- padding-bottom: 15px;
-}
-
-.title {
- font-size: 21px;
- line-height: 27px;
- margin-bottom: 10px;
- color: var(--title-text-and-button-background);
-}
-
-.description {
- font-size: 13px;
- line-height: 19px;
- margin-top: 0;
- width: 460px;
-}
-
-.dashboardButton {
- font-size: 13px !important;
- line-height: 17px !important;
- border-radius: 5px !important;
- padding: 6px 15px !important;
- margin: 4px !important;
- color: var(--info-modal-button-text) !important;
- background-color: var(--info-modal-button-bg) !important;
- border: 0 !important;
- text-decoration: none !important;
- box-shadow: 0 5px 13px var(--icons-shadow);
-}
-
-.dashboardButton:hover {
- background-color: var(--title-text-and-button-background-hovered) !important;
-}
-
-.links {
- margin-left: 130px;
-}
-
-.infoButton {
- border-radius: 0px;
- background-image: var(--tickets-info-icon);
- background-position: 50% 50%;
- background-size: 20px;
- background-repeat: no-repeat;
- box-shadow: none;
- outline: 0;
- display: inline-block;
- vertical-align: middle;
- width: 20px;
- height: 20px;
- padding: 3px;
- background-color: transparent;
- border: 0;
- line-height: inherit;
- text-decoration: none;
- cursor: pointer;
- margin: 4px 0;
-}
-
-.infoButton:hover {
- opacity: 0.7;
-}
diff --git a/app/components/views/GovernancePage/Blockchain/hooks.js b/app/components/views/GovernancePage/Blockchain/hooks.js
index a6dd634ce2..1eb4f35fc4 100644
--- a/app/components/views/GovernancePage/Blockchain/hooks.js
+++ b/app/components/views/GovernancePage/Blockchain/hooks.js
@@ -1,34 +1,18 @@
-import * as sel from "selectors";
-import { useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
-import * as ca from "actions/ClientActions";
-import * as spa from "actions/VSPActions";
+import * as sel from "selectors";
+import * as gov from "actions/GovernanceActions";
-export function useVotingPrefs() {
- const dispatch = useDispatch();
- const configuredStakePools = useSelector(sel.configuredStakePools);
- const defaultStakePool = useSelector(sel.defaultStakePool);
- const stakePool = useSelector(sel.selectedStakePool);
+export function useBlockchain() {
const allAgendas = useSelector(sel.allAgendas);
- const settingVoteChoices = useSelector(sel.setVoteChoicesAttempt);
- const settingVspdVoteChoices = useSelector(sel.setVspdVoteChoicesAttempt);
const voteChoices = useSelector(sel.voteChoices);
- const onUpdateVotePreference = (agendaId, choiceId, passphrase) =>
- dispatch(ca.setVoteChoicesAttempt(agendaId, choiceId, passphrase));
- const onChangeStakePool = useCallback(
- () => dispatch(spa.changeSelectedStakePool),
- [dispatch]
- );
- const isLoading = settingVoteChoices || settingVspdVoteChoices;
+ const dispatch = useDispatch();
+ const viewAgendaDetailsHandler = (agendaId) => {
+ return dispatch(gov.viewAgendaDetails(agendaId));
+ };
return {
- configuredStakePools,
- defaultStakePool,
- stakePool,
allAgendas,
- onUpdateVotePreference,
- onChangeStakePool,
- voteChoices,
- isLoading
+ viewAgendaDetailsHandler,
+ voteChoices
};
}
diff --git a/app/components/views/GovernancePage/Blockchain/index.js b/app/components/views/GovernancePage/Blockchain/index.js
new file mode 100644
index 0000000000..ffd9034f37
--- /dev/null
+++ b/app/components/views/GovernancePage/Blockchain/index.js
@@ -0,0 +1 @@
+export { default } from "./Blockchain";
diff --git a/app/components/views/GovernancePage/GovernancePage.jsx b/app/components/views/GovernancePage/GovernancePage.jsx
index e751d3d287..ffa8c49fc1 100644
--- a/app/components/views/GovernancePage/GovernancePage.jsx
+++ b/app/components/views/GovernancePage/GovernancePage.jsx
@@ -1,9 +1,9 @@
import { TabbedPage, TabbedPageTab as Tab, TitleHeader } from "layout";
import { FormattedMessage as T } from "react-intl";
import { Switch, Redirect } from "react-router-dom";
-import ProposalsTab from "./Proposals/ProposalsTab";
-import VotingPrefsTab from "./Blockchain/Blockchain";
-import TabHeader from "./TabHeader/TabHeader";
+import ProposalsTab from "./Proposals";
+import VotingPrefsTab from "./Blockchain";
+import TabHeader from "./TabHeader";
import { GOVERNANCE_ICON } from "constants";
import styles from "./GovernancePage.module.css";
diff --git a/app/components/views/GovernancePage/PageHeader/PageHeader.jsx b/app/components/views/GovernancePage/PageHeader/PageHeader.jsx
new file mode 100644
index 0000000000..7f345190a6
--- /dev/null
+++ b/app/components/views/GovernancePage/PageHeader/PageHeader.jsx
@@ -0,0 +1,11 @@
+import styles from "./PageHeader.module.css";
+
+const PageHeader = ({ title, optionalButton, description }) => (
+
+
{title}
+
{description}
+ {optionalButton}
+
+);
+
+export default PageHeader;
diff --git a/app/components/views/GovernancePage/PageHeader/PageHeader.module.css b/app/components/views/GovernancePage/PageHeader/PageHeader.module.css
new file mode 100644
index 0000000000..004e386bce
--- /dev/null
+++ b/app/components/views/GovernancePage/PageHeader/PageHeader.module.css
@@ -0,0 +1,46 @@
+.header {
+ color: var(--modal-text);
+ padding: 30px 0 30px 95px;
+ width: 824px;
+ display: flex;
+ flex-direction: column;
+}
+
+.title {
+ font-size: 24px;
+ line-height: 30px;
+ color: var(--grey-6);
+ display: flex;
+ flex-direction: row;
+ margin-bottom: 5px;
+ align-items: center;
+}
+
+.description {
+ font-size: 16px;
+ line-height: 20px;
+ margin-top: 0;
+ color: var(--main-dark-blue);
+}
+
+@media screen and (max-width: 1179px) {
+ .header {
+ width: 696px;
+ padding-left: 55px;
+ }
+
+ .description {
+ width: 484px;
+ }
+}
+
+@media screen and (max-width: 768px) {
+ .header {
+ width: 338px;
+ padding-left: 20px;
+ }
+
+ .description {
+ width: 334px;
+ }
+}
diff --git a/app/components/views/GovernancePage/PageHeader/index.js b/app/components/views/GovernancePage/PageHeader/index.js
new file mode 100644
index 0000000000..c9b885cbfc
--- /dev/null
+++ b/app/components/views/GovernancePage/PageHeader/index.js
@@ -0,0 +1 @@
+export { default } from "./PageHeader";
diff --git a/app/components/views/GovernancePage/Proposals/ProposalsFilter/ProposalsFilter.jsx b/app/components/views/GovernancePage/Proposals/ProposalsFilter/ProposalsFilter.jsx
deleted file mode 100644
index 0f47169142..0000000000
--- a/app/components/views/GovernancePage/Proposals/ProposalsFilter/ProposalsFilter.jsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { FormattedMessage as T } from "react-intl";
-import { TabsHeader } from "shared";
-import styles from "./ProposalsFilter.module.css";
-
-const tabs = [
- {
- label: ,
- value: "finishedVote",
- icon: styles.allIcon
- },
- {
- label: ,
- value: "approvedVote",
- icon: styles.approvedIcon
- },
- {
- label: ,
- value: "rejectedVote",
- icon: styles.rejectedIcon
- }
-];
-
-const ProposalsFilter = ({ filterTab, setFilterTab }) => {
- const onSelectFilterTab = (index) => {
- const newTab = tabs[index].value;
- setFilterTab(newTab);
- };
-
- return (
-
- tab.value === filterTab)}
- />
-
- );
-};
-
-export default ProposalsFilter;
diff --git a/app/components/views/GovernancePage/Proposals/ProposalsFilter/ProposalsFilter.module.css b/app/components/views/GovernancePage/Proposals/ProposalsFilter/ProposalsFilter.module.css
deleted file mode 100644
index 5d84c3f554..0000000000
--- a/app/components/views/GovernancePage/Proposals/ProposalsFilter/ProposalsFilter.module.css
+++ /dev/null
@@ -1,47 +0,0 @@
-.tabs {
- width: 800px;
- margin-left: 85px;
- height: 40px;
-}
-
-.tabs > div > ul {
- justify-content: right;
- position: relative;
- right: -20px;
- width: fit-content;
- margin-left: auto;
- top: 0;
-}
-
-.tabs > div {
- background-color: transparent;
- padding-top: 0px;
-}
-
-@media screen and (max-width: 1179px) {
- .tabs {
- margin-left: 0;
- width: 700px;
- }
-}
-
-@media screen and (max-width: 768px) {
- .tabs {
- width: 100%;
- }
-
- .allIcon {
- background-image: var(--ticket-live-icon);
- }
-
- .approvedIcon {
- background-image: var(--ticket-voted-icon);
- }
-
- .rejectedIcon {
- background-image: var(--ticket-revoked-icon);
- }
- .tabs > div > ul {
- left: 0px;
- }
-}
diff --git a/app/components/views/GovernancePage/Proposals/ProposalsList/ProposalsList.jsx b/app/components/views/GovernancePage/Proposals/ProposalsList/ProposalsList.jsx
index eb03b954cf..0e398815f9 100644
--- a/app/components/views/GovernancePage/Proposals/ProposalsList/ProposalsList.jsx
+++ b/app/components/views/GovernancePage/Proposals/ProposalsList/ProposalsList.jsx
@@ -1,14 +1,30 @@
import { PoliteiaLoading, NoProposals } from "indicators";
import InfiniteScroll from "react-infinite-scroller";
-import ProposalsListItem from "../ProposalsListItem/ProposalsListItem";
+import ProposalsListItem from "../ProposalsListItem";
import { useProposalsList } from "../hooks";
import { LoadingError } from "shared";
import styles from "./ProposalsList.module.css";
import { useCallback, useLayoutEffect, useState, useEffect } from "react";
-import ProposalsFilter from "../ProposalsFilter/ProposalsFilter";
+import { EyeFilterMenu } from "buttons";
+import { FormattedMessage as T } from "react-intl";
+
+const getProposalTypes = () => [
+ {
+ key: "finishedVote",
+ label:
+ },
+ {
+ key: "approvedVote",
+ label:
+ },
+ {
+ key: "rejectedVote",
+ label:
+ }
+];
const ProposalsList = ({ finishedVote, tab }) => {
- const [filterTab, setFilterTab] = useState(tab);
+ const [selectedFilter, setSelectedFilter] = useState(tab);
const {
getProposalError,
inventoryError,
@@ -17,11 +33,7 @@ const ProposalsList = ({ finishedVote, tab }) => {
proposals,
state,
send
- } = useProposalsList(filterTab);
-
- const handleSetFilterTab = (tab) => {
- setFilterTab(tab);
- };
+ } = useProposalsList(selectedFilter);
// This part of the code is meant to solve the situation when the window
// is too tall, and the user can not trigger `loadMore` with scrolling.
@@ -55,37 +67,37 @@ const ProposalsList = ({ finishedVote, tab }) => {
);
case "success":
return proposals &&
- proposals[filterTab] &&
- proposals[filterTab].length ? (
-
+ proposals[selectedFilter] &&
+ proposals[selectedFilter].length ? (
+ <>
{tab === "finishedVote" && (
-
- )}
-
-
- {proposals[filterTab].map((v) => (
-
- ))}
+
+ setSelectedFilter(type.key)}
+ />
-
-
+ )}
+
+
+
+ {proposals[selectedFilter].map((v) => (
+
+ ))}
+
+
+
+ >
) : (
);
diff --git a/app/components/views/GovernancePage/Proposals/ProposalsList/ProposalsList.module.css b/app/components/views/GovernancePage/Proposals/ProposalsList/ProposalsList.module.css
index 2b132c7ac9..c04df9ec71 100644
--- a/app/components/views/GovernancePage/Proposals/ProposalsList/ProposalsList.module.css
+++ b/app/components/views/GovernancePage/Proposals/ProposalsList/ProposalsList.module.css
@@ -13,7 +13,7 @@
.proposalList {
margin-top: 15px;
- margin-left: 85px;
+ margin-left: 60px;
padding-bottom: 50px;
display: block;
}
@@ -26,13 +26,34 @@
height: calc(100% - 50px);
}
-@media screen and (max-width: 768px) {
- .proposalList {
- padding-bottom: 100px;
- }
+.filters {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ width: 825px;
+ margin-top: 22px;
+}
+
+.scrollWrapper {
+ height: calc(100% - 54px);
+ overflow: auto;
}
+
@media screen and (max-width: 1179px) {
.proposalList {
margin-left: 35px;
}
+ .filters {
+ width: 704px;
+ }
+}
+
+@media screen and (max-width: 768px) {
+ .proposalList {
+ padding-bottom: 100px;
+ margin-left: 10px;
+ }
+ .filters {
+ width: 367px;
+ }
}
diff --git a/app/components/views/GovernancePage/Proposals/ProposalsList/index.js b/app/components/views/GovernancePage/Proposals/ProposalsList/index.js
new file mode 100644
index 0000000000..40221dc141
--- /dev/null
+++ b/app/components/views/GovernancePage/Proposals/ProposalsList/index.js
@@ -0,0 +1 @@
+export { default } from "./ProposalsList";
diff --git a/app/components/views/GovernancePage/Proposals/ProposalsListItem/CardWrapper/CardWrapper.jsx b/app/components/views/GovernancePage/Proposals/ProposalsListItem/CardWrapper/CardWrapper.jsx
new file mode 100644
index 0000000000..2e2d852a5f
--- /dev/null
+++ b/app/components/views/GovernancePage/Proposals/ProposalsListItem/CardWrapper/CardWrapper.jsx
@@ -0,0 +1,13 @@
+import { classNames } from "pi-ui";
+import styles from "./CardWrapper.module.css";
+
+const CardWrapper = ({ onClick, className, children }) => (
+
+);
+
+export default CardWrapper;
diff --git a/app/components/views/GovernancePage/Proposals/ProposalsListItem/CardWrapper/CardWrapper.module.css b/app/components/views/GovernancePage/Proposals/ProposalsListItem/CardWrapper/CardWrapper.module.css
new file mode 100644
index 0000000000..47d2e5c016
--- /dev/null
+++ b/app/components/views/GovernancePage/Proposals/ProposalsListItem/CardWrapper/CardWrapper.module.css
@@ -0,0 +1,46 @@
+.cardWrapper {
+ display: flex;
+ cursor: pointer;
+ margin-bottom: 5px;
+}
+
+.cardWrapper:hover {
+ filter: drop-shadow(0px 2px 4px rgba(0, 0, 0, 0.16));
+}
+
+.continueButton {
+ width: 40px;
+ background-color: var(--governance-nav-button-bg);
+ cursor: pointer;
+}
+
+.cardWrapper:hover .continueButton {
+ background-color: var(--grey-5);
+}
+
+.continueArrow {
+ height: 10px;
+ width: 10px;
+ background-image: var(--menu-arrow-up);
+ background-position: 50% 5px;
+ background-repeat: no-repeat;
+ transform: rotate(90deg);
+}
+
+.cardWrapper:hover .continueArrow {
+ background-image: var(--arrow-left-white);
+ background-position: 50% 0;
+ transform: rotate(180deg);
+}
+
+.cardWrapper.ended.passed {
+ border-left: 2px solid var(--vote-yes-color);
+}
+
+.cardWrapper.ended.declined {
+ border-left: 2px solid var(--vote-no-color);
+}
+
+.cardWrapper.ended .proposalName {
+ color: var(--stroke-color-hovered);
+}
diff --git a/app/components/views/GovernancePage/Proposals/ProposalsListItem/CardWrapper/index.js b/app/components/views/GovernancePage/Proposals/ProposalsListItem/CardWrapper/index.js
new file mode 100644
index 0000000000..a48871930a
--- /dev/null
+++ b/app/components/views/GovernancePage/Proposals/ProposalsListItem/CardWrapper/index.js
@@ -0,0 +1 @@
+export { default } from "./CardWrapper";
diff --git a/app/components/views/GovernancePage/Proposals/ProposalsListItem/ProposalsListItem.jsx b/app/components/views/GovernancePage/Proposals/ProposalsListItem/ProposalsListItem.jsx
index 65d3c44031..e0cdce89f0 100644
--- a/app/components/views/GovernancePage/Proposals/ProposalsListItem/ProposalsListItem.jsx
+++ b/app/components/views/GovernancePage/Proposals/ProposalsListItem/ProposalsListItem.jsx
@@ -1,10 +1,9 @@
-import { FormattedMessage as T } from "react-intl";
-import { VotingProgress } from "indicators";
import { PROPOSAL_VOTING_ACTIVE, PROPOSAL_VOTING_FINISHED } from "constants";
-import { FormattedRelative } from "shared";
import { classNames } from "pi-ui";
import { useProposalsListItem } from "../hooks";
import styles from "./ProposalsListItem.module.css";
+import ProposalCard from "../../../ProposalDetailsPage/helpers/ProposalCard";
+import CardWrapper from "./CardWrapper";
const ProposalsListItem = ({
name,
@@ -12,88 +11,78 @@ const ProposalsListItem = ({
token,
voteCounts,
voteStatus,
- currentVoteChoice,
- quorumPass,
voteResult,
modifiedSinceLastAccess,
votingSinceLastAccess,
quorumMinimumVotes,
finishedVote,
linkto,
- linkedfrom,
- approved
+ approved,
+ totalVotes,
+ endTimestamp,
+ blocksLeft,
+ creator,
+ proposalStatus,
+ version
}) => {
- const { viewProposalDetailsHandler, tsDate } = useProposalsListItem(token);
+ const {
+ viewProposalDetailsHandler,
+ tsDate,
+ isTestnet,
+ isDarkTheme,
+ linkedProposal
+ } = useProposalsListItem(token);
const isVoting = voteStatus === PROPOSAL_VOTING_ACTIVE;
- const isVotingFinished = voteStatus === PROPOSAL_VOTING_FINISHED;
const isModified =
(!isVoting && modifiedSinceLastAccess) ||
(isVoting && votingSinceLastAccess);
+
+ const shortToken = token.substring(0, 7);
+ const shortRFPToken = linkedProposal?.token.substring(0, 7);
+ const proposalPath = `/record/${shortToken}`;
+ const isVoteActive = voteStatus === PROPOSAL_VOTING_ACTIVE;
+ const isVoteActiveOrFinished =
+ isVoteActive || voteStatus === PROPOSAL_VOTING_FINISHED;
+
return (
-
-
-
-
{name}
- {linkedfrom && (
-
-
-
- )}
- {linkto && (
-
-
-
- )}
-
-
{token.substring(0, 7)}
-
-
- {(isVoting || isVotingFinished) && (
-
- )}
- {!isVotingFinished ? (
-
-
- }}
- />
-
- ) : (
-
- {quorumPass ? (
- linkto && !approved && voteResult !== "declined" ? (
-
- ) : (
- voteResult
- )
- ) : (
-
- )}
-
- )}
-
-
+
+
);
};
diff --git a/app/components/views/GovernancePage/Proposals/ProposalsListItem/ProposalsListItem.module.css b/app/components/views/GovernancePage/Proposals/ProposalsListItem/ProposalsListItem.module.css
index 91ec50de21..c4c6a763e2 100644
--- a/app/components/views/GovernancePage/Proposals/ProposalsListItem/ProposalsListItem.module.css
+++ b/app/components/views/GovernancePage/Proposals/ProposalsListItem/ProposalsListItem.module.css
@@ -1,3 +1,7 @@
+.overview {
+ padding-left: 15px !important;
+}
+
.listItem {
padding: 18px 16px 6px 20px;
border-left: 2px solid var(--background-back-color);
diff --git a/app/components/views/GovernancePage/Proposals/ProposalsListItem/index.js b/app/components/views/GovernancePage/Proposals/ProposalsListItem/index.js
new file mode 100644
index 0000000000..cf68692ba3
--- /dev/null
+++ b/app/components/views/GovernancePage/Proposals/ProposalsListItem/index.js
@@ -0,0 +1 @@
+export { default } from "./ProposalsListItem";
diff --git a/app/components/views/GovernancePage/Proposals/ProposalsTab.jsx b/app/components/views/GovernancePage/Proposals/ProposalsTab.jsx
index 80060e5c75..525e22621e 100644
--- a/app/components/views/GovernancePage/Proposals/ProposalsTab.jsx
+++ b/app/components/views/GovernancePage/Proposals/ProposalsTab.jsx
@@ -1,53 +1,14 @@
import { FormattedMessage as T } from "react-intl";
import { createElement as h } from "react";
-import { Button, Tooltip, classNames } from "pi-ui";
-import ProposalsList from "./ProposalsList/ProposalsList";
+import { Button, Tooltip } from "pi-ui";
+import ProposalsList from "./ProposalsList";
import PoliteiaDisabled from "./PoliteiaDisabled";
import { PoliteiaLink as PiLink } from "shared";
import { TabbedPage, TabbedPageTab as Tab } from "layout";
import { useProposalsTab } from "./hooks";
import styles from "./ProposalsTab.module.css";
-
-const PageHeader = ({ isTestnet, onRefreshProposals }) => (
-
- {/* TODO: wrapp this 'header' in a component same header is used in VotingPrefs.jsx */}
-
-
-
-
-
-
- }
- placement="left">
-
-
-
-
-);
+import PageHeader from "../PageHeader";
+import { SmallButton } from "buttons";
const ListLink = ({ count, children }) => (
<>
@@ -72,19 +33,68 @@ const ProposalsTab = () => {
}
return (
}
header={
-
+
+
+
+
+
+
+ }
+ placement="right">
+
+
+
+ >
+ }
+ description={
+