From 5fa32eae221a158345142d7f8edf3e4b449621da Mon Sep 17 00:00:00 2001 From: willpan Date: Fri, 18 Oct 2024 17:19:58 +0800 Subject: [PATCH 1/3] feat: update-upload-api --- power-voting/src/App.tsx | 5 ++-- .../src/components/VoteList/index.tsx | 28 +++++++++++-------- power-voting/src/pages/Home/index.tsx | 3 +- power-voting/src/utils/index.ts | 2 +- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/power-voting/src/App.tsx b/power-voting/src/App.tsx index 81a5eb2..7dc9a27 100644 --- a/power-voting/src/App.tsx +++ b/power-voting/src/App.tsx @@ -262,13 +262,14 @@ const App: React.FC = () => { }; const searchKey = async (value?: string) => { const params = { + chainId, page: 1, pageSize: 5, searchKey: value?.trim(), status: status === VOTE_ALL_STATUS ? 0 : status } const { data: { data: votingData } } = await axios.get('/api/proposal/list', { params }) - setVotingList({ votingList: votingData.list || [], totalPage: votingData.total, searchKey: value }) + setVotingList({ votingList: votingData?.list || [], totalPage: votingData?.total || 0, searchKey: value }) } useEffect(() => { if(!chain) return @@ -338,7 +339,7 @@ const App: React.FC = () => {
{languageOptions.map((item) => { return ( -
changeLanguage(item.value)}> +
changeLanguage(item.value)}>
{item.label}
) diff --git a/power-voting/src/components/VoteList/index.tsx b/power-voting/src/components/VoteList/index.tsx index e7e3d8d..f044ff1 100644 --- a/power-voting/src/components/VoteList/index.tsx +++ b/power-voting/src/components/VoteList/index.tsx @@ -120,18 +120,22 @@ const VoteList: React.FC = ({ voteList, chain }) => { } }); - arr.forEach((item, index) => { - // Check if it's not the last item in the array - if (index < arr.length - 1) { - // Append percent value and count with a plus sign - totalPercent += `${item} / ${count} + `; - } else { - // Append percent value and count without a plus sign - totalPercent += `${item} / ${count}`; - } - }); - // Append the final vote percentage - totalPercent += `= ${votes}%`; + if (arr.length) { + arr.forEach((item, index) => { + // Check if it's not the last item in the array + if (index < arr.length - 1) { + // Append percent value and count with a plus sign + totalPercent += `${item} / ${count} + `; + } else { + // Append percent value and count without a plus sign + totalPercent += `${item} / ${count}`; + } + }); + // Append the final vote percentage + totalPercent += `= ${votes}%`; + } else { + totalPercent += '0%'; + } return
{totalPercent}
; } diff --git a/power-voting/src/pages/Home/index.tsx b/power-voting/src/pages/Home/index.tsx index bddb74e..64178db 100644 --- a/power-voting/src/pages/Home/index.tsx +++ b/power-voting/src/pages/Home/index.tsx @@ -299,7 +299,8 @@ const Home = () => { const queryVotingList = async (page: number, proposalStatus: number) => { const params = { - page: page, + chainId, + page, pageSize: 5, searchKey: searchKey, status: proposalStatus === VOTE_ALL_STATUS ? 0 : proposalStatus, diff --git a/power-voting/src/utils/index.ts b/power-voting/src/utils/index.ts index 9d0d8f1..00d1921 100644 --- a/power-voting/src/utils/index.ts +++ b/power-voting/src/utils/index.ts @@ -160,7 +160,7 @@ export const getWeb3IpfsId = async (params: object | string) => { }, }, ); - return resp.data["data"]["root"]["/"]; + return resp.data.data; } catch (e) { return "" } From b3e86b763a3871cc6a750076f822127a5207bd82 Mon Sep 17 00:00:00 2001 From: willpan Date: Fri, 25 Oct 2024 17:06:41 +0800 Subject: [PATCH 2/3] fix: multi language error --- power-voting/src/App.tsx | 364 +++----------- power-voting/src/common/abi/oracle.json | 448 ++++++++++------- power-voting/src/common/abi/power-voting.json | 457 +++++++++--------- power-voting/src/common/consts.ts | 33 +- power-voting/src/common/hooks.ts | 12 + power-voting/src/common/store.ts | 3 +- power-voting/src/components/CreateTable.tsx | 7 +- power-voting/src/components/Header.tsx | 381 +++++++++++++++ power-voting/src/components/Table.tsx | 11 +- power-voting/src/index.tsx | 16 +- power-voting/src/lang/config.tsx | 4 +- power-voting/src/lang/en.json | 48 +- power-voting/src/lang/zh.json | 27 +- power-voting/src/pages/CreateVote/index.tsx | 150 +++--- power-voting/src/pages/Fip/approve/index.tsx | 15 +- power-voting/src/pages/Fip/propose/index.tsx | 192 ++++---- power-voting/src/pages/Fip/revoke/index.tsx | 17 +- power-voting/src/pages/Home/index.tsx | 279 ++--------- power-voting/src/pages/MinerId/index.tsx | 11 +- .../src/pages/UcanDelegate/add/index.tsx | 15 +- .../src/pages/UcanDelegate/delete/index.tsx | 10 +- .../src/pages/UcanDelegate/help/index.tsx | 2 +- power-voting/src/pages/Vote/index.tsx | 45 +- .../src/pages/VotingResults/index.tsx | 27 +- power-voting/src/utils/index.ts | 20 + power-voting/webpack.config.js | 7 + 26 files changed, 1350 insertions(+), 1251 deletions(-) create mode 100644 power-voting/src/components/Header.tsx diff --git a/power-voting/src/App.tsx b/power-voting/src/App.tsx index 7dc9a27..75e2453 100644 --- a/power-voting/src/App.tsx +++ b/power-voting/src/App.tsx @@ -12,50 +12,46 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { SearchOutlined } from '@ant-design/icons'; import { - ConnectButton, - useConnectModal + lightTheme, + RainbowKitProvider, } from "@rainbow-me/rainbowkit"; -import { ConfigProvider, Dropdown, FloatButton, Input, Modal, theme } from 'antd'; -import enUS from 'antd/locale/en_US'; -import zhCN from 'antd/locale/zh_CN'; -import axios from "axios"; +import { ConfigProvider, FloatButton, theme } from 'antd'; import dayjs from 'dayjs'; import 'dayjs/locale/zh-cn'; -import React, { useEffect, useRef, useState } from "react"; -import Countdown from 'react-countdown'; +import enUS from 'antd/locale/en_US'; +import zhCN from 'antd/locale/zh_CN'; import { useTranslation } from 'react-i18next'; -import { Link, useLocation, useNavigate, useRoutes } from "react-router-dom"; +import React, { useEffect, useRef } from "react"; +import { useLocation, useRoutes } from "react-router-dom"; import "tailwindcss/tailwind.css"; import { useAccount } from "wagmi"; import timezones from '../public/json/timezons.json'; -import { STORING_DATA_MSG, VOTE_ALL_STATUS } from "./common/consts"; -import { useCheckFipEditorAddress, useVoterInfoSet } from "./common/hooks"; -import { useCurrentTimezone, usePropsalStatus, useVoterInfo, useVotingList } from "./common/store"; +import { mainnetChainId } from "./common/consts" +import { useVoterInfoSet } from "./common/hooks" +import { useCurrentTimezone, useVoterInfo } from "./common/store"; import "./common/styles/reset.less"; +import Header from "./components/Header"; import Footer from './components/Footer'; import './lang/config'; import routes from "./router"; + +const lang = localStorage.getItem("lang") || "en"; +dayjs.locale(lang === 'en' ? lang : "zh-cn"); + const App: React.FC = () => { // Destructure values from custom hooks - const { chain, address, isConnected } = useAccount(); - const chainId = chain?.id || 0; + const { chain, address} = useAccount(); + const chainId = chain?.id || mainnetChainId; const prevAddressRef = useRef(address); - const { openConnectModal } = useConnectModal(); - const navigate = useNavigate(); - const location = useLocation(); + + const { i18n } = useTranslation(); // Render routes based on URL const element = useRoutes(routes); const isLanding = false;//location.pathname === "/" || element?.props?.match?.route?.path === "*" - // State variables - const [expirationTime, setExpirationTime] = useState(0); - const [modalOpen, setModalOpen] = useState(false); - const [language, setLanguage] = useState({ meaning: 'en', value: enUS }); - const [isFocus, setIsFocus] = useState(false); // Determine whether the mouse has clicked on the search box - const [searchValue, setSearchValue] = useState(); // Stores the value of the search box + // Get the user's timezone const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const text = timezones.find((item: any) => item.value === timezone)?.text; @@ -67,20 +63,28 @@ const App: React.FC = () => { // Get voter information using custom hook const { voterInfo } = useVoterInfoSet(chainId, address); - const { isFipEditorAddress } = useCheckFipEditorAddress(chainId, address); - // Update voter information in state const setVoterInfo = useVoterInfo((state: any) => state.setVoterInfo); - const setVotingList = useVotingList((state: any) => state.setVotingList); + // Update current timezone in state const setTimezone = useCurrentTimezone((state: any) => state.setTimezone); - const status = usePropsalStatus((state: any) => state.status); const { pathname } = useLocation(); - const { t, i18n } = useTranslation(); + + const handleChange = (value: string) => { + i18n.changeLanguage(value); + localStorage.setItem("lang", value); + if (value === 'en') { + dayjs.locale('en'); + } else if (value === 'zh') { + dayjs.locale('zh-cn'); + } + } + useEffect(() => { window.scrollTo(0, 0); }, [pathname]); + // Reload the page if address changes useEffect(() => { const prevAddress = prevAddressRef.current; @@ -103,287 +107,37 @@ const App: React.FC = () => { } }, [GMTOffset]) - /** - * Handle delegation action - */ - const handleDelegate = async () => { - // Retrieve ucanStorageData from localStorage - const ucanStorageData = JSON.parse(localStorage.getItem('ucanStorage') || '[]'); - const ucanIndex = ucanStorageData?.findIndex((item: any) => item.address === address); - if (ucanIndex > -1) { - if (Date.now() < ucanStorageData[ucanIndex].timestamp) { - setModalOpen(true); - setExpirationTime(ucanStorageData[ucanIndex].timestamp); - // Data has not expired - return; - } else { - // Data has expired - setExpirationTime(0); - ucanStorageData?.splice(ucanIndex, 1); - localStorage.setItem('ucanStorage', JSON.stringify(ucanStorageData)); - } - } - - // Prompt user to connect if not already connected - if (!isConnected) { - openConnectModal && openConnectModal(); - return; - } - - - if (!voterInfo) { - navigate('/ucanDelegate/add'); - return - } - // Determine if the user has a GitHub account - const isGithubType = !!voterInfo[0]; - if (voterInfo[2]) { - // Fetch data from IPFS using voter's identifier - const { data } = await axios.get(`https://${voterInfo[2]}.ipfs.w3s.link/`); - if (isGithubType) { - // Process GitHub data and navigate to appropriate page - const regex = /\/([^/]+)\/([^/]+)\/git\/blobs\/(\w+)/; - const result = data.match(regex); - const aud = result[1]; - navigate('/ucanDelegate/delete', { - state: { - params: { - isGithubType, - aud, - prf: '' - } - } - }); - } - else { - // Process non-GitHub data and navigate to appropriate page - const decodeString = atob(data.split('.')[1]); - const payload = JSON.parse(decodeString); - const { aud, prf } = payload; - navigate('/ucanDelegate/delete', { - state: { - params: { - isGithubType, - aud, - prf - } - } - }); - } - } else { - // Navigate to add delegate page if no voter information is available - navigate('/ucanDelegate/add'); - } - } - - const handleJump = (route: string) => { - if (!isConnected) { - openConnectModal && openConnectModal(); - return; - } - navigate(route); - } - - const items: any = [ - { - key: 'ucan', - label: ( - - {t('content.UCANDelegates')} - - ), - }, - { - key: 'minerId', - label: ( - { handleJump('/minerid') }} - > - {t('content.minerIDsManagement')} - - ), - }, - ]; + const lang = localStorage.getItem("lang") || "en"; - if (isFipEditorAddress) { - items.push({ - key: '3', - label: 'FIP Editor Management', - children: [ - { - key: '3-1', - label: ( - { handleJump('/fip-editor/propose') }} - > - {t('content.propose')} - - ), - }, - { - key: '3-2', - label: ( - { handleJump('/fip-editor/approve') }} - > - {t('content.approve')} - - ), - }, - { - key: '3-3', - label: ( - { handleJump('/fip-editor/revoke') }} - > - {t('content.revoke')} - - ), - }, - ], - }) - } - - const languageOptions = [ - { label: 'EN', value: 'en' }, - { label: '中文', value: 'zh' }, - ]; - const changeLanguage = (value: string) => { - i18n.changeLanguage(value) - if (value === 'en') { - setLanguage({ meaning: 'en', value: enUS }); - dayjs.locale('en'); - } else if (value === 'zh') { - setLanguage({ meaning: 'zh', value: zhCN }); - dayjs.locale('zh-cn'); - } - }; - const searchKey = async (value?: string) => { - const params = { - chainId, - page: 1, - pageSize: 5, - searchKey: value?.trim(), - status: status === VOTE_ALL_STATUS ? 0 : status - } - const { data: { data: votingData } } = await axios.get('/api/proposal/list', { params }) - setVotingList({ votingList: votingData?.list || [], totalPage: votingData?.total || 0, searchKey: value }) - } - useEffect(() => { - if(!chain) return - setSearchValue('') - searchKey() - }, [chain]) return ( - + -
- {!isLanding &&
-
-
-
- - - -
-
- - {t('content.powerVoting')} - -
- {(location.pathname === '/home' || location.pathname === '/') && -
- searchKey(searchValue)} className={`${isFocus ? "text-[#1677ff]" : "text-[#8b949e]"} text-xl hover:text-[#1677ff]`} />} - onClick={() => setIsFocus(true)} - onBlur={() => setIsFocus(false)} - onChange={(e) => setSearchValue(e.currentTarget.value)} - onPressEnter={() => searchKey(searchValue)} - value={searchValue} - className={`${isFocus ? 'w-[270px]' : "w-[180px]"} font-medium text-base item-center text-slate-800 bg-[#f7f7f7] rounded-lg`} - /> -
- - } - -
-
- - - -
- -
- {languageOptions.map((item) => { - return ( -
changeLanguage(item.value)}> -
{item.label}
-
- ) - }) - } -
-
-
- { setModalOpen(false) }} - footer={false} - style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }} - > -

