From 74b0b0a621d427c6a62ac88c14f9c8e9eb317418 Mon Sep 17 00:00:00 2001 From: Ted Ian Osias Date: Wed, 8 Oct 2025 21:36:15 +0800 Subject: [PATCH 1/3] Fix: enforce 2FA before persisting session; refactor Login.jsx for clarity (bug-001-two-factor-does-not-prevent-login) --- src/pages/Login.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 15add191..4ba4f161 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; import { MetaTags } from "react-meta-tags"; import { withTranslation } from "react-i18next"; From ff843b492814b27d61ddc8c2244709764a5deebd Mon Sep 17 00:00:00 2001 From: Ted Ian Osias Date: Wed, 8 Oct 2025 21:45:48 +0800 Subject: [PATCH 2/3] Chore: fix CI lint by removing unused useMemo import in Login.jsx --- src/pages/Login.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 4ba4f161..15add191 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { MetaTags } from "react-meta-tags"; import { withTranslation } from "react-i18next"; From 33f446f3aa93c7df6b71e6b39ec2faf7a85622cd Mon Sep 17 00:00:00 2001 From: Ted Ian Osias Date: Wed, 8 Oct 2025 23:14:50 +0800 Subject: [PATCH 3/3] Governance UI/UX: proposal card interactivity, breadcrumbs, loaders; vote outcome constants + error handling; accessibility & responsiveness; remove optimistic counts --- src/components/global/Breadcrumbs.jsx | 30 +++++++ src/components/governance/AddressList.jsx | 14 ++- src/components/governance/ProposalCard.jsx | 66 +++++++++++--- .../governance/ProposalCardInfo.jsx | 25 +++++- src/components/governance/ProposalsList.jsx | 21 ++++- src/constants/votes.js | 15 ++++ src/pages/Governance.js | 6 ++ src/scss/_tables.scss | 89 +++++++++++++++++-- src/utils/sign-vote.js | 2 +- 9 files changed, 238 insertions(+), 30 deletions(-) create mode 100644 src/components/global/Breadcrumbs.jsx create mode 100644 src/constants/votes.js diff --git a/src/components/global/Breadcrumbs.jsx b/src/components/global/Breadcrumbs.jsx new file mode 100644 index 00000000..1e94ed30 --- /dev/null +++ b/src/components/global/Breadcrumbs.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +/** + * Simple breadcrumb navigation + * @param {Array<{label: string, to?: string}>} items + */ +const Breadcrumbs = ({ items = [] }) => { + if (!items.length) return null; + return ( + + ); +}; + +export default Breadcrumbs; + diff --git a/src/components/governance/AddressList.jsx b/src/components/governance/AddressList.jsx index 1ee61213..12b6e5bb 100644 --- a/src/components/governance/AddressList.jsx +++ b/src/components/governance/AddressList.jsx @@ -181,9 +181,12 @@ const AddressList = ({proposal, vote, onAfterVote}) => { // }) }) .catch(err => { + const message = (voteData === 'Invalid network version') + ? 'Invalid network version' + : (err?.response?.data?.message || err?.message || 'Unknown error'); addressErrorVote.push({ name: address.name, - err: (voteData === 'Invalid network version') ? 'Invalid network version' : err.response.data.message + err: message }) }); } @@ -233,9 +236,12 @@ const AddressList = ({proposal, vote, onAfterVote}) => { ) : ( <> - { - loadingAddress &&

Loading...

- } + {loadingAddress && ( +
+
+ Loading addresses… +
+ )} { (!loadingAddress && addressList.length === 0) &&

You don't have a voting address please add one

} diff --git a/src/components/governance/ProposalCard.jsx b/src/components/governance/ProposalCard.jsx index f360b6fc..dc02e39b 100644 --- a/src/components/governance/ProposalCard.jsx +++ b/src/components/governance/ProposalCard.jsx @@ -1,9 +1,10 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useRef } from "react"; import { useTranslation } from "react-i18next"; import { Collapse } from "react-collapse"; import swal from "sweetalert2"; import { useUser } from "../../context/user-context"; +import { VOTE_OUTCOME } from "../../constants/votes"; import ProposalCardInfo from "./ProposalCardInfo"; import CustomModal from "../global/CustomModal"; import AddressList from "./AddressList"; @@ -35,6 +36,9 @@ function ProposalCard({ proposal, enabled, userInfo, onLoadProposals }) { const [month_remaining, setMonth_remaining] = useState(0); const [payment_type, setPayment_type] = useState(""); const [vote, setVote] = useState(""); + const titleButtonRef = useRef(null); + const touchStartX = useRef(null); + const touchStartY = useRef(null); /** * UseEffect that calculates the approx payment dates of the current proposal @@ -74,6 +78,8 @@ function ProposalCard({ proposal, enabled, userInfo, onLoadProposals }) { }; }, [proposal]); + + /** * Function that receives a string of a number with , as separator and returns it without it * @function @@ -157,7 +163,7 @@ function ProposalCard({ proposal, enabled, userInfo, onLoadProposals }) { }; return ( -
+
{proposal.YesCount} {proposal.NoCount} @@ -171,13 +177,36 @@ function ProposalCard({ proposal, enabled, userInfo, onLoadProposals }) {
{proposalDate(proposal.CreationTime)}
- setUseCollapse(!useCollapse)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setUseCollapse(!useCollapse); + } + }} + onTouchStart={(e) => { + const t = e.changedTouches[0]; + touchStartX.current = t.clientX; + touchStartY.current = t.clientY; + }} + onTouchEnd={(e) => { + const t = e.changedTouches[0]; + const dx = t.clientX - (touchStartX.current ?? 0); + const dy = t.clientY - (touchStartY.current ?? 0); + if (Math.abs(dx) > 40 && Math.abs(dy) < 30) { + setUseCollapse((prev) => !prev); + } + }} > - {proposal.title || proposal.name} - + {proposal.title || proposal.name} + {useCollapse ? '▾' : '▸'} +
@@ -191,47 +220,56 @@ function ProposalCard({ proposal, enabled, userInfo, onLoadProposals }) { isOpened={useCollapse} initialStyle={{ height: 0, overflow: "hidden" }} > -
+
+
+ +
{user && (