{t(STORING_DATA_MSG)} {t('content.pleaseWait')}:  - { - if (completed) { - // Render a completed state - setModalOpen(false); - } else { - // Render a countdown - return {minutes}:{seconds}; - } - }} - /> -

-
+ }} locale={lang === "en" ? enUS : zhCN}> +
+ {!isLanding &&
} +
+ { + element + }
-
} -
- { - element - } +
+
-
- -
- + + ) } diff --git a/power-voting/src/common/abi/oracle.json b/power-voting/src/common/abi/oracle.json index 7c5e751..48127f6 100644 --- a/power-voting/src/common/abi/oracle.json +++ b/power-voting/src/common/abi/oracle.json @@ -1,4 +1,42 @@ [ + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "voter", + "type": "address" + } + ], + "name": "addF4Task", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64[]", + "name": "minerIds", + "type": "uint64[]" + }, + { + "internalType": "address", + "name": "voter", + "type": "address" + } + ], + "name": "addMinerIds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -10,6 +48,37 @@ "name": "AddressEmptyCode", "type": "error" }, + { + "inputs": [ + { + "internalType": "string", + "name": "date", + "type": "string" + }, + { + "internalType": "string", + "name": "cid", + "type": "string" + } + ], + "name": "addSnapshot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "ucanCid", + "type": "string" + } + ], + "name": "addTask", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -28,9 +97,16 @@ }, { "inputs": [], - "name": "FailedInnerCall", + "name": "FailedCall", "type": "error" }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "InvalidInitialization", @@ -123,6 +199,62 @@ "name": "ZeroAddressError", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "voterAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64[]", + "name": "actorIds", + "type": "uint64[]" + }, + { + "indexed": false, + "internalType": "string", + "name": "github", + "type": "string" + } + ], + "name": "CreateDelegate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "voterAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64[]", + "name": "actorIds", + "type": "uint64[]" + }, + { + "indexed": false, + "internalType": "uint64[]", + "name": "minerIds", + "type": "uint64[]" + }, + { + "indexed": false, + "internalType": "string", + "name": "github", + "type": "string" + } + ], + "name": "DeleteDelegate", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -175,99 +307,205 @@ "type": "event" }, { - "anonymous": false, "inputs": [ { - "indexed": true, "internalType": "address", - "name": "implementation", + "name": "voterAddress", "type": "address" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" } ], - "name": "Upgraded", - "type": "event" + "name": "removeVoter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { "inputs": [], - "name": "UPGRADE_INTERFACE_VERSION", - "outputs": [ + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { - "internalType": "string", - "name": "", - "type": "string" + "components": [ + { + "internalType": "uint64[]", + "name": "actorIds", + "type": "uint64[]" + }, + { + "internalType": "uint64[]", + "name": "minerIds", + "type": "uint64[]" + }, + { + "internalType": "string", + "name": "githubAccount", + "type": "string" + }, + { + "internalType": "address", + "name": "ethAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "ucanCid", + "type": "string" + } + ], + "internalType": "struct IOracle.VoterInfo", + "name": "voterInfoParam", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "taskId", + "type": "uint256" } ], - "stateMutability": "view", + "name": "taskCallback", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [], - "name": "acceptOwnership", + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { + "anonymous": false, "inputs": [ { - "internalType": "uint64", - "name": "", - "type": "uint64" + "indexed": false, + "internalType": "address", + "name": "voterAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64[]", + "name": "minerIds", + "type": "uint64[]" } ], - "name": "actorIdList", - "outputs": [ + "name": "UpdateMinerId", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "nodeAddress", + "type": "address" + }, { "internalType": "bool", - "name": "", + "name": "allow", "type": "bool" } ], - "stateMutability": "view", + "name": "updateNodeAllowList", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", - "name": "voter", + "name": "powerVotingAddress", "type": "address" } ], - "name": "addF4Task", + "name": "updatePowerVotingContract", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { + "anonymous": false, "inputs": [ { - "internalType": "uint64[]", - "name": "minerIds", - "type": "uint64[]" - }, + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [ { "internalType": "address", - "name": "voter", + "name": "newImplementation", "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" } ], - "name": "addMinerIds", + "name": "upgradeToAndCall", "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "name": "actorIdList", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "string", - "name": "ucanCid", + "name": "", "type": "string" } ], - "name": "addTask", - "outputs": [], - "stateMutability": "nonpayable", + "name": "dateToCid", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", "type": "function" }, { @@ -393,13 +631,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -471,31 +702,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "voterAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "taskId", - "type": "uint256" - } - ], - "name": "removeVoter", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -515,51 +721,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint64[]", - "name": "actorIds", - "type": "uint64[]" - }, - { - "internalType": "uint64[]", - "name": "minerIds", - "type": "uint64[]" - }, - { - "internalType": "string", - "name": "githubAccount", - "type": "string" - }, - { - "internalType": "address", - "name": "ethAddress", - "type": "address" - }, - { - "internalType": "string", - "name": "ucanCid", - "type": "string" - } - ], - "internalType": "struct IOracle.VoterInfo", - "name": "voterInfoParam", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "taskId", - "type": "uint256" - } - ], - "name": "taskCallback", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -580,65 +741,16 @@ "type": "function" }, { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "nodeAddress", - "type": "address" - }, - { - "internalType": "bool", - "name": "allow", - "type": "bool" - } - ], - "name": "updateNodeAllowList", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "powerVotingAddress", - "type": "address" - } - ], - "name": "updatePowerVotingContract", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - }, + "inputs": [], + "name": "UPGRADE_INTERFACE_VERSION", + "outputs": [ { - "internalType": "bytes", - "name": "data", - "type": "bytes" + "internalType": "string", + "name": "", + "type": "string" } ], - "name": "upgradeToAndCall", - "outputs": [], - "stateMutability": "payable", + "stateMutability": "view", "type": "function" }, { @@ -713,4 +825,4 @@ "stateMutability": "view", "type": "function" } -] +] \ No newline at end of file diff --git a/power-voting/src/common/abi/power-voting.json b/power-voting/src/common/abi/power-voting.json index 469a93b..88121a9 100644 --- a/power-voting/src/common/abi/power-voting.json +++ b/power-voting/src/common/abi/power-voting.json @@ -1,11 +1,4 @@ [ - { - "inputs": [], - "name": "acceptOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -17,19 +10,6 @@ "name": "AddFIPError", "type": "error" }, - { - "inputs": [ - { - "internalType": "uint64[]", - "name": "minerIds", - "type": "uint64[]" - } - ], - "name": "addMinerId", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -63,24 +43,6 @@ "name": "AddressIsAlreadyFipEditor", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "fipEditorAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "approveFipEditor", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -114,57 +76,6 @@ "name": "CannotVoteForOwnProposal", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "fipEditorAddress", - "type": "address" - }, - { - "internalType": "string", - "name": "voterInfoCid", - "type": "string" - }, - { - "internalType": "int8", - "name": "fipEditorProposalType", - "type": "int8" - } - ], - "name": "createFipEditorProposal", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "proposalCid", - "type": "string" - }, - { - "internalType": "uint248", - "name": "startTime", - "type": "uint248" - }, - { - "internalType": "uint248", - "name": "expTime", - "type": "uint248" - }, - { - "internalType": "uint256", - "name": "proposalType", - "type": "uint256" - } - ], - "name": "createProposal", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -183,22 +94,9 @@ }, { "inputs": [], - "name": "FailedInnerCall", + "name": "FailedCall", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "oracleAddress", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -275,31 +173,6 @@ "name": "OwnableUnauthorizedAccount", "type": "error" }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "fipEditorAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "revokeFipEditor", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -322,45 +195,6 @@ "name": "TimeError", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "ucanCid", - "type": "string" - } - ], - "name": "ucanDelegate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "oracleAddress", - "type": "address" - } - ], - "name": "updateOracleContract", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "UUPSUnauthorizedCallContext", @@ -503,42 +337,6 @@ "name": "Upgraded", "type": "event" }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "upgradeToAndCall", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "internalType": "string", - "name": "info", - "type": "string" - } - ], - "name": "vote", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "anonymous": false, "inputs": [ @@ -617,19 +415,26 @@ "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "GET_VOTER_INFO_SELECTOR", + "outputs": [ { - "internalType": "address", + "internalType": "bytes4", "name": "", - "type": "address" + "type": "bytes4" } ], - "name": "fipAddressMap", + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REVOKE_PROPOSAL_TYPE", "outputs": [ { - "internalType": "bool", + "internalType": "int8", "name": "", - "type": "bool" + "type": "int8" } ], "stateMutability": "view", @@ -637,25 +442,140 @@ }, { "inputs": [], - "name": "fipEditorProposalId", + "name": "UPGRADE_INTERFACE_VERSION", "outputs": [ { - "internalType": "uint256", + "internalType": "string", "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "addF4Task", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64[]", + "name": "minerIds", + "type": "uint64[]" + } + ], + "name": "addMinerId", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fipEditorAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "approveFipEditor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fipEditorAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "voterInfoCid", + "type": "string" + }, + { + "internalType": "int8", + "name": "fipEditorProposalType", + "type": "int8" + } + ], + "name": "createFipEditorProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "proposalCid", + "type": "string" + }, + { + "internalType": "uint248", + "name": "startTime", + "type": "uint248" + }, + { + "internalType": "uint248", + "name": "expTime", + "type": "uint248" + }, + { + "internalType": "uint256", + "name": "proposalType", "type": "uint256" } ], + "name": "createProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "fipAddressMap", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "GET_VOTER_INFO_SELECTOR", + "name": "fipEditorProposalId", "outputs": [ { - "internalType": "bytes4", + "internalType": "uint256", "name": "", - "type": "bytes4" + "type": "uint256" } ], "stateMutability": "view", @@ -797,6 +717,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "oracleAddress", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "oracleContract", @@ -893,28 +826,102 @@ }, { "inputs": [], - "name": "REVOKE_PROPOSAL_TYPE", - "outputs": [ + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { - "internalType": "int8", - "name": "", - "type": "int8" + "internalType": "address", + "name": "fipEditorAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" } ], - "stateMutability": "view", + "name": "revokeFipEditor", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [], - "name": "UPGRADE_INTERFACE_VERSION", - "outputs": [ + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "string", - "name": "", + "name": "ucanCid", "type": "string" } ], - "stateMutability": "view", + "name": "ucanDelegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "oracleAddress", + "type": "address" + } + ], + "name": "updateOracleContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "string", + "name": "info", + "type": "string" + } + ], + "name": "vote", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" } ] \ No newline at end of file diff --git a/power-voting/src/common/consts.ts b/power-voting/src/common/consts.ts index ecaacd4..50fc277 100644 --- a/power-voting/src/common/consts.ts +++ b/power-voting/src/common/consts.ts @@ -11,6 +11,9 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +import { filecoin } from 'wagmi/chains'; + +export const mainnetChainId = filecoin.id; export const powerVotingMainNetContractAddress = process.env.POWER_VOTING_MAINNET_CONTRACT_ADDRESS || ''; export const oracleMainNetContractAddress = process.env.ORACLE_MAINNET_CONTRACT_ADDRESS || ''; export const oraclePowerMainNetContractAddress = process.env.ORACLE_POWER_MAINNET_CONTRACT_ADDRESS || ''; @@ -25,8 +28,8 @@ export const uploadApi = '/api/w3storage/upload'; export const proposalHistoryApi = '/api/proposal/history'; export const proposalDraftAddApi = '/api/proposal/draft/add'; export const proposalDraftGetApi = '/api/proposal/draft/get'; - -export const worldTimeApi = 'https://worldtimeapi.org/api/timezone/Etc/UTC'; +export const blockHeightGetApi = '/api/filecoin/height'; +export const votePowerGetApi = '/api/proposal/draft/get'; export const IN_PROGRESS_STATUS = 2; export const COMPLETED_STATUS = 4; export const PENDING_STATUS = 1; @@ -288,7 +291,7 @@ var ( 2.1 Go to https://vote.storswift.io. -2.2 Click UCAN Delegates to cancel authorization. The website will monitor whether the Eth account has UCAN authorization or not. The action will cancel the authorization if it does. +2.2 Click UCAN Delegates to cancel authorization. The website will monitor whether the Eth account has UCAN authorization or not. The action will cancel the authorization if it does.

@@ -497,9 +500,9 @@ export const NOT_FIP_EDITOR_MSG = 'content.fipCreateProposals'; export const NO_FIP_EDITOR_APPROVE_ADDRESS_MSG = 'content.inputAddress'; export const NO_FIP_EDITOR_REVOKE_ADDRESS_MSG = 'content.selectAddress'; export const NO_ENOUGH_FIP_EDITOR_REVOKE_ADDRESS_MSG = 'content.twoFIPRevoke'; -export const FIP_ALREADY_EXECUTE_MSG = "content.activePproposal" +export const FIP_ALREADY_EXECUTE_MSG = "content.activeProposal" export const FIP_APPROVE_SELF_MSG = "content.noPropose" -export const FIP_APPROVE_ALREADY_MSG = "content.addressDditor" +export const FIP_APPROVE_ALREADY_MSG = "content.addressEditor" export const HAVE_APPROVED_MSG = 'content.alreadyApproved'; export const HAVE_REVOKED_MSG = 'content.alreadyRevoked'; export const CAN_NOT_REVOKE_YOURSELF_MSG = 'content.revokeYourself'; @@ -507,23 +510,3 @@ export const SAVE_DRAFT_SUCCESS = "content.saveSuccess" export const SAVE_DRAFT_TOO_LARGE = "content.savedDescriptionCharacters" export const SAVE_DRAFT_FAIL = "content.saveFail" export const UPLOAD_DATA_FAIL_MSG = "content.saveDataFail" - -// Converts hexadecimal to a string -export const hexToString = (hex: any) => { - if(!hex){ - return ''; - } - let str = ''; - if (hex.substring(1, 3) === '0x') { - str = hex.substring(3) - } else { - str = hex; - } - // Split a hexadecimal string by two characters - const pairs = str.match(/[\dA-Fa-f]{2}/g); - if (pairs == null) { - return ''; - } - // Converts split hexadecimal numbers to characters and concatenates them - return pairs.map((pair: any) => String.fromCharCode(parseInt(pair, 16))).join('').replace(/[^\x20-\x7E]/g, '').trim(); -} diff --git a/power-voting/src/common/hooks.ts b/power-voting/src/common/hooks.ts index 64afd4a..0628e7b 100644 --- a/power-voting/src/common/hooks.ts +++ b/power-voting/src/common/hooks.ts @@ -42,6 +42,18 @@ export const useCheckFipEditorAddress = (chainId: number, address: `0x${string}` }; } +export const useVoterAddress = (chainId: number) => { + const { data, isSuccess: voterAddressSuccess } = useReadContract({ + address: getContractAddress(chainId, 'oracle'), + abi: oracleAbi, + functionName: 'getVoterAddresses', + }); + return { + voterAddress: data, + voterAddressSuccess + } as any; +} + export const useLatestId = (chainId: number, enabled: boolean) => { const { data: latestId, isLoading: getLatestIdLoading, refetch } = useReadContract({ address: getContractAddress(chainId, 'powerVoting'), diff --git a/power-voting/src/common/store.ts b/power-voting/src/common/store.ts index cd0682c..9b6b516 100644 --- a/power-voting/src/common/store.ts +++ b/power-voting/src/common/store.ts @@ -1,5 +1,4 @@ import { create } from 'zustand'; - interface StoringCidState { storingCid: string[]; } @@ -22,7 +21,7 @@ export const useVotingList = create(set => ({ setVotingList: (newData: any) => set({ votingData: newData }), })); -export const usePropsalStatus = create(set => ({ +export const useProposalStatus = create(set => ({ status: '', setStatusList: (status: 0) => set({ status: status }), })); diff --git a/power-voting/src/components/CreateTable.tsx b/power-voting/src/components/CreateTable.tsx index a49a809..adcaae2 100644 --- a/power-voting/src/components/CreateTable.tsx +++ b/power-voting/src/components/CreateTable.tsx @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { QuestionCircleOutlined } from '@ant-design/icons'; import type { ReactNode } from 'react'; import React from 'react'; -import { QuestionCircleOutlined } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; -import { FILECOIN_AUTHORIZE_DOC, FILECOIN_DEAUTHORIZE_DOC, GITHUB_AUTHORIZE_DOC, GITHUB_DEAUTHORIZE_DOC } from "../common/consts"; export default function Table({ title = '', link = {} as { type: string, action: string, href: string }, list = [] as { name: string, hide?: boolean, comp: ReactNode, width?: number, desc?: ReactNode, }[], subTitle =

}) { const navigate = useNavigate(); @@ -25,9 +24,9 @@ export default function Table({ title = '', link = {} as { type: string, action: const handleJump = () => { let doc = ''; if (type === 'filecoin') { - doc = action === 'authorize' ? FILECOIN_AUTHORIZE_DOC : FILECOIN_DEAUTHORIZE_DOC; + doc = action === 'authorize' ? 'filecion_authorize_doc' : 'filecion_deauthorize_doc'; } else { - doc = action === 'authorize' ? GITHUB_AUTHORIZE_DOC : GITHUB_DEAUTHORIZE_DOC; + doc = action === 'authorize' ? 'github_authorize_doc' : 'github_deauthorize_doc'; } navigate(href, { state: { diff --git a/power-voting/src/components/Header.tsx b/power-voting/src/components/Header.tsx new file mode 100644 index 0000000..c6748eb --- /dev/null +++ b/power-voting/src/components/Header.tsx @@ -0,0 +1,381 @@ +// Copyright (C) 2023-2024 StorSwift Inc. +// This file is part of the PowerVoting library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { SearchOutlined } from '@ant-design/icons'; +import { + ConnectButton, + useConnectModal, +} from "@rainbow-me/rainbowkit"; +import { Dropdown, Input, Modal } from 'antd'; +import axios from "axios"; +import 'dayjs/locale/zh-cn'; +import React, { useEffect, useRef, useState } from "react"; +import Countdown from 'react-countdown'; +import { useTranslation } from 'react-i18next'; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import "tailwindcss/tailwind.css"; +import { useAccount } from "wagmi"; +import timezones from '../../public/json/timezons.json'; +import { mainnetChainId, STORING_DATA_MSG, VOTE_ALL_STATUS } from "../common/consts"; +import { useCheckFipEditorAddress, useVoterAddress, useVoterInfoSet } from "../common/hooks"; +import { useCurrentTimezone, useProposalStatus, useVoterInfo, useVotingList } from "../common/store"; +import "../common/styles/reset.less"; +import '../lang/config'; + +const Header = (props: any) => { + const { changeLang } = props; + // Destructure values from custom hooks + const { chain, address, isConnected } = useAccount(); + const chainId = chain?.id || mainnetChainId; + const prevAddressRef = useRef(address); + const { openConnectModal } = useConnectModal(); + const navigate = useNavigate(); + const location = useLocation(); + + // State variables + const [expirationTime, setExpirationTime] = useState(0); + const [modalOpen, setModalOpen] = useState(false); + const [isFocus, setIsFocus] = useState(false); // Determine whether the mouse has clicked on the search box + const [searchValue, setSearchValue] = useState(); // Stores the value of the search box + // Get the user's timezone + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const text = timezones.find((item: any) => item.value === timezone)?.text; + + // Extract GMT offset from timezone + const regex = /(?<=\().*?(?=\))/g; + const GMTOffset = text?.match(regex); + + // Get voter information using custom hook + const { voterInfo } = useVoterInfoSet(chainId, address); + + const { isFipEditorAddress } = useCheckFipEditorAddress(chainId, address); + + const { voterAddressSuccess } = useVoterAddress(chainId); + + // Update voter information in state + const setVoterInfo = useVoterInfo((state: any) => state.setVoterInfo); + const setVotingList = useVotingList((state: any) => state.setVotingList); + + // Update current timezone in state + const setTimezone = useCurrentTimezone((state: any) => state.setTimezone); + const status = useProposalStatus((state: any) => state.status); + + const { pathname } = useLocation(); + const { t } = useTranslation(); + + useEffect(() => { + window.scrollTo(0, 0); + }, [pathname]); + + // Reload the page if address changes + useEffect(() => { + const prevAddress = prevAddressRef.current; + if (address && prevAddress !== address) { + window.location.reload(); + } + }, [address]); + + // Update voter information when available + useEffect(() => { + if (voterInfo) { + setVoterInfo(voterInfo); + } + }, [voterInfo]); + + // Set user's timezone based on GMT offset + useEffect(() => { + if (GMTOffset) { + setTimezone(GMTOffset); + } + }, [GMTOffset]) + + + /** + * Handle delegation action + */ + const handleDelegate = async () => { + // Prompt user to connect if not already connected + if (!isConnected) { + openConnectModal && openConnectModal(); + return; + } + + // Retrieve ucanStorageData from localStorage + const ucanStorageData = JSON.parse(localStorage.getItem('ucanStorage') || '[]'); + const ucanIndex = ucanStorageData?.findIndex((item: any) => item.address === address); + + if (ucanIndex > -1) { + if (Date.now() < ucanStorageData[ucanIndex].timestamp) { + setModalOpen(true); + setExpirationTime(ucanStorageData[ucanIndex].timestamp); + // Data has not expired + return; + } else { + // Data has expired + setExpirationTime(0); + ucanStorageData?.splice(ucanIndex, 1); + localStorage.setItem('ucanStorage', JSON.stringify(ucanStorageData)); + } + } + + if (!voterInfo) { + navigate('/ucanDelegate/add'); + return + } + // Determine if the user has a GitHub account + const isGithubType = !!voterInfo[0]; + if (voterInfo[2]) { + // Fetch data from IPFS using voter's identifier + const { data } = await axios.get(`https://${voterInfo[2]}.ipfs.w3s.link/`); + if (isGithubType) { + // Process GitHub data and navigate to appropriate page + const regex = /\/([^/]+)\/([^/]+)\/git\/blobs\/(\w+)/; + const result = data.match(regex); + const aud = result[1]; + navigate('/ucanDelegate/delete', { + state: { + params: { + isGithubType, + aud, + prf: '' + } + } + }); + } + else { + // Process non-GitHub data and navigate to appropriate page + const decodeString = atob(data.split('.')[1]); + const payload = JSON.parse(decodeString); + const { aud, prf } = payload; + navigate('/ucanDelegate/delete', { + state: { + params: { + isGithubType, + aud, + prf + } + } + }); + } + } else { + // Navigate to add delegate page if no voter information is available + navigate('/ucanDelegate/add'); + } + } + + const handleJump = (route: string) => { + if (!isConnected) { + openConnectModal && openConnectModal(); + return; + } + navigate(route); + } + + const items: any = [ + { + key: 'ucan', + label: ( + + {t('content.UCANDelegates')} + + ), + }, + { + key: 'minerId', + label: ( + { handleJump('/minerid') }} + > + {t('content.minerIDsManagement')} + + ), + }, + ]; + + if (isFipEditorAddress) { + items.push({ + key: '3', + label: t('content.fipEditorManagement'), + children: [ + { + key: '3-1', + label: ( + { handleJump('/fip-editor/propose') }} + > + {t('content.propose')} + + ), + }, + { + key: '3-2', + label: ( + { handleJump('/fip-editor/approve') }} + > + {t('content.approve')} + + ), + }, + { + key: '3-3', + label: ( + { handleJump('/fip-editor/revoke') }} + > + {t('content.revoke')} + + ), + }, + ], + }) + } + + const languageOptions = [ + { label: 'EN', value: 'en' }, + { label: '中文', value: 'zh' }, + ]; + + const lang = localStorage.getItem("lang") || "en"; + const changeLanguage = (value: string) => { + changeLang(value); + }; + const searchKey = async (value?: string) => { + const params = { + chainId, + page: 1, + pageSize: 5, + searchKey: value?.trim(), + status: status === VOTE_ALL_STATUS ? 0 : status + } + const { data: { data: votingData } } = await axios.get('/api/proposal/list', { params }); + setVotingList({ votingList: votingData?.list || [], totalPage: votingData?.total || 0, searchKey: value }); + } + + useEffect(() => { + if(!chain) return + setSearchValue(''); + searchKey(); + }, [chain]); + + useEffect(() => { + if (!isConnected) { + searchKey(); + } + // if (isConnected && voterAddressSuccess && voterAddress) { + // if (!voterAddress.includes(address)) { + // writeContract({ + // abi: fileCoinAbi, + // address: getContractAddress(chainId, 'powerVoting'), + // functionName: 'addF4Task', + // }); + // } + // } + }, [isConnected, voterAddressSuccess]); + + return ( +
+
+
+
+ + + +
+
+ + {t('content.powerVoting')} + +
+ {(location.pathname === '/home' || location.pathname === '/') && +
+ searchKey(searchValue)} className={`${isFocus ? "text-[#1677ff]" : "text-[#8b949e]"} text-xl hover:text-[#1677ff]`} />} + onClick={() => setIsFocus(true)} + onBlur={() => setIsFocus(false)} + onChange={(e) => setSearchValue(e.currentTarget.value)} + onPressEnter={() => searchKey(searchValue)} + value={searchValue} + className={`${isFocus ? 'w-[270px]' : "w-[180px]"} font-medium text-base item-center text-slate-800 bg-[#f7f7f7] rounded-lg`} + /> +
+ } + +
+
+ + + +
+ +
+ {languageOptions.map((item) => { + return ( +
changeLanguage(item.value)}> +
{item.label}
+
+ ) + }) + } +
+
+
+ { setModalOpen(false) }} + footer={false} + style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }} + > +

{t(STORING_DATA_MSG)} {t('content.pleaseWait')}:  + { + if (completed) { + // Render a completed state + setModalOpen(false); + } else { + // Render a countdown + return {minutes}:{seconds}; + } + }} + /> +

+
+
+
+ ) +} + +export default Header diff --git a/power-voting/src/components/Table.tsx b/power-voting/src/components/Table.tsx index 058f976..0a435c7 100644 --- a/power-voting/src/components/Table.tsx +++ b/power-voting/src/components/Table.tsx @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { QuestionCircleOutlined } from '@ant-design/icons'; import type { ReactNode } from 'react'; import React from 'react'; -import { QuestionCircleOutlined } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; -import { FILECOIN_AUTHORIZE_DOC, FILECOIN_DEAUTHORIZE_DOC, GITHUB_AUTHORIZE_DOC, GITHUB_DEAUTHORIZE_DOC } from "../common/consts"; export default function Table({ title = '', link = {} as { type: string, action: string, href: string }, list = [] as { name: string, hide?: boolean, comp: ReactNode, width?: number, desc?: ReactNode, }[], subTitle = null }) { const navigate = useNavigate(); @@ -25,9 +24,9 @@ export default function Table({ title = '', link = {} as { type: string, action: const handleJump = () => { let doc = ''; if (type === 'filecoin') { - doc = action === 'authorize' ? FILECOIN_AUTHORIZE_DOC : FILECOIN_DEAUTHORIZE_DOC; + doc = action === 'authorize' ? 'filecion_authorize_doc' : 'filecion_deauthorize_doc'; } else { - doc = action === 'authorize' ? GITHUB_AUTHORIZE_DOC : GITHUB_DEAUTHORIZE_DOC; + doc = action === 'authorize' ? 'github_authorize_doc' : 'github_deauthorize_doc'; } navigate(href, { state: { @@ -62,8 +61,8 @@ export default function Table({ title = '', link = {} as { type: string, action: {list.filter((item: { name: string, hide?: boolean, comp: ReactNode, width?: number, desc?: ReactNode }) => !item.hide).map((item: { name: string, hide?: boolean, comp: ReactNode, width?: number, desc?: ReactNode }) => ( - - + +
{item.name}
{item.desc &&
{item.desc} diff --git a/power-voting/src/index.tsx b/power-voting/src/index.tsx index ba105fb..47577dc 100644 --- a/power-voting/src/index.tsx +++ b/power-voting/src/index.tsx @@ -15,8 +15,6 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { - lightTheme, - RainbowKitProvider, getDefaultConfig, } from "@rainbow-me/rainbowkit"; import "@rainbow-me/rainbowkit/styles.css"; @@ -30,6 +28,7 @@ import App from "./App"; const queryClient = new QueryClient(); + const filecoinCalibrationChain = { id: 314159, name: 'Filecoin Calibration', @@ -39,7 +38,7 @@ const filecoinCalibrationChain = { symbol: 'tFIL', }, rpcUrls: { - default: { http: ['https://api.calibration.node.glif.io/rpc/v1'] }, + default: { http: ['/rpc/v1'] }, }, blockExplorers: { default: { @@ -82,16 +81,7 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - - + diff --git a/power-voting/src/lang/config.tsx b/power-voting/src/lang/config.tsx index 020e829..e3c80e4 100644 --- a/power-voting/src/lang/config.tsx +++ b/power-voting/src/lang/config.tsx @@ -18,6 +18,8 @@ import { initReactI18next } from 'react-i18next'; import zh from './zh.json'; // English language pack import en from './en.json'; + +const lang = localStorage.getItem("lang") || "en"; const resources = { en: { @@ -30,7 +32,7 @@ const resources = { i18n.use(initReactI18next).init({ resources, - lng: 'en', //Set default language + lng: lang, //Set default language interpolation: { escapeValue: false } diff --git a/power-voting/src/lang/en.json b/power-voting/src/lang/en.json index 0f97e52..4100a51 100644 --- a/power-voting/src/lang/en.json +++ b/power-voting/src/lang/en.json @@ -22,7 +22,7 @@ "alreadyRevoked": "You have already revoked!", "alreadyApproved": "You have already approved!", "noPropose": "Cannot propose to self", - "activePproposal": "Address has an active proposal ", + "activeProposal": "Address has an active proposal ", "twoFIPRevoke": "There must be more than two FIP editors to revoke", "selectAddress": "Please select an address!", "inputAddress": "Please input an address!", @@ -35,7 +35,7 @@ "voteSuccessful": "Vote successful!", "dataStoredFailed": "Data stored on chain failed!", "saveDataFail": "Save Data Fail", - "addressDditor": "Address is already a FIP editor", + "addressEditor": "Address is already a FIP editor", "storing": "Storing", "wrongNetwork": "Wrong Network", "passed": "Passed", @@ -93,23 +93,24 @@ "createProposals": "Create Proposal", "inputEditorAddress": "Input editor address", "inputProposeInfo": "Input propose info", - "inputMinerIDmultiplEseparate": "Input miner ID (For multiple miner IDs, use commas to separate them.)", + "inputMinerIDmultiplEseparate": "Input miner ID (For multiple miner IDs, use commas to separate them)", "proposeInfo": "Propose Info", "proposeType": "Propose Type", "editorAddress": "Editor Address", "question": "Question", "approved": "Approved", + "revoked": "Revoked", "role": "Role", "power": "Power", "totalPower": "Total Power", "percent": "Percent", "vote": "Vote", - "ucatType": "UCAN Type", + "ucanType": "UCAN Type", "issuer": "Issuer", "audience": "Audience", - "filecoinAddress": "Your Filecoin address.", + "filecoinAddress": "Input Your Filecoin address", "proof": "Proof", - "yourGithubAccount": "Your github account.", + "yourGithubAccount": "Input Your github account", "signature": "Signature", "ucanDelegatesAuthorize": "UCAN Delegates (Authorize)", "authorize": "Authorize", @@ -118,18 +119,27 @@ "deauthorize": "Deauthorize", "submit": "Submit", "totalPercent": "Total Percent", - "documentation":"Documentation", - "resources":"Resources", - "poweredBy":"Powered by", - "allRightReserved":"All Right Reserved © 2024", - "partners":"Partners", - "contactSupport":"Contact & Support", - "legal":"Legal", - "privacyTerms":"Privacy & Terms", - "codeConduct":"Code of Conduct", - "discord":"Discord", - "slack":"Slack", - "FAQs":"FAQs", - "reject":"Reject" + "documentation": "Documentation", + "resources": "Resources", + "poweredBy": "Powered by", + "allRightReserved": "All Right Reserved © 2024", + "partners": "Partners", + "contactSupport": "Contact & Support", + "legal": "Legal", + "privacyTerms": "Privacy & Terms", + "codeConduct": "Code of Conduct", + "discord": "Discord", + "slack": "Slack", + "FAQs": "FAQs", + "reject": "Reject", + "connectWallet": "Connect Wallet", + "fipEditorManagement": "FIP Editor Management", + "searchProposals": "Search Proposals", + "rejectedSignature": "Rejected signature!", + "filecion_authorize_doc": "\n# I. How to use UCAN signature\n\n1. First, please install Go Toolchain, you can find instructions here (https://go.dev/doc/install),Go version >=1.20. \n\n2. Get the code of UCAN signature tool.\n\n ```\ngit clone https://gitlab.com/storswiftlabs/wh/dapp/power-voting/ucan-utils\n``` \n\n3. Go into the utils directory and install the dependencies.\n\n```\ngo mod tidy\n```\n\n4. Build the binary file.\n\n```\ngo build -o signature\n```\n\n5. run.\n\n```\n./signature --aud 0x257c072306d848A6fd2f662Aead6855A7738dFEF --act add --privateKey --keyType secp256k1\n```\n\n6. Return a UCAN signature.\n\n```\neyJhbGciOiJzZWNwMjU2azEiLCJ0eXBlIjoiSldUIiwidmVyc2lvbiI6IjAuMC4xIn0.eyJpc3MiOiJ0MXkyNHY2Y3BiNzNwbnVkM2tlcHFoN3Zsb2h1YmNqYTR6emtrZ2MyeSIsImF1ZCI6IjB4MjU3YzA3MjMwNmQ4NDhBNmZkMmY2NjJBZWFkNjg1NUE3NzM4ZEZFRiIsImFjdCI6ImFkZCIsInByZiI6IiJ9.qYl0CQhK_EnqoKMf7Ph6x1gx1LW875y-nL__iH89s6MocYgfEZoETWAuPwwIU21LA4f-2LntzgcxdQv0Eks7bwA\n```\n\n\n# II. Authorization for F1、F2 Owner、F3 addresses\n\n## 1. Add authorization\n\n### 1.1 Create a UCAN signature authorized by Filecoin account to Eth account\n\n[Follow the process below to create a UCAN signature with act as add.](#i-how-to-use-ucan-signature)\n\nAttention: Field **act** should be set to **add**\n\nThe parameters need to be changed as follows:\n\n```\nvar (\n\\taud = \"0x257c072306d848A6fd2f662Aead6855A7738dFEF\" //Actual Eth address that requires authorization.\n\\tact = \"add\" //For \"act\", input \"add\"\n\\tprivateKeyStr = \"\" //Input private key against Filecoin address. \n\\tkeyTypeStr = \"secp256k1\" //The encryption algorithm of Filecoin addresses is as follows: addresses starting with f1 use secp256k1, addresses starting with f3 use bls\n)\n```\n\n### 1.2 Create a UCAN signature authorized by Eth account to Filecoin account\n\n1. Go to https://vote.storswift.io.\n\n2. Click UCAN Delegates to authorize.\n\n

\n \n

\n\n3. Select **Filecoin** for UCAN Type. \n\n4. Enter **Filecoin address** that requires authorization against field Aud. The Filecoin address is the one that its private key is entered in [1.1 Create a UCAN signature authorized by Filecoin account to Eth account](#11-create-a-ucan-signature-authorized-by-filecoin-account-to-eth-account)\n\n5. Enter **UCAN signature** created in 1.1 Create a UCAN signature authorized by Filecoin account to Eth account against filed Proof.\n\n

\n \n

\n\n### 1.3 Authorization\n\nAfter filling in the parameters, click **Authorize** to sign the message and send it on chain, then authorized successfully.\n", + "filecion_deauthorize_doc": "\n# I. How to use UCAN signature\n\n1. First, please install Go Toolchain, you can find instructions here (https://go.dev/doc/install),Go version >=1.20. \n\n2. Get the code of UCAN signature tool.\n\n\\ ```\ngit clone https://gitlab.com/storswiftlabs/wh/dapp/power-voting/ucan-utils\n```\n\n3. Go into the utils directory and install the dependencies.\n\n```\ngo mod tidy\n```\n\n4. Build the binary file.\n\n```\ngo build -o signature\n```\n\n5. run.\n\n```\n./signature --aud 0x257c072306d848A6fd2f662Aead6855A7738dFEF --act add --privateKey --keyType secp256k1\n```\n\n6. Return a UCAN signature.\n\n```\neyJhbGciOiJzZWNwMjU2azEiLCJ0eXBlIjoiSldUIiwidmVyc2lvbiI6IjAuMC4xIn0.eyJpc3MiOiJ0MXkyNHY2Y3BiNzNwbnVkM2tlcHFoN3Zsb2h1YmNqYTR6emtrZ2MyeSIsImF1ZCI6IjB4MjU3YzA3MjMwNmQ4NDhBNmZkMmY2NjJBZWFkNjg1NUE3NzM4ZEZFRiIsImFjdCI6ImFkZCIsInByZiI6IiJ9.qYl0CQhK_EnqoKMf7Ph6x1gx1LW875y-nL__iH89s6MocYgfEZoETWAuPwwIU21LA4f-2LntzgcxdQv0Eks7bwA\n```\n\n# II. Cancel authorization\n\n## 1. Create a UCAN signature deauthorized by Filecoin account to Eth account\n\nAttention:field act should be set to del.\n\n[Follow the process below to create a UCAN signature with act as del.](#i-how-to-use-ucan-signature)\n\nThe parameters need to be changed as follows:\n\n```\nvar (\n\\taud = \"0x257c072306d848A6fd2f662Aead6855A7738dFEF\" //Eth address that requires authorization\n\\tact = \"del\" // Input \"del\" for field \"act\"\n\\tprivateKeyStr = \"\" // Input the private key against Filecoin address\n\\tkeyTypeStr = \"secp256k1\" // The encryption algorithm of Filecoin addresses is as follows: addresses starting with f1 use secp256k1, addresses starting with f3 use bls\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\n)\n```\n\n## 2. Create a UCAN signature deauthorized by Eth account to Filecoin account\n\n**Prerequisite: Eth account has UCAN authorization already**。\n\n2.1 Go to https://vote.storswift.io.\n\n2.2 Click UCAN Delegates to cancel authorization. The website will monitor whether the Eth account has UCAN authorization or not. The action will cancel the authorization if it does.\n\n

\n \n

\n\n## 3. Iss & Aud are auto filled, you only need to enter UCAN created in 1.1 Create a UCAN signature deauthorized by Filecoin account to Eth account against field Proof.\n\n

\n \n

\n", + "github_authorize_doc": "\n# I. Authorization for developers\n\n## 1. Add Authorization\n\n### 1.1 Create a UCAN signature authorized by Eth account to Github handle\n\n#### 1. Go to https://vote.storswift.io.\n\n#### 2. Click UCAN Delegates to authorize.\n\n

\n \n

\n\n#### 3. Select **Github** for UCAN type\n\n#### 4. Enter **Github handle** that requires authorization in field Aud.\n\n

\n \n

\n\n#### 5. Click Sign to generate. Signature is the UCAN authorized by Eth to Github. In subsequent operations, the Signature needs to be sent to the Github repo.\n\n

\n \n

\n\n### 1.2 Create an initialized public repository on Github\n\n#### Select **Public** and **Add a README file**. The repository name can be customized. There are no special requirements for that. Here UCAN is used for repo name as demonstration.\n\n

\n \n

\n\n### 1.3 Create a Token used to upload UCAN signature to the repository\n\n#### 1. Select **Developer settings** in [Github Settings ](https://github.com/settings/profile)\n\n\n

\n \n

\n\n#### 2. Follow 4 steps below to create Token.\n\n

\n \n

\n\n#### 3. Select **write:packages**, the token name can be customized and there are no special requirements. The demonstration here uses **ucan** as the token name.\n\n

\n \n

\n\n#### 4. Remember to save the Token and you will not be able to view the Token after leaving the page.\n\n

\n \n

\n\n### 1.4 Upload the UCAN signature to Github repository (authorized by ETH address to Github handle) \n\n#### 1. Command\n\n```\n curl -L \\\\\n -X POST \\\\\n -H \"Accept: application/vnd.Github+json\" \\\\\n -H \"Authorization: Bearer \" \\\\\n -H \"X-Github-Api-Version: 2022-11-28\" \\\\\n https://api.Github.com/repos///git/blobs \\\\\n -d '{\"content\":\"\",\"encoding\":\"utf-8\"}'\n \n```\n\n#### 2. Example:\n\nThe OWNER here 1.1 Create a UCAN signature authorized by Eth account to Github account is the Github handle entered in field Aud.\n\nThe UCAN signature here is the one generated from 1.1 Create a UCAN signature authorized by Eth account to Github account.\n\nThe REPO here is repo name created from 1.2 Create an initialized public repository on Github.\n\nThe TOKEN here is one generated from 1.3 Create a Token used to upload the UCAN signature to the repository.\n\n```\n curl -L \\\\\n -X POST \\\\\n -H \"Accept: application/vnd.Github+json\" \\\\\n -H \"Authorization: Bearer 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\" \\\\\n -H \"X-Github-Api-Version: 2022-11-28\" \\\\\n https://api.Github.com/repos/Hzexiang/UCAN/git/blobs \\\\\n -d '{\"content\":\"eyJhbGciOiJlY2RzYSIsInR5cGUiOiJKV1QiLCJ2ZXJzaW9uIjoiMC4wLjEifQ.eyJpc3MiOiIweDI1N2MwNzIzMDZkODQ4QTZmZDJmNjYyQWVhZDY4NTVBNzczOGRGRUYiLCJhdWQiOiJ0ZXN0IiwicHJmIjoiIiwiYWN0IjoiYWRkIn0.MHhmZWE5YTE5NjdjYzQ1ZDJjMmIxNTcyZDAyMzI0OGM1YWY1N2ZiNTE3ZDMxMGY3MmRhNWNiZTEyY2MxY2VjY2FjMGE1NzMwMmRmODk0ZjU1NTE2MWU4MDk3Nzc4YmFkN2M5ZDg4NzFjNmY5ODI1NmRhM2FjY2IxMGRlMzczNWY4NDFj\",\"encoding\":\"utf-8\"}'\n```\n\n#### 3. Request returns the results.\n\n```\n{\n \"sha\": \"30662d9adc5588d55739c30299ca180e85126f54\",\n \"url\": \"https://api.Github.com/repos///git/blobs/\"\n}\n```\n\n### 1.5 Enter the returned URL on website and proceed to the next step, then wait for the node to get the data\n\n#### Enter the **returned URL** as required and then click **Authorize**.\n\n

\n \n

\n", + "github_deauthorize_doc": "\n# I. Deauthorization for developers\n\n## 1. Create a UCAN signature deauthorized by Eth account to Github handle\n\n### 1.1 Go to https://vote.storswift.io.\n\n### 1.2 Click UCAN Delegates to authorize.\n\n

\n \n

\n\n### 1.3 After authorized successfully for Developers, the authorized Github handle will be displayed when entering authorization page. No need to enter parameters, click on **Sign** and you will get UCAN signature for cancelling authorization. \n\n

\n \n

\n\n

\n \n

\n\n## 2. Upload the UCAN signature to Github repository (deauthorized by ETH address to Github handle) \n\n### 2.1 Create a new public repository on Github, refer to 1.2 Create an initialized public repository on Github if necessary.\n\n### 2.2 Create a Token used to upload the UCAN signature to repository, refer to 1.3 Create a Token used to upload the UCAN signature to the repository if necessary\n\n### 2.3 Upload the UCAN signature to Github repository (deauthorized by ETH account to Github) , UCAN signature is generated from 2.1 Create a UCAN signature deauthorized by Eth account to Github account 1.4 Upload the UCAN signature to Github repository (authorized by ETH address to Github handle).\n\n\n\n## 3. Enter the returned URL on website and proceed to the next step, then wait for the node to get the data\n\n### Enter the returned URL like below and click **Deauthorize**\n\n

\n \n

\n\n## 4. Delete the Github repository that saves UCAN signature\n\n### 4.1 After deauthorization, the Eth account can still obtain the authorized UCAN signature in the repo through the URL and authorize it again. To avoid the case mentioned before, there is need to delete the repository that saves authorized&deauthorized UCAN signature. \n\n### 4.2 Find the **settings** for repository that saves UCAN signature.\n\n

\n \n

\n\n### 4.3 Select **Delete this repository** at the bottom of the page.\n\n

\n \n

\n", + "proofDes": "The full UCAN content (include header, payload and signature) signed by your Filecoin private key." } } \ No newline at end of file diff --git a/power-voting/src/lang/zh.json b/power-voting/src/lang/zh.json index 1235496..c4f48d9 100644 --- a/power-voting/src/lang/zh.json +++ b/power-voting/src/lang/zh.json @@ -14,15 +14,15 @@ "voteCounting": "计票", "complete": "完成", "operationCanceled": "操作取消", - "storingChain": "数据上链中", - "storedSuccessfully": "数据上链成功", + "storingChain": "数据上链中!", + "storedSuccessfully": "数据上链成功!", "saveFail": "保存失败", "saveSuccess": "保存成功", "revokeYourself": "您不能自行撤销!", "alreadyRevoked": "您已撤销!", "alreadyApproved": "您已批准!", "noPropose": "不能向自己提案", - "activePproposal": "此地址已有一个活跃提案 ", + "activeProposal": "此地址已有一个活跃提案 ", "twoFIPRevoke": "必须有两名以上的FIP编辑者方可撤销", "selectAddress": "请选择一个地址!", "inputAddress": "请输入地址", @@ -35,7 +35,7 @@ "voteSuccessful": "投票成功", "dataStoredFailed": "数据上链失败!", "saveDataFail": "保存数据失败", - "addressDditor": "此地址已经是一个 FIP 编辑者", + "addressEditor": "此地址已经是一个 FIP 编辑者", "storing": "存储中", "wrongNetwork": "网络错误", "passed": "通过", @@ -99,17 +99,18 @@ "editorAddress": "编辑者地址", "question": "问题", "approved": "已批准", + "revoked": "已撤销", "role": "角色", "power": "算力", "totalPower": "总算力", "percent": "百分比", "vote": "投票", - "ucatType": "UCAN类型", + "ucanType": "UCAN类型", "issuer": "发布人", "audience": "受众", - "filecoinAddress": "您的Filecoin地址.", + "filecoinAddress": "请输入您的Filecoin地址", "proof": "证明", - "yourGithubAccount": "您的Github账号.", + "yourGithubAccount": "请输入您的Github账号", "signature": "签名", "ucanDelegatesAuthorize": "UCAN委托 (授权)", "authorize": "授权", @@ -129,6 +130,16 @@ "codeConduct": "行为准则", "discord": "Discord", "slack": "Slack", - "FAQs":"问答" + "FAQs": "问答", + "reject": "驳回", + "connectWallet": "连接钱包", + "fipEditorManagement": "FIP编辑管理", + "searchProposals": "搜索提案", + "rejectedSignature": "签名被拒绝!", + "filecion_authorize_doc": "\n# I. 如何使用UCAN签名\n\n1. 首先您需要安装 Go 工具链,您可以在此处(https://go.dev/doc/install)找到说明,Go版本>=1.20。 \n\n2. 获取UCAN签名工具的代码。\n\n```\ngit clone https://gitlab.com/storswiftlabs/wh/dapp/power-voting/ucan-utils\n```\n\n3. 进入utils目录并安装依赖项。\n\n```\ngo mod tidy\n```\n\n4. 构建二进制文件。\n\n```\ngo build -o signature\n```\n\n5. 运行。\n\n```\n./signature --aud 0x257c072306d848A6fd2f662Aead6855A7738dFEF --act add --privateKey --keyType secp256k1\n```\n\n6. 返回一个UCAN签名。\n\n```\neyJhbGciOiJzZWNwMjU2azEiLCJ0eXBlIjoiSldUIiwidmVyc2lvbiI6IjAuMC4xIn0.eyJpc3MiOiJ0MXkyNHY2Y3BiNzNwbnVkM2tlcHFoN3Zsb2h1YmNqYTR6emtrZ2MyeSIsImF1ZCI6IjB4MjU3YzA3MjMwNmQ4NDhBNmZkMmY2NjJBZWFkNjg1NUE3NzM4ZEZFRiIsImFjdCI6ImFkZCIsInByZiI6IiJ9.qYl0CQhK_EnqoKMf7Ph6x1gx1LW875y-nL__iH89s6MocYgfEZoETWAuPwwIU21LA4f-2LntzgcxdQv0Eks7bwA\n```\n\n\n# II. F1、F2 Owner、F3地址的授权\n\n## 1. 添加授权\n\n### 1.1 创建Filecoin账户对Eth账户授权的UCAN签名\n\n[按照这个流程创建一个act为add的UCAN签名。](#i-如何使用ucan签名)\n\nAttention: 注意:act字段需要设置为add\n\n参数需要更改为如下:\n\n```\nvar (\n\\taud = \"0x257c072306d848A6fd2f662Aead6855A7738dFEF\" //需要授权的Eth地址。\n\\tact = \"add\" // 动作,这里需要填写为add。\n\\tprivateKeyStr = \"\" // 填写Filecoin账户对应的私钥。 \n\\tkeyTypeStr = \"secp256k1\" // Filecoin地址的加密算法如:f1开头的地址使用secp256k1,f3开头的地址使用bls\n)\n```\n\n### 1.2 创建Eth账户对Filecoin账户授权的UCAN签名\n\n1.打开网站:https://vote.storswift.io。\n\n2. 点击UCAN Delegates 进行授权。\n\n

\n \n

\n\n3. UCAN Type 选择Filecoin。 \n\n4. Aud填写需要授权的Filecoin账户地址,此Filecoin账户地址为[1.1、创建Filecoin账户对Eth账户授权的UCAN签名](#11创建Filecoin账户对Eth账户授权的UCAN签名)中填入Filecoin私钥对应的账户地址。\n\n5. Proof 填写在[1.1、创建Filecoin账户对Eth账户授权的UCAN签名](#11创建Filecoin账户对Eth账户授权的UCAN签名)中创建的Filecoin账户签名的UCAN。\n\n

\n \n

\n\n### 1.3 授权\n\n参数填写完毕,点击Authorize按钮,对消息进行签名并发送到链上,授权成功。\n", + "filecion_deauthorize_doc": " \n# I. 如何使用UCAN签名\n\n1. 首先您需要安装 Go 工具链,您可以在此处(https://go.dev/doc/install)找到说明,Go版本>=1.20。 \n\n2. 获取UCAN签名工具的代码.\n\n```\ngit clone https://gitlab.com/storswiftlabs/wh/dapp/power-voting/ucan-utils\n```\n\n3. 进入utils目录并安装依赖项。\n\n```\ngo mod tidy\n```\n\n4. 构建二进制文件。\n\n```\ngo build -o signature\n```\n\n5. 运行。\n\n```\n./signature --aud 0x257c072306d848A6fd2f662Aead6855A7738dFEF --act add --privateKey --keyType secp256k1\n```\n\n6. 返回一个UCAN签名。\n\n```\neyJhbGciOiJzZWNwMjU2azEiLCJ0eXBlIjoiSldUIiwidmVyc2lvbiI6IjAuMC4xIn0.eyJpc3MiOiJ0MXkyNHY2Y3BiNzNwbnVkM2tlcHFoN3Zsb2h1YmNqYTR6emtrZ2MyeSIsImF1ZCI6IjB4MjU3YzA3MjMwNmQ4NDhBNmZkMmY2NjJBZWFkNjg1NUE3NzM4ZEZFRiIsImFjdCI6ImFkZCIsInByZiI6IiJ9.qYl0CQhK_EnqoKMf7Ph6x1gx1LW875y-nL__iH89s6MocYgfEZoETWAuPwwIU21LA4f-2LntzgcxdQv0Eks7bwA\n```\n\n# II. 取消授权\n\n## 1. 创建Filecoin账户对Eth账户取消授权的UCAN签名\n\n注意:act字段需要设置为del.\n\n[按照这个流程创建一个act为del的UCAN签名。](#i-如何使用ucan签名)\n\n参数需要更改为如下:\n\n```\nvar (\n\\taud = \"0x257c072306d848A6fd2f662Aead6855A7738dFEF\" //需要授权的Eth地址\n\\tact = \"del\" // 动作,这里需要填写为del\n\\tprivateKeyStr = \"\" // 填写Filecoin账户对应的私钥\n\\tkeyTypeStr = \"secp256k1\" // Filecoin地址的加密算法如:f1开头的地址使用secp256k1,f3开头的地址使用bls\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\n)\n```\n\n## 2.创建Eth账户对Filecoin账户取消授权的UCAN签名\n\n**前提条件:Eth账户已有UCAN授权**。\n\n2.1 打开网站:https://vote.storswift.io。\n\n2.2 点击UCAN Delegates 取消授权. 网站会监听Eth账户是否存在UCAN授权. 如果存在这次操作就为取消授权。\n\n

\n \n

\n\n## 3. Iss、Aud均是网站自动填写,您只需要在Proof处填写在[1.1、创建filecoin账户对Eth账户取消授权的UCAN签名](#11创建filecoin账户对Eth账户取消授权的UCAN签名)中创建的Filecoin账户的签名的UCAN。\n\n

\n \n

\n", + "github_authorize_doc": "\n# I. Developer 授权\n\n## 1. 添加授权\n\n### 1.1创建Eth账户对Github账户授权的UCAN签名\n\n#### 1. 打开网站:https://vote.storswift.io。\n\n#### 2. 点击UCAN Delegates 进行授权。\n\n

\n \n

\n\n#### 3. UCAN Type 选择Github。\n\n#### 4. Aud填写需要授权的Github用户名。\n\n

\n \n

\n\n#### 5. 点击Sign生成,Signature就是Eth对Github授权的UCAN签名,在后续的操作中需要将Signature发送到Github仓库。\n\n

\n \n

\n\n### 1.2 在Github上创建一个初始化的公开仓库\n\n#### 选中Public和 Add a README file,仓库名称可以自定义命名,没有特殊需求,此处演示使用ucan作为仓库名称。\n\n

\n \n

\n\n### 1.3 创建一个用于上传UCAN签名到仓库的Token\n\n#### 1. 在 [Github Settings](https://github.com/settings/profile) 中,选择Developer settings。\n\n\n

\n \n

\n\n#### 2. 按照下图中的4步开始创建Token。\n\n

\n \n

\n\n#### 3. 选中write:packages,Token名称可以自定义命名,没有特殊需求,此处演示使用ucan作为Token名称。\n\n

\n \n

\n\n#### 4. 保存好Token,离开页面以后就无法查看Token。\n\n

\n \n

\n\n### 1.4 上传Eth对Github授权的UCAN签名到Github仓库\n\n#### 1. 命令\n\n```\n curl -L \\\\\n -X POST \\\\\n -H \"Accept: application/vnd.Github+json\" \\\\\n -H \"Authorization: Bearer \" \\\\\n -H \"X-Github-Api-Version: 2022-11-28\" \\\\\n https://api.Github.com/repos///git/blobs \\\\\n -d '{\"content\":\"\",\"encoding\":\"utf-8\"}'\n \n```\n\n#### 2. 示例:\n\n此处的OWNER则是[1.1、创建Eth账户对Github账户授权的UCAN签名])填入的Aud对应的Github用户名。\n\n此处的UCAN签名则是[1.1、创建Eth账户对Github账户授权的UCAN签名]生成的UCAN签名。\n\n此处的REPO则是[1.2、在Github上创建一个初始化的公开仓库]创建的Github仓库名称。\n\n此处的TOKEN则是[1.3、创建一个用于上传UCAN签名到仓库的Token]生成的Token。\n\n```\n curl -L \\\\\n -X POST \\\\\n -H \"Accept: application/vnd.Github+json\" \\\\\n -H \"Authorization: Bearer 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\" \\\\\n -H \"X-Github-Api-Version: 2022-11-28\" \\\\\n https://api.Github.com/repos/Hzexiang/UCAN/git/blobs \\\\\n -d '{\"content\":\"eyJhbGciOiJlY2RzYSIsInR5cGUiOiJKV1QiLCJ2ZXJzaW9uIjoiMC4wLjEifQ.eyJpc3MiOiIweDI1N2MwNzIzMDZkODQ4QTZmZDJmNjYyQWVhZDY4NTVBNzczOGRGRUYiLCJhdWQiOiJ0ZXN0IiwicHJmIjoiIiwiYWN0IjoiYWRkIn0.MHhmZWE5YTE5NjdjYzQ1ZDJjMmIxNTcyZDAyMzI0OGM1YWY1N2ZiNTE3ZDMxMGY3MmRhNWNiZTEyY2MxY2VjY2FjMGE1NzMwMmRmODk0ZjU1NTE2MWU4MDk3Nzc4YmFkN2M5ZDg4NzFjNmY5ODI1NmRhM2FjY2IxMGRlMzczNWY4NDFj\",\"encoding\":\"utf-8\"}'\n```\n\n#### 3. 请求返回。\n\n```\n{\n \"sha\": \"30662d9adc5588d55739c30299ca180e85126f54\",\n \"url\": \"https://api.Github.com/repos///git/blobs/\"\n}\n```\n\n### 1.5 将返回的URL填入网站并执行下一步,然后等待节点获取数据\n\n#### 把返回的URL填入网站中,点击Authorize授权。\n\n

\n \n

\n", + "github_deauthorize_doc": "\n# I. 开发者取消授权\n\n## 1. 创建Eth账户对Github账户取消授权的UCAN签名\n\n### 1.1 打开网站:https://vote.storswift.io。\n\n### 1.2 点击UCAN Delegates 进行授权.\n\n

\n \n

\n\n### 1.3 拥有Developer授权以后进入授权页面会显示当前授权的Github 用户名,不用填写参数,直接点击Sign,得到取消授权的UCAN签名。 \n\n

\n \n

\n\n

\n \n

\n\n## 2. 上传Eth对Github取消授权的UCAN签名到Github仓库 \n\n### 2.1 在Github上创建一个新的的公开仓库,如果没有仓库,请参考1.2 [在 Github 上创建初始化的公共仓库] .\n\n### 2.2 创建一个用于上传UCAN签名到仓库的Token,如果没有Token参考1.3 [创建一个用于上传UCAN签名到仓库的Token] \n\n### 2.3 上传Eth对Github取消授权的UCAN签名到Github仓库 , UCAN签名为[2.1、创建Eth账户对Github账户取消授权的UCAN签名] 创建的签名 1.4 [上传Eth对Github授权的UCAN签名到Github仓库]。\n\n\n\n## 3. 将返回的URL填入网站并执行下一步,然后等待节点获取数据\n\n###将返回的URL填入网页中URL栏中,点击Deauthorize\n\n

\n \n

\n\n## 4. 删除存放UCAN签名对应的Github仓库\n\n### 4.1 避免取消授权以后,Eth账户还能通过URL获取到仓库中授权的UCAN签名,再次进行授权,所以取消授权以后需要,删除存放UCAN签名和取消授权UCAN签名对应的仓库。 \n\n### 4.2 进入存放UCAN签名的仓库, 选择Settings。\n\n

\n \n

\n\n### 4.3 在页面的最下面选择Delete this repository。\n\n

\n \n

\n", + "proofDes": "完整的UCAN 内容(其中包括标题,有效负载和签名)通过您的Filecoin私钥签名" } } \ No newline at end of file diff --git a/power-voting/src/pages/CreateVote/index.tsx b/power-voting/src/pages/CreateVote/index.tsx index 06eb869..07ee796 100644 --- a/power-voting/src/pages/CreateVote/index.tsx +++ b/power-voting/src/pages/CreateVote/index.tsx @@ -41,16 +41,17 @@ import { WRONG_START_TIME_MSG, githubApi, proposalDraftAddApi, - proposalDraftGetApi, - worldTimeApi -} from '../../common/consts'; + proposalDraftGetApi, mainnetChainId + // blockHeightGetApi +} from "../../common/consts" import { useCheckFipEditorAddress } from "../../common/hooks"; -import { useStoringCid, useVoterInfo } from "../../common/store"; +import { useStoringCid, useVoterInfo, useVotingList } from "../../common/store"; import Table from '../../components/CreateTable'; import LoadingButton from "../../components/LoadingButton"; import Editor from '../../components/MDEditor'; import { getContractAddress, getWeb3IpfsId, validateValue } from '../../utils'; import './index.less'; +import { UserRejectedRequestError } from "viem"; dayjs.extend(utc); dayjs.extend(timezone); @@ -58,7 +59,7 @@ const { RangePicker } = DatePicker; const CreateVote = () => { const { isConnected, address, chain } = useAccount(); - const chainId = chain?.id || 0; + const chainId = chain?.id || mainnetChainId; const { t } = useTranslation(); const { openConnectModal } = useConnectModal(); const prevAddressRef = useRef(address); @@ -66,6 +67,7 @@ const CreateVote = () => { const voterInfo = useVoterInfo((state: any) => state.voterInfo); const addStoringCid = useStoringCid((state: any) => state.addStoringCid); + const setVotingList = useVotingList((state: any) => state.setVotingList); const { register, @@ -93,8 +95,7 @@ const CreateVote = () => { const { data: hash, - writeContract, - error, + writeContractAsync, isPending: writeContractPending, isSuccess: writeContractSuccess, reset @@ -102,8 +103,9 @@ const CreateVote = () => { const [cid, setCid] = useState(''); const [loading, setLoading] = useState(writeContractPending); - const [isDraftSave, setDraftSave] = useState(false) - const [hasDraft, setHasDraft] = useState(false) + const [isDraftSave, setDraftSave] = useState(false); + const [hasDraft, setHasDraft] = useState(false); + useEffect(() => { if (!isConnected) { navigate("/home"); @@ -118,26 +120,16 @@ const CreateVote = () => { } }, [address]); - useEffect(() => { - if (error) { - messageApi.open({ - type: 'error', - content: (error as BaseError)?.shortMessage || error?.message, - }); - } - reset(); - }, [error]); - - const OPTION_SPLIT_TAG = "&%" + const OPTION_SPLIT_TAG = "&%"; const loadDraft = async () => { try { const resp = await axios.get(proposalDraftGetApi, { params: { chainId: chainId, address: address - } - }) + }); + if (resp.data != null && resp.data.data?.length) { const result = (resp.data.data as ProposalDraft[])[0] setValue("descriptions", result.descriptions) @@ -153,11 +145,11 @@ const CreateVote = () => { } catch (e) { console.log(e) } - } + useEffect(() => { - loadDraft() - }, []) + loadDraft(); + }, []); useEffect(() => { if (writeContractSuccess) { @@ -181,12 +173,13 @@ const CreateVote = () => { */ const onSubmit = async (values: any) => { setLoading(true); + // Calculate offset based on selected timezone const offset = dayjs().utcOffset() - dayjs().tz(values.timezone).utcOffset(); const startTimestamp = dayjs(values.time[0]).add(offset, 'minute').unix(); const expTimestamp = dayjs(values.time[1]).add(offset, 'minute').unix(); - const { data } = await axios.get(worldTimeApi); - const currentTime = data?.unixtime; + const currentTime = Math.floor(Date.now() / 1000); + // Check if current time is after start time if (currentTime > startTimestamp) { messageApi.open({ @@ -222,6 +215,9 @@ const CreateVote = () => { githubObj.githubName = githubName; githubObj.githubAvatar = data.avatar_url; } + + // const { data: { data: blockHeight } } = await axios.get(`${blockHeightGetApi}`, { params: { netId: chainId } }); + // Prepare values object with additional information const _values = { ...values, @@ -233,6 +229,7 @@ const CreateVote = () => { option: VOTE_OPTIONS, address: address, chainId: chainId, + // day: blockHeight?.day, currentTime, }; const cid = await getWeb3IpfsId(_values); @@ -250,17 +247,60 @@ const CreateVote = () => { // Check if user is a FIP editor if (isFipEditorAddress) { // Create voting using dynamic contract API - writeContract({ - abi: fileCoinAbi, - address: getContractAddress(chain?.id || 0, 'powerVoting'), - functionName: 'createProposal', - args: [ + try { + await writeContractAsync({ + abi: fileCoinAbi, + address: getContractAddress(chain?.id || mainnetChainId, 'powerVoting'), + functionName: 'createProposal', + args: [ + cid, + startTimestamp, + expTimestamp, + 1 + ], + }); + const params = { + ...githubObj, + GMTOffset, + startTime: startTimestamp, + expTime: expTimestamp, + address: address, + chainId: chainId, + currentTime, + timezone: values.timezone, + name: values.name, + descriptions: values.descriptions, cid, - startTimestamp, - expTimestamp, - 1 - ], - }); + proposalId: 0 + } + await axios.post('/api/proposal/add', params); + //clear draft + if (hasDraft) { + clearDraft() + } + const listParams = { + chainId, + page: 1, + pageSize: 5, + status: 0, + } + const { data: { data: votingData } } = await axios.get('/api/proposal/list', { params: listParams }); + setVotingList({ votingList: votingData?.list || [], totalPage: votingData?.total, searchKey: '' }); + } catch (error: any) { + if (error as UserRejectedRequestError) { + messageApi.open({ + type: 'warning', + content: t('content.dataStoredFailed'), + }); + } else { + messageApi.open({ + type: 'error', + content: (error as BaseError)?.shortMessage || error?.message, + }); + } + reset(); + } + } else { messageApi.open({ type: 'warning', @@ -270,25 +310,6 @@ const CreateVote = () => { } else { openConnectModal && openConnectModal(); } - const params = { - ...githubObj, - GMTOffset, - startTime: startTimestamp, - expTime: expTimestamp, - address: address, - chainId: chainId, - currentTime, - timezone: values.timezone, - name: values.name, - descriptions: values.descriptions, - cid, - proposalId: '' - } - await axios.post('/api/proposal/add', params); - //clear draft - if (hasDraft) { - clearDraft() - } setLoading(false); } const clearDraft = async () => { @@ -298,14 +319,14 @@ const CreateVote = () => { name: '', descriptions: '', // GMTOffset, - startTime: '', - expTime: '', + startTime: 0, + expTime: 0, address: address, chainId: chainId, - currentTime:'' + currentTime: 0 } await axios.post(proposalDraftAddApi, data) - setHasDraft(false) + setHasDraft(false); } catch (e) { console.log(e) } @@ -344,16 +365,11 @@ const CreateVote = () => { githubObj.githubName = githubName; githubObj.githubAvatar = data.avatar_url; } - const { data: timeData } = await axios.get(worldTimeApi); - const currentTime = timeData?.unixtime; + const currentTime = Math.floor(Date.now() / 1000); const offset = dayjs().utcOffset() - dayjs().tz(values.timezone).utcOffset(); const startTimestamp = dayjs(values.time[0]).add(offset, 'minute').unix(); const expTimestamp = dayjs(values.time[1]).add(offset, 'minute').unix(); - // Get text for timezone array - // const text = timezoneOption?.find((item: any) => item.value === values.value)?.text || ''; - // Extract GMT offset from text using regex - // const regex = /(?<=\().*?(?=\))/g; - // const GMTOffset = text.match(regex); + const data = { timezone: values.timezone, name: values.name, diff --git a/power-voting/src/pages/Fip/approve/index.tsx b/power-voting/src/pages/Fip/approve/index.tsx index ba80d4f..8c86f03 100644 --- a/power-voting/src/pages/Fip/approve/index.tsx +++ b/power-voting/src/pages/Fip/approve/index.tsx @@ -22,10 +22,10 @@ import type { BaseError } from "wagmi"; import { useAccount, useWaitForTransactionReceipt, useWriteContract } from "wagmi"; import fileCoinAbi from "../../../common/abi/power-voting.json"; import { - HAVE_APPROVED_MSG, + HAVE_APPROVED_MSG, mainnetChainId, STORING_DATA_MSG, - web3AvatarUrl, -} from "../../../common/consts"; + web3AvatarUrl +} from "../../../common/consts" import { useApproveProposalId, useCheckFipEditorAddress, useFipEditorProposalDataSet, useFipEditors } from "../../../common/hooks"; import EllipsisMiddle from "../../../components/EllipsisMiddle"; import Loading from "../../../components/Loading"; @@ -34,7 +34,7 @@ import "./index.less"; const FipEditorApprove = () => { const { isConnected, address, chain } = useAccount(); const { t } = useTranslation(); - const chainId = chain?.id || 0; + const chainId = chain?.id || mainnetChainId; const navigate = useNavigate(); const prevAddressRef = useRef(address); const [messageApi, contextHolder] = message.useMessage(); @@ -98,7 +98,7 @@ const FipEditorApprove = () => { title: t('content.status'), dataIndex: 'status', key: 'status', - render: (value: string) => value || '-' + render: (value: string) => value ? t(`content.approved`) : '-' }, ] @@ -165,7 +165,7 @@ const FipEditorApprove = () => { align: 'center' as const, width: 120, render: (_: any, record: any) => { - const disabled = !!record.voteList.find((item: any) => item.address === address && item.status === 'Approved'); + const disabled = !!record.voteList.find((item: any) => item.address === address && item.status === "approved"); return ( { }, [error]); useEffect(() => { + console.log(writeContractError) if (writeContractError) { messageApi.open({ type: 'error', @@ -260,7 +261,7 @@ const FipEditorApprove = () => { voters: obj.voters, ratio: `${obj.voters?.length} / ${fipEditors?.length}`, voteList: fipEditors?.map((address: string) => { - return { address, status: obj.voters?.includes(address) ? 'Approved' : '' } + return { address, status: obj.voters?.includes(address) ? "approved" : '' } }).sort((a) => (a.status ? -1 : 1)) }); } catch (e) { diff --git a/power-voting/src/pages/Fip/propose/index.tsx b/power-voting/src/pages/Fip/propose/index.tsx index 6c98d1d..f48b2d8 100644 --- a/power-voting/src/pages/Fip/propose/index.tsx +++ b/power-voting/src/pages/Fip/propose/index.tsx @@ -22,26 +22,25 @@ import type { BaseError } from "wagmi"; import { useAccount, useWaitForTransactionReceipt, useWriteContract } from "wagmi"; import fileCoinAbi from "../../../common/abi/power-voting.json"; import { - // FIP_ALREADY_EXECUTE_MSG, - // FIP_APPROVE_ALREADY_MSG, - // FIP_APPROVE_SELF_MSG, + FIP_ALREADY_EXECUTE_MSG, + FIP_APPROVE_ALREADY_MSG, + FIP_APPROVE_SELF_MSG, FIP_EDITOR_APPROVE_TYPE, - FIP_EDITOR_REVOKE_TYPE, - // NO_ENOUGH_FIP_EDITOR_REVOKE_ADDRESS_MSG, - // NO_FIP_EDITOR_APPROVE_ADDRESS_MSG, - // NO_FIP_EDITOR_REVOKE_ADDRESS_MSG, + FIP_EDITOR_REVOKE_TYPE, mainnetChainId, + NO_ENOUGH_FIP_EDITOR_REVOKE_ADDRESS_MSG, + NO_FIP_EDITOR_APPROVE_ADDRESS_MSG, + NO_FIP_EDITOR_REVOKE_ADDRESS_MSG, STORING_DATA_MSG, - UPLOAD_DATA_FAIL_MSG, - hexToString, -} from "../../../common/consts"; -import { useCheckFipEditorAddress, useFipEditors } from "../../../common/hooks"; + UPLOAD_DATA_FAIL_MSG +} from "../../../common/consts" +import { useCheckFipEditorAddress, useFipEditors, useRevokeProposalId, useApproveProposalId, useFipEditorProposalDataSet } from "../../../common/hooks"; import LoadingButton from '../../../components/LoadingButton'; import Table from '../../../components/Table'; -import { getContractAddress, getWeb3IpfsId } from "../../../utils"; +import { getContractAddress, getWeb3IpfsId, hexToString } from "../../../utils"; const FipEditorPropose = () => { const { isConnected, address, chain } = useAccount(); const { t } = useTranslation(); - const chainId = chain?.id || 0; + const chainId = chain?.id || mainnetChainId; const navigate = useNavigate(); const prevAddressRef = useRef(address); @@ -57,23 +56,23 @@ const FipEditorPropose = () => { const { fipEditors } = useFipEditors(chainId); - //load revoke proposa - // const { revokeProposalId } = useRevokeProposalId(chainId); - // const revokeResult = useFipEditorProposalDataSet({ - // chainId, - // idList: revokeProposalId, - // page: 1, - // pageSize: revokeProposalId?.length ?? 0, - // }); - - //load approve - // const { approveProposalId } = useApproveProposalId(chainId); - // const approveResult = useFipEditorProposalDataSet({ - // chainId, - // idList: approveProposalId, - // page: 1, - // pageSize: approveProposalId?.length ?? 0, - // }); + //load revoke proposal + const { revokeProposalId } = useRevokeProposalId(chainId); + const revokeResult = useFipEditorProposalDataSet({ + chainId, + idList: revokeProposalId, + page: 1, + pageSize: revokeProposalId?.length ?? 0, + }); + + //load approve proposal + const { approveProposalId } = useApproveProposalId(chainId); + const approveResult = useFipEditorProposalDataSet({ + chainId, + idList: approveProposalId, + page: 1, + pageSize: approveProposalId?.length ?? 0, + }); const { data: hash, @@ -142,72 +141,72 @@ const FipEditorPropose = () => { */ const onSubmit = async () => { // Check if required fields are filled based on proposal type - // if (fipProposalType === FIP_EDITOR_APPROVE_TYPE && !fipAddress) { - // messageApi.open({ - // type: 'warning', - // // Prompt user to fill required fields - // content: t(NO_FIP_EDITOR_APPROVE_ADDRESS_MSG), - // }); - // return; - // } - - // if (fipProposalType === FIP_EDITOR_REVOKE_TYPE && !selectedAddress) { - // messageApi.open({ - // type: 'warning', - // // Prompt user to fill required fields - // content: t(NO_FIP_EDITOR_REVOKE_ADDRESS_MSG), - // }); - // return; - // } - - // if (fipProposalType === FIP_EDITOR_REVOKE_TYPE && fipEditors.length <= 2) { - // messageApi.open({ - // type: 'warning', - // // must more than 2 - // content: t(NO_ENOUGH_FIP_EDITOR_REVOKE_ADDRESS_MSG), - // }); - // return; - // } - - - - // if (fipProposalType === FIP_EDITOR_REVOKE_TYPE && revokeResult.getFipEditorProposalIdSuccess) { - // const find = revokeResult.fipEditorProposalData?.find((v: any) => v.result[1] === selectedAddress) - // if (find) { - // messageApi.open({ - // type: 'warning', - // content: t(FIP_ALREADY_EXECUTE_MSG), - // }); - // return; - // } - // } - // if (fipProposalType === FIP_EDITOR_APPROVE_TYPE && approveResult.getFipEditorProposalIdSuccess) { - // const find = approveResult.fipEditorProposalData?.find((v: any) => v.result[1] === fipAddress) - // if (find) { - // messageApi.open({ - // type: 'warning', - // content: t(FIP_ALREADY_EXECUTE_MSG), - // }); - // return; - // } - // } - - // if (fipProposalType === FIP_EDITOR_APPROVE_TYPE && fipAddress === address) { - // messageApi.open({ - // type: 'warning', - // content: t(FIP_APPROVE_SELF_MSG), - // }); - // return; - // } - - // //fipEditors - // if (fipProposalType === FIP_EDITOR_APPROVE_TYPE && fipEditors.includes(fipAddress)) { - // messageApi.open({ - // type: 'warning', - // content: t(FIP_APPROVE_ALREADY_MSG), - // }); - // return; - // } + if (fipProposalType === FIP_EDITOR_APPROVE_TYPE && !fipAddress) { + messageApi.open({ + type: 'warning', + // Prompt user to fill required fields + content: t(NO_FIP_EDITOR_APPROVE_ADDRESS_MSG), + }); + return; + } + + if (fipProposalType === FIP_EDITOR_REVOKE_TYPE && !selectedAddress) { + messageApi.open({ + type: 'warning', + // Prompt user to fill required fields + content: t(NO_FIP_EDITOR_REVOKE_ADDRESS_MSG), + }); + return; + } + + if (fipProposalType === FIP_EDITOR_REVOKE_TYPE && fipEditors.length <= 2) { + messageApi.open({ + type: 'warning', + // must more than 2 + content: t(NO_ENOUGH_FIP_EDITOR_REVOKE_ADDRESS_MSG), + }); + return; + } + + + + if (fipProposalType === FIP_EDITOR_REVOKE_TYPE && revokeResult.getFipEditorProposalIdSuccess) { + const find = revokeResult.fipEditorProposalData?.find((v: any) => v.result[1] === selectedAddress) + if (find) { + messageApi.open({ + type: 'warning', + content: t(FIP_ALREADY_EXECUTE_MSG), + }); + return; + } + } + if (fipProposalType === FIP_EDITOR_APPROVE_TYPE && approveResult.getFipEditorProposalIdSuccess) { + const find = approveResult.fipEditorProposalData?.find((v: any) => v.result[1] === fipAddress) + if (find) { + messageApi.open({ + type: 'warning', + content: t(FIP_ALREADY_EXECUTE_MSG), + }); + return; + } + } + + if (fipProposalType === FIP_EDITOR_APPROVE_TYPE && fipAddress === address) { + messageApi.open({ + type: 'warning', + content: t(FIP_APPROVE_SELF_MSG), + }); + return; + } + + //fipEditors + if (fipProposalType === FIP_EDITOR_APPROVE_TYPE && fipEditors.includes(fipAddress)) { + messageApi.open({ + type: 'warning', + content: t(FIP_APPROVE_ALREADY_MSG), + }); + return; + } // Set loading state to true while submitting proposal @@ -216,7 +215,6 @@ const FipEditorPropose = () => { // Get the IPFS CID for the proposal information const cid = await getWeb3IpfsId(fipInfo); - if (!cid?.length) { setLoading(false); messageApi.open({ diff --git a/power-voting/src/pages/Fip/revoke/index.tsx b/power-voting/src/pages/Fip/revoke/index.tsx index a189a34..ba9dca5 100644 --- a/power-voting/src/pages/Fip/revoke/index.tsx +++ b/power-voting/src/pages/Fip/revoke/index.tsx @@ -23,10 +23,10 @@ import { useAccount, useWaitForTransactionReceipt, useWriteContract } from "wagm import fileCoinAbi from "../../../common/abi/power-voting.json"; import { CAN_NOT_REVOKE_YOURSELF_MSG, - HAVE_REVOKED_MSG, + HAVE_REVOKED_MSG, mainnetChainId, STORING_DATA_MSG, web3AvatarUrl -} from "../../../common/consts"; +} from "../../../common/consts" import { useCheckFipEditorAddress, useFipEditorProposalDataSet, useFipEditors, useRevokeProposalId } from "../../../common/hooks"; import EllipsisMiddle from "../../../components/EllipsisMiddle"; import Loading from "../../../components/Loading"; @@ -34,7 +34,7 @@ import { getContractAddress } from "../../../utils"; import "./index.less"; const FipEditorRevoke = () => { const {isConnected, address, chain} = useAccount(); - const chainId = chain?.id || 0; + const chainId = chain?.id || mainnetChainId; const navigate = useNavigate(); const prevAddressRef = useRef(address); const { t } = useTranslation(); @@ -100,7 +100,7 @@ const FipEditorRevoke = () => { title: t('content.status'), dataIndex: 'status', key: 'status', - render: (value: string) => value || '-' + render: (value: string) => value ? t(`content.revoked`) : '-' }, ]; @@ -127,7 +127,7 @@ const FipEditorRevoke = () => { } }, { - title:
{t('content.info')}
, + title:
{t('content.info')}
, dataIndex: 'info', key: 'info', ellipsis: { showTitle: false }, @@ -177,7 +177,7 @@ const FipEditorRevoke = () => { okText={t('content.yes')} cancelText={t('content.no')} > - +
) @@ -261,7 +261,7 @@ const FipEditorRevoke = () => { }, [getFipEditorProposalIdSuccess]); useEffect(() => { - if (isConnected && !loading && !getRevokeProposalIdLoading && !getFipEditorProposalIdLoading) { + if (isConnected && !getRevokeProposalIdLoading && !getFipEditorProposalIdLoading) { initState(); } }, [chain, page, address]); @@ -303,7 +303,7 @@ const FipEditorRevoke = () => { return { address, status: obj.voters?.includes(address) ? 'Revoked' : '' } }).sort((a) => (a.status ? -1 : 1)) }); - }catch(e){ + } catch(e){ console.log(e) } @@ -334,6 +334,7 @@ const FipEditorRevoke = () => {
record.proposalId} dataSource={fipProposalList} diff --git a/power-voting/src/pages/Home/index.tsx b/power-voting/src/pages/Home/index.tsx index 64178db..dd3586c 100644 --- a/power-voting/src/pages/Home/index.tsx +++ b/power-voting/src/pages/Home/index.tsx @@ -22,10 +22,9 @@ import React, { useEffect, useState } from "react"; import { useTranslation } from 'react-i18next'; import { useNavigate } from "react-router-dom"; import VoteStatusBtn from "src/components/VoteStatusBtn"; -import type { BaseError } from "wagmi"; import { useAccount, useWaitForTransactionReceipt } from "wagmi"; import { - IN_PROGRESS_STATUS, + IN_PROGRESS_STATUS, mainnetChainId, PENDING_STATUS, STORING_DATA_MSG, STORING_FAILED_MSG, @@ -34,9 +33,9 @@ import { VOTE_ALL_STATUS, VOTE_FILTER_LIST, web3AvatarUrl -} from '../../common/consts'; -import { useCheckFipEditorAddress, useLatestId, useProposalDataSet } from "../../common/hooks"; -import { useCurrentTimezone, usePropsalStatus, useStoringCid, useVotingList } from "../../common/store"; +} from "../../common/consts"; +import { useCheckFipEditorAddress } from "../../common/hooks" +import { useCurrentTimezone, useProposalStatus, useStoringCid, useVotingList } from "../../common/store"; import type { ProposalResult, VotingList } from '../../common/types'; import EllipsisMiddle from "../../components/EllipsisMiddle"; import ListFilter from "../../components/ListFilter"; @@ -49,60 +48,36 @@ const Home = () => { const navigate = useNavigate(); const { t, i18n } = useTranslation(); const { chain, address, isConnected } = useAccount(); - const chainId = chain?.id || 0; + const chainId = chain?.id || mainnetChainId; const { openConnectModal } = useConnectModal(); - const [filterList, setFilterList] = useState([ - { - label: t('content.all'), - value: VOTE_ALL_STATUS + const items = VOTE_FILTER_LIST.map((item) => { + return { + label: t(item.label), + value: item.value } - ]) + }); const [proposalStatus, setProposalStatus] = useState(VOTE_ALL_STATUS); - // const [proposalList, setProposalList] = useState([]); const [page, setPage] = useState(1); - const [pageSize] = useState(5); - // const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); - const [shouldRefetch, setShouldRefetch] = useState(false); const [messageApi, contextHolder] = message.useMessage(); const timezone = useCurrentTimezone((state: any) => state.timezone); const storingCid = useStoringCid((state: any) => state.storingCid); const setStoringCid = useStoringCid((state: any) => state.setStoringCid); const { votingList, totalPage, searchKey } = useVotingList((state: any) => state.votingData); const setVotingList = useVotingList((state: any) => state.setVotingList); - const setStatusList = usePropsalStatus((state: any) => state.setStatusList); + const setStatusList = useProposalStatus((state: any) => state.setStatusList); const { isFipEditorAddress } = useCheckFipEditorAddress(chainId, address); - const { latestId, getLatestIdLoading, refetch } = useLatestId(chainId, !shouldRefetch); - const { getProposalIdLoading, getProposalIdSuccess, error } = useProposalDataSet({ - chainId, - total: Number(latestId), - page, - pageSize, - }); + const { isFetched, isSuccess, isError } = useWaitForTransactionReceipt({ hash: storingCid[0]?.hash }); - useEffect(() => { - if (error) { - messageApi.open({ - type: 'error', - content: (error as BaseError)?.shortMessage || error?.message, - }); - } - }, [error]); - - useEffect(() => { - if (getProposalIdSuccess) { - getProposalList(page); - } - }, [getProposalIdSuccess]); useEffect(() => { - if (isConnected && !loading && !getLatestIdLoading && !getProposalIdLoading) { - getProposalList(page); + if (isConnected && !loading) { + queryVotingList(page, proposalStatus); } }, [chain, page, address, isConnected, i18n.language]); @@ -116,7 +91,10 @@ const Home = () => { messageApi.open({ type: 'success', content: t(STORING_SUCCESS_MSG) - }) + }); + setTimeout(() => { + queryVotingList(page, proposalStatus); + }, 3000) } // If the transaction fails, show an error message if (isError) { @@ -125,179 +103,11 @@ const Home = () => { content: t(STORING_FAILED_MSG) }) } - // After 3 seconds, set shouldRefetch to true and trigger a refetch - setTimeout(() => { - setShouldRefetch(true); - refetch().then(() => { - // Reset shouldRefetch after refetching - getProposalList(page); - setShouldRefetch(false); - }).finally(() => { - // queryVotingList(page, proposalStatus); - }); - - }, 3000); } }, [isFetched]); - /** - * get proposal list - * @param page - */ - const getProposalList = async (page: number) => { - setLoading(true); - console.log('page', page) - // Convert latest ID to number - // const total = latestId ? Number(latestId) : 0; - // Calculate the offset based on the current page number - // const offset = (page - 1) * pageSize; - // setTotal(total); - try { - // Fetch and process proposal data - // const list = await Promise.all(proposalData.map(async (data, index) => { - // const { result } = data as any; - // const proposalId = total - offset - index; - // const params = { - // proposalId, - // network: chainId - // }; - - - // Fetch proposal results data from the API - // const { data: { data: resultData } } = await axios.get(proposalResultApi, { params }); - // Map proposal results data to a more structured format - // const proposalResults = resultData.map((item: ProposalResult) => ({ - // optionId: item.optionId, - // votes: item.votes - // })); - // Return formatted proposal object - // return { - // id: proposalId, - // cid: result[0], - // creator: result[2], - // createTime: 0, - // startTime: Number(result[3]), - // expTime: Number(result[4]), - // proposalType: Number(result[1]), - - // proposalResults - // }; - // })); - - // Filter out already stored proposals - // const storingList = storingCid.filter((item: any) => !list.some(option => option.cid === item.cid)); - // setStoringCid(storingList); - - // Generate IPFS URLs for storing list - // const ipfsUrls = storingList.map( - // (item: any) => `https://${item.cid}.ipfs.w3s.link/` - // ); - - // Fetch data from IPFS URLs - // const responses = await Promise.all(ipfsUrls.map((url: string) => axios.get(url))); - // Process the fetched data and create proposal objects - // const storingData = responses?.map((res, i) => { - // const { data } = res; - // return { - // ...data, - // cid: ipfsUrls[i], - // proposalType: 1, - // proposalStatus: STORING_STATUS, - // proposalResults: data.option?.map((item: string) => { - // return { - // name: item, - // count: 0 - // } - // }) - // }; - // }) - // Process and set the fetched proposal list - // const proposalsList = await getList(list); - // const originList = proposalsList || []; - // Set filter list for proposal filtering - setFilterList(VOTE_FILTER_LIST.map((item) => { - return { - label: t(item.label), - value: item.value - } - })); - // Set the proposal list state - // setProposalList([...storingData, ...originList]); - } catch (e) { - console.log(e); - } finally { - setLoading(false); - } - } - /** - * get proposal info - * @param proposals - */ - // const getList = async (proposals: ProposalData[]) => { - // // IPFS URL list - // const ipfsUrls = proposals.map( - // (_item: ProposalData) => `https://${_item.cid}.ipfs.w3s.link/` - // ); - // try { - // // IPFS data List - // const responses = await Promise.all(ipfsUrls.map((url: string) => axios.get(url))); - // const { data } = await axios.get(worldTimeApi); - // const results: ProposalList[] = responses.map((res, i: number) => { - // const proposal = proposals[i]; - // const now = data?.unixtime; - // let proposalStatus = 0; - // // Set proposal status - // if (now < proposal.startTime) { - // proposalStatus = PENDING_STATUS; - // } else { - // if (now >= proposal.expTime) { - // if (proposal.proposalResults.length === 0) { - // proposalStatus = VOTE_COUNTING_STATUS - // } else { - - // proposalStatus = COMPLETED_STATUS - // } - // } else { - // proposalStatus = IN_PROGRESS_STATUS - // } - // } - // // Prepare option - // const option = res.data.option?.map((item: string, index: number) => { - // const proposalItem = proposal?.proposalResults?.find( - // (proposal: ProposalResult) => proposal.optionId === index - // ); - // return { - // name: item, - // count: proposalItem?.votes ? Number(proposalItem.votes) : 0, - // }; - // }); - - - // let subStatus = 0 - // if (proposalStatus == COMPLETED_STATUS) { - // const passedOption = option?.find((v: any) => { return v.name === VOTE_OPTIONS[0] }) - // const rejectOption = option?.find((v: any) => { return v.name === VOTE_OPTIONS[1] }) - // if (passedOption?.count > rejectOption?.count) { - // subStatus = PASSED_STATUS - // } else { - // subStatus = REJECTED_STATUS - // } - // } - // return { - // ...res.data, - // id: proposal.id, - // cid: proposal.cid, - // option, - // proposalStatus, - // subStatus - // }; - // }); - // return results; - // } catch (error) { - // console.error(error); - // } - // }; const queryVotingList = async (page: number, proposalStatus: number) => { + setLoading(true); const params = { chainId, page, @@ -305,8 +115,9 @@ const Home = () => { searchKey: searchKey, status: proposalStatus === VOTE_ALL_STATUS ? 0 : proposalStatus, } - const { data: { data: votingData } } = await axios.get('/api/proposal/list', { params }) - setVotingList({ votingList: votingData?.list || [], totalPage: votingData?.total, searchKey: searchKey }) + const { data: { data: votingData } } = await axios.get('/api/proposal/list', { params }); + setVotingList({ votingList: votingData?.list || [], totalPage: votingData?.total, searchKey: searchKey }); + setLoading(false); } /** * filter proposal list @@ -376,6 +187,7 @@ const Home = () => { const maxOption = (item?.voteResult || [])?.reduce((prev, current) => { return (prev.votes > current.votes) ? prev : current; }, 0); + let href = ''; let img = ''; if (item?.githubName) { @@ -412,7 +224,6 @@ const Home = () => { -
{ @@ -428,23 +239,23 @@ const Home = () => { {markdownToText(item.descriptions)}
{ - maxOption > 0 && + maxOption?.votes > 0 &&
{ item.voteResult?.map((option: ProposalResult, index: number) => { - const isapprove = option.optionId == 0 //0 approve 1 reject - const passed = maxOption.optionId == 0 - let bgColor = "#F7F7F7" - let txColor = "#273141" - let borderColor = "#F7F7F7" + const isapprove = option.optionId == 0; //0 approve 1 reject + const passed = maxOption.optionId == 0; + let bgColor = "#F7F7F7"; + let txColor = "#273141"; + let borderColor = "#F7F7F7"; if (isapprove && passed) { - bgColor = "#E3FFEE" - txColor = "#006227" - borderColor = "#87FFBE" + bgColor = "#E3FFEE"; + txColor = "#006227"; + borderColor = "#87FFBE"; } else if (!isapprove && !passed) { - bgColor = "#FFF3F3" - txColor = "#AA0101" - borderColor = "#FFDBDB" + bgColor = "#FFF3F3"; + txColor = "#AA0101"; + borderColor = "#FFDBDB"; } return (
@@ -452,13 +263,13 @@ const Home = () => { style={{ color: txColor }} className='absolute ml-3 flex items-center leading-[35px] font-semibold'> { - option.votes > 0 && option.votes === maxOption.votes && + ((maxOption.votes === 50 && option.optionId === 1) || (maxOption.votes > 50 && option.votes > 0 && option.votes === maxOption.votes)) && } - {option.optionId === 0 ? t('content.approve') : t('content.reject')} + {option.optionId === 0 ? t('content.approve') : t('content.rejected')}
{option.votes}%
{option.votes > 0 &&
@@ -480,7 +291,7 @@ const Home = () => { const renderContent = () => { // Display loading when data is loading - if (getProposalIdLoading || getLatestIdLoading || loading) { + if (loading) { return ( ); @@ -521,14 +332,12 @@ const Home = () => {
{contextHolder}
-
- -
+ { !!isFipEditorAddress &&