From eb2d89c72f3d1069398e7a42fe328d5cd77570e9 Mon Sep 17 00:00:00 2001 From: Joseph Chalabi Date: Sat, 7 Dec 2024 00:07:51 -0700 Subject: [PATCH 1/8] group page rework / modal fixes --- components/admins/modals/validatorModal.tsx | 263 +++++----- components/admins/modals/warningModal.tsx | 45 +- components/bank/components/historyBox.tsx | 14 +- components/bank/components/sendBox.tsx | 10 + components/bank/components/tokenList.tsx | 19 +- components/bank/forms/ibcSendForm.tsx | 2 + components/bank/forms/sendForm.tsx | 53 +- components/bank/modals/sendModal.tsx | 53 +- components/factory/modals/BurnModal.tsx | 126 +++-- components/factory/modals/MintModal.tsx | 145 +++--- .../factory/modals/updateDenomMetadata.tsx | 47 +- .../__tests__/groupProposals.test.tsx | 2 +- .../groups/components/groupControls.tsx | 469 ++++++++++++++++++ .../groups/components/groupProposals.tsx | 442 ----------------- components/groups/components/index.tsx | 2 +- components/groups/components/myGroups.tsx | 103 +++- next.config.js | 4 - pages/bank.tsx | 10 +- pages/groups/index.tsx | 4 +- tests/mock.ts | 5 + 20 files changed, 1104 insertions(+), 714 deletions(-) create mode 100644 components/groups/components/groupControls.tsx delete mode 100644 components/groups/components/groupProposals.tsx diff --git a/components/admins/modals/validatorModal.tsx b/components/admins/modals/validatorModal.tsx index 05f961dd..c2363f4d 100644 --- a/components/admins/modals/validatorModal.tsx +++ b/components/admins/modals/validatorModal.tsx @@ -15,6 +15,7 @@ import { Formik, Form, Field, ErrorMessage, FieldProps } from 'formik'; import * as Yup from 'yup'; import { calculateIsUnsafe } from '@/utils/maths'; import { TextInput } from '@/components/react'; +import { createPortal } from 'react-dom'; const PowerUpdateSchema = Yup.object().shape({ power: Yup.number() @@ -121,11 +122,27 @@ export function ValidatorDetailsModal({ (document.getElementById(modalId) as HTMLDialogElement)?.close(); }; - return ( + const modalContent = ( {}} enableReinitialize > - {({ isValid, errors, touched }) => { - return ( -
- -
-

Validator Details

-
- VALIDATOR -
- {validator?.logo_url !== '' ? ( - - ) : ( - - )} - - {validator?.description.moniker} - -
+ {({ isValid, errors, touched }) => ( +
+ +
+

Validator Details

+
+ VALIDATOR +
+ {validator?.logo_url !== '' ? ( + + ) : ( + + )} + + {validator?.description.moniker} +
+
-
-
- - SECURITY CONTACT - -

- {isEmail(validator?.description.security_contact) - ? validator?.description.security_contact - : 'No Security Contact'} -

-
-
- POWER -
-
-
- - {({ field, meta }: FieldProps) => ( -
- ) => { - field.onChange(e); - setPowerInput(e.target.value); - }} - /> - {meta.touched && meta.error && ( -
-
-
- )} -
- )} -
-
- +
- {isUnsafe && Number(power) > 0 && ( -
- Warning: This power update may be unsafe -
- )} +
+ {isUnsafe && Number(power) > 0 && ( +
+ Warning: This power update may be unsafe +
+ )}
-
- - OPERATOR ADDRESS - -
- -
+
+
+ OPERATOR ADDRESS +
+
-
-
- DETAILS - {validator?.description.details.length > 50 && ( - - )} -
-

- {validator?.description.details - ? validator.description.details.substring(0, 50) + - (validator.description.details.length > 50 ? '...' : '') - : 'No Details'} -

+
+
+
+ DETAILS + {validator?.description.details.length > 50 && ( + + )}
+

+ {validator?.description.details + ? validator.description.details.substring(0, 50) + + (validator.description.details.length > 50 ? '...' : '') + : 'No Details'} +

- ); - }} +
+ )} -
- + +
); + + // Only render if we're in the browser + if (typeof document !== 'undefined') { + return createPortal(modalContent, document.body); + } + + return null; } diff --git a/components/admins/modals/warningModal.tsx b/components/admins/modals/warningModal.tsx index bb868e2d..95e8cb20 100644 --- a/components/admins/modals/warningModal.tsx +++ b/components/admins/modals/warningModal.tsx @@ -6,6 +6,7 @@ import { MsgRemoveValidator } from '@liftedinit/manifestjs/dist/codegen/strangel import { useChain } from '@cosmos-kit/react'; import React, { useEffect, useState } from 'react'; import { PiWarning } from 'react-icons/pi'; +import { createPortal } from 'react-dom'; interface WarningModalProps { admin: string; @@ -90,11 +91,27 @@ export function WarningModal({ (document.getElementById(modalId) as HTMLDialogElement)?.close(); }; - return ( + const modalContent = (
✕ -
+
@@ -136,9 +153,29 @@ export function WarningModal({
-
- + +
); + + // Only render if we're in the browser + if (typeof document !== 'undefined') { + return createPortal(modalContent, document.body); + } + + return null; } diff --git a/components/bank/components/historyBox.tsx b/components/bank/components/historyBox.tsx index 3471c2c0..60465555 100644 --- a/components/bank/components/historyBox.tsx +++ b/components/bank/components/historyBox.tsx @@ -53,6 +53,9 @@ export function HistoryBox({ txLoading, isError, refetch, + skeletonGroupCount, + skeletonTxCount, + isGroup, }: { isLoading: boolean; address: string; @@ -63,6 +66,9 @@ export function HistoryBox({ txLoading: boolean; isError: boolean; refetch: () => void; + skeletonGroupCount: number; + skeletonTxCount: number; + isGroup?: boolean; }) { const [selectedTx, setSelectedTx] = useState(null); @@ -70,10 +76,6 @@ export function HistoryBox({ const { metadatas } = useTokenFactoryDenomsMetadata(); - const isMobile = useIsMobile(); - const skeletonGroupCount = 1; - const skeletonTxCount = isMobile ? 5 : 9; - function formatDateShort(dateString: string): string { const date = new Date(dateString); return date.toLocaleString('en-US', { @@ -167,7 +169,7 @@ export function HistoryBox({

- Transaction History + {isGroup ? 'Group Transactions' : 'Transaction History'}

{totalPages > 1 && ( @@ -228,7 +230,7 @@ export function HistoryBox({
{[...Array(skeletonGroupCount)].map((_, groupIndex) => (
-
+
{[...Array(skeletonTxCount)].map((_, txIndex) => (
void; refetchHistory: () => void; + refetchProposals?: () => void; selectedDenom?: string; + isGroup?: boolean; + admin?: string; }) { const [activeTab, setActiveTab] = useState<'send' | 'cross-chain'>('send'); const [selectedChain, setSelectedChain] = useState(''); @@ -82,6 +88,7 @@ export default function SendBox({ refetchBalances={refetchBalances} refetchHistory={refetchHistory} selectedDenom={selectedDenom} + isGroup={isGroup} /> ) : ( )} diff --git a/components/bank/components/tokenList.tsx b/components/bank/components/tokenList.tsx index 0b68b381..57341884 100644 --- a/components/bank/components/tokenList.tsx +++ b/components/bank/components/tokenList.tsx @@ -15,14 +15,22 @@ interface TokenListProps { refetchBalances: () => void; refetchHistory: () => void; address: string; + pageSize: number; + isGroup?: boolean; + admin?: string; + refetchProposals?: () => void; } -export default function TokenList({ +export function TokenList({ balances, isLoading, refetchBalances, refetchHistory, address, + pageSize, + isGroup, + admin, + refetchProposals, }: TokenListProps) { const [searchTerm, setSearchTerm] = useState(''); const [selectedDenom, setSelectedDenom] = useState(null); @@ -30,10 +38,6 @@ export default function TokenList({ const [currentPage, setCurrentPage] = useState(1); const [openDenomInfoModal, setOpenDenomInfoModal] = useState(false); - const isMobile = useIsMobile(); - - const pageSize = isMobile ? 4 : 9; - const filteredBalances = useMemo(() => { if (!Array.isArray(balances)) return []; return balances.filter(balance => @@ -57,7 +61,7 @@ export default function TokenList({

- Your Assets + {isGroup ? 'Group Assets' : 'Your Assets'}

{totalPages > 1 && ( @@ -237,6 +241,9 @@ export default function TokenList({ refetchHistory={refetchHistory} selectedDenom={selectedDenom} setOpen={setIsSendModalOpen} + isGroup={isGroup} + admin={admin} + refetchProposals={refetchProposals} />
); diff --git a/components/bank/forms/ibcSendForm.tsx b/components/bank/forms/ibcSendForm.tsx index ea4e3a8f..ac11ea7e 100644 --- a/components/bank/forms/ibcSendForm.tsx +++ b/components/bank/forms/ibcSendForm.tsx @@ -31,6 +31,7 @@ export default function IbcSendForm({ selectedChain, setSelectedChain, selectedDenom, + isGroup, }: Readonly<{ address: string; destinationChain: string; @@ -43,6 +44,7 @@ export default function IbcSendForm({ selectedChain: string; setSelectedChain: (selectedChain: string) => void; selectedDenom?: string; + isGroup?: boolean; }>) { const [isSending, setIsSending] = useState(false); const [searchTerm, setSearchTerm] = useState(''); diff --git a/components/bank/forms/sendForm.tsx b/components/bank/forms/sendForm.tsx index 9ca1e1f4..9ede0c3e 100644 --- a/components/bank/forms/sendForm.tsx +++ b/components/bank/forms/sendForm.tsx @@ -12,6 +12,8 @@ import { TextInput } from '@/components/react/inputs'; import { SearchIcon } from '@/components/icons'; import { TailwindModal } from '@/components/react/modal'; import { MdContacts } from 'react-icons/md'; +import { Any } from 'cosmjs-types/google/protobuf/any'; +import { MsgSend } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/tx'; export default function SendForm({ address, @@ -20,6 +22,9 @@ export default function SendForm({ refetchBalances, refetchHistory, selectedDenom, + isGroup, + admin, + refetchProposals, }: Readonly<{ address: string; balances: CombinedBalanceInfo[]; @@ -27,6 +32,9 @@ export default function SendForm({ refetchBalances: () => void; refetchHistory: () => void; selectedDenom?: string; + isGroup?: boolean; + admin?: string; + refetchProposals?: () => void; }>) { const [isSending, setIsSending] = useState(false); const [searchTerm, setSearchTerm] = useState(''); @@ -34,6 +42,7 @@ export default function SendForm({ const { tx } = useTx(chainName); const { estimateFee } = useFeeEstimation(chainName); const { send } = cosmos.bank.v1beta1.MessageComposer.withTypeUrl; + const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; const [isContactsOpen, setIsContactsOpen] = useState(false); const filteredBalances = balances?.filter(token => { @@ -97,25 +106,59 @@ export default function SendForm({ }) => { setIsSending(true); try { + if (!address) { + throw new Error('Wallet not connected'); + } + const exponent = values.selectedToken.metadata?.denom_units[1]?.exponent ?? 6; const amountInBaseUnits = parseNumberToBigInt(values.amount, exponent).toString(); - const msg = send({ - fromAddress: address, - toAddress: values.recipient, - amount: [{ denom: values.selectedToken.coreDenom, amount: amountInBaseUnits }], - }); + if (isGroup && !admin) { + throw new Error('Admin address not provided for group transaction'); + } + + const msg = isGroup + ? submitProposal({ + groupPolicyAddress: admin!, + messages: [ + Any.fromPartial({ + typeUrl: MsgSend.typeUrl, + value: MsgSend.encode( + send({ + fromAddress: admin!, + toAddress: values.recipient, + amount: [{ denom: values.selectedToken.coreDenom, amount: amountInBaseUnits }], + }).value + ).finish(), + }), + ], + metadata: '', + proposers: [address], + title: `Send Tokens`, + summary: `This proposal will send ${values.amount} ${values.selectedToken.metadata?.display} to ${values.recipient}`, + exec: 0, + }) + : send({ + fromAddress: address, + toAddress: values.recipient, + amount: [{ denom: values.selectedToken.coreDenom, amount: amountInBaseUnits }], + }); + + console.log('Estimating fee for address:', address); const fee = await estimateFee(address, [msg]); + await tx([msg], { memo: values.memo, fee, onSuccess: () => { refetchBalances(); refetchHistory(); + refetchProposals?.(); }, }); } catch (error) { console.error('Error during sending:', error); + // You might want to show this error to the user through a toast or alert } finally { setIsSending(false); } diff --git a/components/bank/modals/sendModal.tsx b/components/bank/modals/sendModal.tsx index abc49c38..43b53eac 100644 --- a/components/bank/modals/sendModal.tsx +++ b/components/bank/modals/sendModal.tsx @@ -2,6 +2,7 @@ import React from 'react'; import SendBox from '../components/sendBox'; import { CombinedBalanceInfo } from '@/utils/types'; import { useEffect } from 'react'; +import { createPortal } from 'react-dom'; interface SendModalProps { modalId: string; @@ -13,6 +14,9 @@ interface SendModalProps { refetchHistory: () => void; selectedDenom?: string; setOpen?: (isOpen: boolean) => void; + isGroup?: boolean; + admin?: string; + refetchProposals?: () => void; } export default function SendModal({ @@ -25,6 +29,9 @@ export default function SendModal({ selectedDenom, isOpen, setOpen, + isGroup, + admin, + refetchProposals, }: SendModalProps) { const handleClose = () => { if (setOpen) { @@ -44,14 +51,30 @@ export default function SendModal({ return () => document.removeEventListener('keydown', handleEscape); }, [isOpen]); - return ( + const modalContent = (
@@ -72,11 +95,33 @@ export default function SendModal({ refetchBalances={refetchBalances} refetchHistory={refetchHistory} selectedDenom={selectedDenom} + isGroup={isGroup} + admin={admin} + refetchProposals={refetchProposals} />
- +
); + + // Only render if we're in the browser + if (typeof document !== 'undefined') { + return createPortal(modalContent, document.body); + } + + return null; } diff --git a/components/factory/modals/BurnModal.tsx b/components/factory/modals/BurnModal.tsx index 165f2d00..f932a203 100644 --- a/components/factory/modals/BurnModal.tsx +++ b/components/factory/modals/BurnModal.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; - +import { createPortal } from 'react-dom'; import BurnForm from '@/components/factory/forms/BurnForm'; import { useGroupsByAdmin, usePoaGetAdmin } from '@/hooks'; import { ExtendedMetadataSDKType, truncateString } from '@/utils'; @@ -33,6 +33,7 @@ export default function BurnModal({ document.addEventListener('keydown', handleEscape); return () => document.removeEventListener('keydown', handleEscape); }, [isOpen]); + const { poaAdmin, isPoaAdminLoading } = usePoaGetAdmin(); const { groupByAdmin, isGroupByAdminLoading } = useGroupsByAdmin( poaAdmin ?? 'manifest1afk9zr2hn2jsac63h4hm60vl9z3e5u69gndzf7c99cqge3vzwjzsfmy9qj' @@ -44,51 +45,84 @@ export default function BurnModal({ if (!denom) return null; - const handleMultiBurnOpen = () => { - onSwitchToMultiBurn(); - }; - - return ( - <> - -
-
- -
-

- Burn{' '} - - {denom.display - ? denom.display.startsWith('factory') - ? (denom.display.split('/').pop()?.toUpperCase() ?? - truncateString(denom.display, 12).toUpperCase()) - : truncateString(denom.display, 12).toUpperCase() - : 'UNKNOWN'} - -

-
- {isLoading ? ( -
- ) : ( - - )} -
-
-
- + const modalContent = ( + +
+ + -
- +

+ Burn{' '} + + {denom.display + ? denom.display.startsWith('factory') + ? (denom.display.split('/').pop()?.toUpperCase() ?? + truncateString(denom.display, 12).toUpperCase()) + : truncateString(denom.display, 12).toUpperCase() + : 'UNKNOWN'} + +

+
+ {isLoading ? ( +
+ ) : ( + + )} +
+
+
+ +
+ ); + + // Only render if we're in the browser + if (typeof document !== 'undefined') { + return createPortal(modalContent, document.body); + } + + return null; } diff --git a/components/factory/modals/MintModal.tsx b/components/factory/modals/MintModal.tsx index 5a1dea16..74a32de9 100644 --- a/components/factory/modals/MintModal.tsx +++ b/components/factory/modals/MintModal.tsx @@ -4,6 +4,7 @@ import MintForm from '@/components/factory/forms/MintForm'; import { useGroupsByAdmin, usePoaGetAdmin } from '@/hooks'; import { ExtendedMetadataSDKType, truncateString } from '@/utils'; import { MultiMintModal } from './multiMfxMintModal'; +import { createPortal } from 'react-dom'; export default function MintModal({ denom, @@ -45,22 +46,7 @@ export default function MintModal({ admin ?? 'manifest1afk9zr2hn2jsac63h4hm60vl9z3e5u69gndzf7c99cqge3vzwjzsfmy9qj' ); - if (!isOpen) { - return null; - } - - if (!denom || !address) { - return ( - -
-
-
-
- -
-
- ); - } + if (!denom) return null; const members = groupByAdmin?.groups?.[0]?.members; const isAdmin = members?.some(member => member?.member?.address === address); @@ -77,56 +63,83 @@ export default function MintModal({ setIsMultiMintOpen(false); }; - return ( - <> - -
-
- -
-

- Mint{' '} - - {denom.display.startsWith('factory') - ? (denom.display.split('/').pop() || denom.display).toUpperCase() - : truncateString(denom.display, 12)} - -

-
- {isLoading ? ( -
- ) : ( - - )} -
-
-
- + const modalContent = ( + +
+ + -
- - - +

+ Mint{' '} + + {denom.display + ? denom.display.startsWith('factory') + ? (denom.display.split('/').pop()?.toUpperCase() ?? + truncateString(denom.display, 12).toUpperCase()) + : truncateString(denom.display, 12).toUpperCase() + : 'UNKNOWN'} + +

+
+ {isLoading ? ( +
+ ) : ( + + )} +
+
+
+ +
+ ); + + // Only render if we're in the browser + if (typeof document !== 'undefined') { + return createPortal(modalContent, document.body); + } + + return null; } diff --git a/components/factory/modals/updateDenomMetadata.tsx b/components/factory/modals/updateDenomMetadata.tsx index 0356414c..1eab38cf 100644 --- a/components/factory/modals/updateDenomMetadata.tsx +++ b/components/factory/modals/updateDenomMetadata.tsx @@ -8,6 +8,7 @@ import Yup from '@/utils/yupExtensions'; import { TextInput, TextArea } from '@/components/react/inputs'; import { truncateString, ExtendedMetadataSDKType } from '@/utils'; import { useEffect } from 'react'; +import { createPortal } from 'react-dom'; const TokenDetailsSchema = Yup.object().shape({ display: Yup.string().noProfanity(), @@ -112,8 +113,27 @@ export function UpdateDenomMetadataModal({ } }; - return ( - + const modalContent = ( + {({ isValid, dirty, values, handleChange, handleSubmit, resetForm }) => ( -
+
); + + // Only render if we're in the browser + if (typeof document !== 'undefined') { + return createPortal(modalContent, document.body); + } + + return null; } diff --git a/components/groups/components/__tests__/groupProposals.test.tsx b/components/groups/components/__tests__/groupProposals.test.tsx index 78b2ee42..7c9fc44c 100644 --- a/components/groups/components/__tests__/groupProposals.test.tsx +++ b/components/groups/components/__tests__/groupProposals.test.tsx @@ -1,7 +1,7 @@ import { describe, test, afterEach, expect, jest, mock, beforeEach } from 'bun:test'; import React from 'react'; import { screen, fireEvent, cleanup, waitFor } from '@testing-library/react'; -import ProposalsForPolicy from '@/components/groups/components/groupProposals'; +import ProposalsForPolicy from '@/components/groups/components/groupControls'; import matchers from '@testing-library/jest-dom/matchers'; import { renderWithChainProvider } from '@/tests/render'; import { mockProposals, mockGroup, mockGroup2 } from '@/tests/mock'; diff --git a/components/groups/components/groupControls.tsx b/components/groups/components/groupControls.tsx new file mode 100644 index 00000000..ee9c64b7 --- /dev/null +++ b/components/groups/components/groupControls.tsx @@ -0,0 +1,469 @@ +import { useState, useEffect } from 'react'; +import { + useProposalsByPolicyAccount, + useTallyCount, + useVotesByProposal, + useMultipleTallyCounts, +} from '@/hooks/useQueries'; +import { ProposalSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/types'; +import { QueryTallyResultResponseSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/query'; + +import { SearchIcon } from '@/components/icons'; +import { useRouter } from 'next/router'; + +import VoteDetailsModal from '@/components/groups/modals/voteDetailsModal'; +import { useGroupsByMember } from '@/hooks/useQueries'; +import { useChain } from '@cosmos-kit/react'; +import { MemberSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/types'; +import { ArrowRightIcon } from '@/components/icons'; +import ProfileAvatar from '@/utils/identicon'; +import { HistoryBox, TransactionGroup } from '@/components'; +import { TokenList } from '@/components'; +import { CombinedBalanceInfo } from '@/utils'; + +type GroupProposalsProps = { + policyAddress: string; + groupName: string; + onBack: () => void; + policyThreshold: string; + isLoading: boolean; + currentPage: number; + setCurrentPage: React.Dispatch>; + sendTxs: TransactionGroup[]; + totalPages: number; + txLoading: boolean; + isError: boolean; + balances: CombinedBalanceInfo[]; + refetchBalances: () => void; + refetchHistory: () => void; + pageSize: number; + skeletonGroupCount: number; + skeletonTxCount: number; +}; + +export default function GroupProposals({ + policyAddress, + groupName, + onBack, + policyThreshold, + isLoading, + currentPage, + setCurrentPage, + sendTxs, + totalPages, + txLoading, + isError, + balances, + refetchBalances, + refetchHistory, + pageSize, + skeletonGroupCount, + skeletonTxCount, +}: GroupProposalsProps) { + const { proposals, isProposalsLoading, isProposalsError, refetchProposals } = + useProposalsByPolicyAccount(policyAddress); + + const [selectedProposal, setSelectedProposal] = useState(null); + const [members, setMembers] = useState([]); + + const [searchTerm, setSearchTerm] = useState(''); + + // Convert proposalId to string before passing it to the hooks + const proposalId = selectedProposal?.id ?? 0n; + + // Use the string version of the proposalId + const { tally, refetchTally } = useTallyCount(proposalId); + const { votes, refetchVotes } = useVotesByProposal(proposalId); + + const filterProposals = (proposals: ProposalSDKType[]) => { + return proposals.filter( + proposal => + proposal.status.toString() !== 'PROPOSAL_STATUS_REJECTED' && + proposal.status.toString() !== 'PROPOSAL_STATUS_WITHDRAWN' + ); + }; + + const router = useRouter(); + + useEffect(() => { + const { proposalId } = router.query; + if (proposalId && typeof proposalId === 'string' && proposals.length > 0) { + const proposalToOpen = proposals.find(p => p.id.toString() === proposalId); + if (proposalToOpen) { + setSelectedProposal(proposalToOpen); + setTimeout(() => { + const modal = document.getElementById(`vote_modal_${proposalId}`) as HTMLDialogElement; + if (modal) { + modal.showModal(); + } + }, 0); + } else { + console.warn(`Proposal with ID ${proposalId} not found`); + // remove the invalid proposalId from the URL + router.push(`/groups?policyAddress=${policyAddress}`, undefined, { shallow: true }); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [router.query, proposals, policyAddress]); + + const handleRowClick = (proposal: ProposalSDKType) => { + setSelectedProposal(proposal); + // Update URL without navigating + router.push(`/groups?policyAddress=${policyAddress}&proposalId=${proposal.id}`, undefined, { + shallow: true, + }); + setTimeout(() => { + const modal = document.getElementById(`vote_modal_${proposal.id}`) as HTMLDialogElement; + if (modal) { + modal.showModal(); + } else { + console.error(`Modal not found for proposal ${proposal.id}`); + } + }, 0); + }; + + const closeModal = () => { + setSelectedProposal(null); + // Remove proposalId from URL when closing the modal + router.push(`/groups?policyAddress=${policyAddress}`, undefined, { shallow: true }); + }; + + function isProposalPassing(tally: QueryTallyResultResponseSDKType) { + const yesCount = BigInt(tally?.tally?.yes_count ?? '0'); + const noCount = BigInt(tally?.tally?.no_count ?? '0'); + const noWithVetoCount = BigInt(tally?.tally?.no_with_veto_count ?? '0'); + const abstainCount = BigInt(tally?.tally?.abstain_count ?? '0'); + + const totalVotes = yesCount + noCount + noWithVetoCount + abstainCount; + const totalNoVotes = noCount + noWithVetoCount; + + // Check if threshold is reached + const threshold = BigInt(policyThreshold); + const isThresholdReached = totalVotes >= threshold; + + // Check for tie + const isTie = yesCount === totalNoVotes && yesCount > 0; + + // Determine if passing based on vote distribution + const isPassing = isThresholdReached && yesCount > totalNoVotes; + + return { + isPassing, + yesCount, + noCount, + noWithVetoCount, + abstainCount, + isThresholdReached, + isTie, + }; + } + + type ChainMessageType = + | '/cosmos.bank.v1beta1.MsgSend' + | '/strangelove_ventures.poa.v1.MsgSetPower' + | '/cosmos.group.v1.MsgCreateGroup' + | '/cosmos.group.v1.MsgUpdateGroupMembers' + | '/cosmos.group.v1.MsgUpdateGroupAdmin' + | '/cosmos.group.v1.MsgUpdateGroupMetadata' + | '/cosmos.group.v1.MsgCreateGroupPolicy' + | '/cosmos.group.v1.MsgCreateGroupWithPolicy' + | '/cosmos.group.v1.MsgSubmitProposal' + | '/cosmos.group.v1.MsgVote' + | '/cosmos.group.v1.MsgExec' + | '/cosmos.group.v1.MsgLeaveGroup' + | '/manifest.v1.MsgUpdateParams' + | '/manifest.v1.MsgPayout' + | '/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade' + | '/cosmos.upgrade.v1beta1.MsgCancelUpgrade'; + + const typeRegistry: Record = { + '/cosmos.bank.v1beta1.MsgSend': 'Send', + '/strangelove_ventures.poa.v1.MsgSetPower': 'Set Power', + '/cosmos.group.v1.MsgCreateGroup': 'Create Group', + '/cosmos.group.v1.MsgUpdateGroupMembers': 'Update Group Members', + '/cosmos.group.v1.MsgUpdateGroupAdmin': 'Update Group Admin', + '/cosmos.group.v1.MsgUpdateGroupMetadata': 'Update Group Metadata', + '/cosmos.group.v1.MsgCreateGroupPolicy': 'Create Group Policy', + '/cosmos.group.v1.MsgCreateGroupWithPolicy': 'Create Group With Policy', + '/cosmos.group.v1.MsgSubmitProposal': 'Submit Proposal', + '/cosmos.group.v1.MsgVote': 'Vote', + '/cosmos.group.v1.MsgExec': 'Execute Proposal', + '/cosmos.group.v1.MsgLeaveGroup': 'Leave Group', + '/manifest.v1.MsgUpdateParams': 'Update Manifest Params', + '/manifest.v1.MsgPayout': 'Payout', + '/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade': 'Software Upgrade', + '/cosmos.upgrade.v1beta1.MsgCancelUpgrade': 'Cancel Upgrade', + }; + + function getHumanReadableType(type: string): string { + const registeredType = typeRegistry[type as ChainMessageType]; + if (registeredType) { + return registeredType; + } + const parts = type.split('.'); + const lastPart = parts[parts.length - 1]; + return lastPart + .replace('Msg', '') + .replace(/([A-Z])/g, ' $1') + .trim(); + } + + const { address } = useChain('manifest'); + const { groupByMemberData } = useGroupsByMember(address ?? ''); + + useEffect(() => { + if (groupByMemberData && policyAddress) { + const group = groupByMemberData.groups.find( + g => g.policies.length > 0 && g.policies[0]?.address === policyAddress + ); + if (group) { + setMembers( + group.members.map(member => ({ + ...member.member, + address: member?.member?.address || '', + weight: member?.member?.weight || '0', + metadata: member?.member?.metadata || '', + added_at: member?.member?.added_at || new Date(), + isCoreMember: true, + isActive: true, + isAdmin: member?.member?.address === group.admin, + isPolicyAdmin: member?.member?.address === group.policies[0]?.admin, + })) + ); + } + } + }, [groupByMemberData, policyAddress]); + + const filteredProposals = filterProposals(proposals).filter(proposal => + proposal.title.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const { tallies, isLoading: isTalliesLoading } = useMultipleTallyCounts(proposals.map(p => p.id)); + + return ( +
+
+
+ {/* Header section */} +
+
+ +

{groupName}

+
+ +
+
+
+ + {/* Search and New Proposal section */} +
+
+

Proposals

+
+ setSearchTerm(e.target.value)} + aria-label="Search proposals" + /> +
+
+
+ + {/* Updated Table section */} +
+ {isProposalsLoading ? ( +
+ +
+ ) : isProposalsError ? ( +
+ Error loading proposals +
+ ) : filteredProposals.length > 0 ? ( + + + + + + + + + + + + {filteredProposals.map(proposal => { + const endTime = new Date(proposal?.voting_period_end); + const now = new Date(); + const msPerMinute = 1000 * 60; + const msPerHour = msPerMinute * 60; + const msPerDay = msPerHour * 24; + + const diff = endTime.getTime() - now.getTime(); + + let timeLeft: string; + + if (diff <= 0) { + timeLeft = 'none'; + } else if (diff >= msPerDay) { + const days = Math.floor(diff / msPerDay); + timeLeft = `${days} day${days === 1 ? '' : 's'}`; + } else if (diff >= msPerHour) { + const hours = Math.floor(diff / msPerHour); + timeLeft = `${hours} hour${hours === 1 ? '' : 's'}`; + } else if (diff >= msPerMinute) { + const minutes = Math.floor(diff / msPerMinute); + timeLeft = `${minutes} minute${minutes === 1 ? '' : 's'}`; + } else { + timeLeft = 'less than a minute'; + } + + const proposalTally = tallies.find(t => t.proposalId === proposal.id)?.tally; + + let status = 'Pending'; + if (proposal.status.toString() === 'PROPOSAL_STATUS_ACCEPTED') { + status = 'Execute'; + } else if (proposal.status.toString() === 'PROPOSAL_STATUS_CLOSED') { + status = 'Executed'; + } else if (proposalTally) { + const { isPassing, isThresholdReached, isTie } = + isProposalPassing(proposalTally); + if (isThresholdReached) { + if (isTie) { + status = 'Tie'; + } else { + status = isPassing ? 'Passing' : 'Failing'; + } + } + } + return ( + handleRowClick(proposal)} + className="group text-black dark:text-white rounded-lg cursor-pointer" + > + + + + + + + ); + })} + +
+ ID + + Title + + Time Left + + Type + + Status +
+ {proposal.id.toString()} + + {proposal.title} + + {timeLeft} + + {proposal.messages.length > 0 + ? proposal.messages.map((message, index) => ( +
+ {getHumanReadableType((message as any)['@type'])} +
+ )) + : 'No messages'} +
+ {isTalliesLoading ? ( + + ) : ( + status + )} +
+ ) : ( +
+ No proposal was found. +
+ )} +
+
+ +
+
+ +
+
+ +
+
+
+ + {/* Modals */} + +
+ ); +} diff --git a/components/groups/components/groupProposals.tsx b/components/groups/components/groupProposals.tsx deleted file mode 100644 index 35133374..00000000 --- a/components/groups/components/groupProposals.tsx +++ /dev/null @@ -1,442 +0,0 @@ -import { useState, useEffect } from 'react'; -import { - useProposalsByPolicyAccount, - useTallyCount, - useVotesByProposal, - useMultipleTallyCounts, -} from '@/hooks/useQueries'; -import { ProposalSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/types'; -import { QueryTallyResultResponseSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/query'; -import Link from 'next/link'; -import { SearchIcon } from '@/components/icons'; -import { useRouter } from 'next/router'; - -import VoteDetailsModal from '@/components/groups/modals/voteDetailsModal'; -import { useGroupsByMember } from '@/hooks/useQueries'; -import { useChain } from '@cosmos-kit/react'; -import { MemberSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/types'; -import { ArrowRightIcon } from '@/components/icons'; -import ProfileAvatar from '@/utils/identicon'; - -type GroupProposalsProps = { - policyAddress: string; - groupName: string; - onBack: () => void; - policyThreshold: string; -}; - -export default function GroupProposals({ - policyAddress, - groupName, - onBack, - policyThreshold, -}: GroupProposalsProps) { - const { proposals, isProposalsLoading, isProposalsError, refetchProposals } = - useProposalsByPolicyAccount(policyAddress); - - const [selectedProposal, setSelectedProposal] = useState(null); - const [members, setMembers] = useState([]); - - const [searchTerm, setSearchTerm] = useState(''); - - // Convert proposalId to string before passing it to the hooks - const proposalId = selectedProposal?.id ?? 0n; - - // Use the string version of the proposalId - const { tally, refetchTally } = useTallyCount(proposalId); - const { votes, refetchVotes } = useVotesByProposal(proposalId); - - const filterProposals = (proposals: ProposalSDKType[]) => { - return proposals.filter( - proposal => - proposal.status.toString() !== 'PROPOSAL_STATUS_REJECTED' && - proposal.status.toString() !== 'PROPOSAL_STATUS_WITHDRAWN' - ); - }; - - const router = useRouter(); - - useEffect(() => { - const { proposalId } = router.query; - if (proposalId && typeof proposalId === 'string' && proposals.length > 0) { - const proposalToOpen = proposals.find(p => p.id.toString() === proposalId); - if (proposalToOpen) { - setSelectedProposal(proposalToOpen); - setTimeout(() => { - const modal = document.getElementById(`vote_modal_${proposalId}`) as HTMLDialogElement; - if (modal) { - modal.showModal(); - } - }, 0); - } else { - console.warn(`Proposal with ID ${proposalId} not found`); - // remove the invalid proposalId from the URL - router.push(`/groups?policyAddress=${policyAddress}`, undefined, { shallow: true }); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [router.query, proposals, policyAddress]); - - const handleRowClick = (proposal: ProposalSDKType) => { - setSelectedProposal(proposal); - // Update URL without navigating - router.push(`/groups?policyAddress=${policyAddress}&proposalId=${proposal.id}`, undefined, { - shallow: true, - }); - setTimeout(() => { - const modal = document.getElementById(`vote_modal_${proposal.id}`) as HTMLDialogElement; - if (modal) { - modal.showModal(); - } else { - console.error(`Modal not found for proposal ${proposal.id}`); - } - }, 0); - }; - - const closeModal = () => { - setSelectedProposal(null); - // Remove proposalId from URL when closing the modal - router.push(`/groups?policyAddress=${policyAddress}`, undefined, { shallow: true }); - }; - - function isProposalPassing(tally: QueryTallyResultResponseSDKType) { - const yesCount = BigInt(tally?.tally?.yes_count ?? '0'); - const noCount = BigInt(tally?.tally?.no_count ?? '0'); - const noWithVetoCount = BigInt(tally?.tally?.no_with_veto_count ?? '0'); - const abstainCount = BigInt(tally?.tally?.abstain_count ?? '0'); - - const totalVotes = yesCount + noCount + noWithVetoCount + abstainCount; - const totalNoVotes = noCount + noWithVetoCount; - - // Check if threshold is reached - const threshold = BigInt(policyThreshold); - const isThresholdReached = totalVotes >= threshold; - - // Check for tie - const isTie = yesCount === totalNoVotes && yesCount > 0; - - // Determine if passing based on vote distribution - const isPassing = isThresholdReached && yesCount > totalNoVotes; - - return { - isPassing, - yesCount, - noCount, - noWithVetoCount, - abstainCount, - isThresholdReached, - isTie, - }; - } - - type ChainMessageType = - | '/cosmos.bank.v1beta1.MsgSend' - | '/strangelove_ventures.poa.v1.MsgSetPower' - | '/cosmos.group.v1.MsgCreateGroup' - | '/cosmos.group.v1.MsgUpdateGroupMembers' - | '/cosmos.group.v1.MsgUpdateGroupAdmin' - | '/cosmos.group.v1.MsgUpdateGroupMetadata' - | '/cosmos.group.v1.MsgCreateGroupPolicy' - | '/cosmos.group.v1.MsgCreateGroupWithPolicy' - | '/cosmos.group.v1.MsgSubmitProposal' - | '/cosmos.group.v1.MsgVote' - | '/cosmos.group.v1.MsgExec' - | '/cosmos.group.v1.MsgLeaveGroup' - | '/manifest.v1.MsgUpdateParams' - | '/manifest.v1.MsgPayout' - | '/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade' - | '/cosmos.upgrade.v1beta1.MsgCancelUpgrade'; - - const typeRegistry: Record = { - '/cosmos.bank.v1beta1.MsgSend': 'Send', - '/strangelove_ventures.poa.v1.MsgSetPower': 'Set Power', - '/cosmos.group.v1.MsgCreateGroup': 'Create Group', - '/cosmos.group.v1.MsgUpdateGroupMembers': 'Update Group Members', - '/cosmos.group.v1.MsgUpdateGroupAdmin': 'Update Group Admin', - '/cosmos.group.v1.MsgUpdateGroupMetadata': 'Update Group Metadata', - '/cosmos.group.v1.MsgCreateGroupPolicy': 'Create Group Policy', - '/cosmos.group.v1.MsgCreateGroupWithPolicy': 'Create Group With Policy', - '/cosmos.group.v1.MsgSubmitProposal': 'Submit Proposal', - '/cosmos.group.v1.MsgVote': 'Vote', - '/cosmos.group.v1.MsgExec': 'Execute Proposal', - '/cosmos.group.v1.MsgLeaveGroup': 'Leave Group', - '/manifest.v1.MsgUpdateParams': 'Update Manifest Params', - '/manifest.v1.MsgPayout': 'Payout', - '/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade': 'Software Upgrade', - '/cosmos.upgrade.v1beta1.MsgCancelUpgrade': 'Cancel Upgrade', - }; - - function getHumanReadableType(type: string): string { - const registeredType = typeRegistry[type as ChainMessageType]; - if (registeredType) { - return registeredType; - } - const parts = type.split('.'); - const lastPart = parts[parts.length - 1]; - return lastPart - .replace('Msg', '') - .replace(/([A-Z])/g, ' $1') - .trim(); - } - - const { address } = useChain('manifest'); - const { groupByMemberData } = useGroupsByMember(address ?? ''); - - const [groupId, setGroupId] = useState(''); - const [groupAdmin, setGroupAdmin] = useState(''); - - useEffect(() => { - if (groupByMemberData && policyAddress) { - const group = groupByMemberData.groups.find( - g => g.policies.length > 0 && g.policies[0]?.address === policyAddress - ); - if (group) { - setMembers( - group.members.map(member => ({ - ...member.member, - address: member?.member?.address || '', - weight: member?.member?.weight || '0', - metadata: member?.member?.metadata || '', - added_at: member?.member?.added_at || new Date(), - isCoreMember: true, - isActive: true, - isAdmin: member?.member?.address === group.admin, - isPolicyAdmin: member?.member?.address === group.policies[0]?.admin, - })) - ); - setGroupId(group.id.toString()); - setGroupAdmin(group.admin); - } - } - }, [groupByMemberData, policyAddress]); - - const filteredProposals = filterProposals(proposals).filter(proposal => - proposal.title.toLowerCase().includes(searchTerm.toLowerCase()) - ); - - const openInfoModal = () => { - const modal = document.getElementById('group-info-modal') as HTMLDialogElement | null; - if (modal) { - modal.showModal(); - } else { - console.error("Modal element 'group-info-modal' not found"); - } - }; - - const openMemberModal = () => { - const modal = document.getElementById('member-management-modal') as HTMLDialogElement | null; - if (modal) { - modal.showModal(); - } else { - console.error("Modal element 'member-management-modal' not found"); - } - }; - - const { tallies, isLoading: isTalliesLoading } = useMultipleTallyCounts(proposals.map(p => p.id)); - - return ( -
- {/* Header section */} -
-
- -

{groupName}

-
- -
-
-
- - {/* Search and New Proposal section */} -
-
-

Proposals

-
- setSearchTerm(e.target.value)} - aria-label="Search proposals" - /> -
-
-
- - - -
-
- - {/* Table section - will fill remaining space */} -
- {isProposalsLoading ? ( -
- -
- ) : isProposalsError ? ( -
- Error loading proposals -
- ) : filteredProposals.length > 0 ? ( - - - - - - - - - - - - {filteredProposals.map(proposal => { - const endTime = new Date(proposal?.voting_period_end); - const now = new Date(); - const msPerMinute = 1000 * 60; - const msPerHour = msPerMinute * 60; - const msPerDay = msPerHour * 24; - - const diff = endTime.getTime() - now.getTime(); - - let timeLeft: string; - - if (diff <= 0) { - timeLeft = 'none'; - } else if (diff >= msPerDay) { - const days = Math.floor(diff / msPerDay); - timeLeft = `${days} day${days === 1 ? '' : 's'}`; - } else if (diff >= msPerHour) { - const hours = Math.floor(diff / msPerHour); - timeLeft = `${hours} hour${hours === 1 ? '' : 's'}`; - } else if (diff >= msPerMinute) { - const minutes = Math.floor(diff / msPerMinute); - timeLeft = `${minutes} minute${minutes === 1 ? '' : 's'}`; - } else { - timeLeft = 'less than a minute'; - } - - const proposalTally = tallies.find(t => t.proposalId === proposal.id)?.tally; - - let status = 'Pending'; - if (proposal.status.toString() === 'PROPOSAL_STATUS_ACCEPTED') { - status = 'Execute'; - } else if (proposal.status.toString() === 'PROPOSAL_STATUS_CLOSED') { - status = 'Executed'; - } else if (proposalTally) { - const { isPassing, isThresholdReached, isTie } = isProposalPassing(proposalTally); - if (isThresholdReached) { - if (isTie) { - status = 'Tie'; - } else { - status = isPassing ? 'Passing' : 'Failing'; - } - } - } - return ( - handleRowClick(proposal)} - className="group text-black dark:text-white rounded-lg cursor-pointer" - > - - - - - - - ); - })} - -
- ID - - Title - - Time Left - - Type - - Status -
- {proposal.id.toString()} - - {proposal.title} - - {timeLeft} - - {proposal.messages.length > 0 - ? proposal.messages.map((message, index) => ( -
{getHumanReadableType((message as any)['@type'])}
- )) - : 'No messages'} -
- {isTalliesLoading ? ( - - ) : ( - status - )} -
- ) : ( -
- No proposal was found. -
- )} -
- - - -
-
- - {/* Modals */} - -
- ); -} diff --git a/components/groups/components/index.tsx b/components/groups/components/index.tsx index 52b8405c..23338717 100644 --- a/components/groups/components/index.tsx +++ b/components/groups/components/index.tsx @@ -1,5 +1,5 @@ export * from './CountdownTimer'; export * from '../modals/groupInfo'; -export * from './groupProposals'; +export * from './groupControls'; export * from './myGroups'; export * from '../../react/StepIndicator'; diff --git a/components/groups/components/myGroups.tsx b/components/groups/components/myGroups.tsx index 9a046c76..d42f70c5 100644 --- a/components/groups/components/myGroups.tsx +++ b/components/groups/components/myGroups.tsx @@ -1,14 +1,21 @@ -import { ExtendedGroupType, ExtendedQueryGroupsByMemberResponseSDKType } from '@/hooks/useQueries'; +import { + ExtendedGroupType, + ExtendedQueryGroupsByMemberResponseSDKType, + useGetFilteredTxAndSuccessfulProposals, + useTokenBalances, + useTokenBalancesResolved, + useTokenFactoryDenomsMetadata, +} from '@/hooks/useQueries'; import ProfileAvatar from '@/utils/identicon'; -import { truncateString } from '@/utils'; -import React, { useState, useEffect } from 'react'; +import { CombinedBalanceInfo, MFX_TOKEN_DATA, truncateString } from '@/utils'; +import React, { useState, useEffect, useMemo } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/router'; import { ProposalSDKType, ThresholdDecisionPolicySDKType, } from '@liftedinit/manifestjs/dist/codegen/cosmos/group/v1/types'; -import GroupProposals from './groupProposals'; +import GroupProposals from './groupControls'; import { useBalance } from '@/hooks/useQueries'; import { shiftDigits } from '@/utils'; import { TruncatedAddressWithCopy } from '@/components/react/addressCopy'; @@ -19,6 +26,8 @@ import { GroupInfo } from '../modals/groupInfo'; import { MemberManagementModal } from '../modals/memberManagementModal'; import { useChain } from '@cosmos-kit/react'; import useIsMobile from '@/hooks/useIsMobile'; +import { chainName } from '@/config'; +import { useEndpointStore } from '@/store/endpointStore'; export function YourGroups({ groups, @@ -92,6 +101,79 @@ export function YourGroups({ router.push('/groups', undefined, { shallow: true }); }; + const { balances, isBalancesLoading, refetchBalances } = useTokenBalances( + selectedGroup?.policyAddress ?? '' + ); + const { + balances: resolvedBalances, + isBalancesLoading: resolvedLoading, + refetchBalances: resolveRefetch, + } = useTokenBalancesResolved(address ?? ''); + + const { selectedEndpoint } = useEndpointStore(); + const indexerUrl = selectedEndpoint?.indexer || ''; + + const { metadatas, isMetadatasLoading } = useTokenFactoryDenomsMetadata(); + const [currentPageGroupInfo, setCurrentPageGroupInfo] = useState(1); + + const pageSizeGroupInfo = isMobile ? 4 : 4; + const pageSizeHistory = isMobile ? 4 : 3; + const skeletonGroupCount = 1; + const skeletonTxCount = isMobile ? 5 : 3; + + const { + sendTxs, + totalPages: totalPagesGroupInfo, + isLoading: txLoading, + isError, + refetch: refetchHistory, + } = useGetFilteredTxAndSuccessfulProposals( + indexerUrl, + selectedGroup?.policyAddress ?? '', + currentPageGroupInfo, + pageSizeHistory + ); + + const combinedBalances = useMemo(() => { + if (!balances || !resolvedBalances || !metadatas) return []; + + // Find 'umfx' balance (mfx token) + const mfxCoreBalance = balances.find(b => b.denom === 'umfx'); + const mfxResolvedBalance = resolvedBalances.find(rb => rb.denom === 'mfx'); + + // Create combined balance for 'mfx' + const mfxCombinedBalance: CombinedBalanceInfo | null = mfxCoreBalance + ? { + denom: mfxResolvedBalance?.denom || 'mfx', + coreDenom: 'umfx', + amount: mfxCoreBalance.amount, + metadata: MFX_TOKEN_DATA, + } + : null; + + // Process other balances + const otherBalances = balances + .filter(coreBalance => coreBalance.denom !== 'umfx') + .map((coreBalance): CombinedBalanceInfo => { + const resolvedBalance = resolvedBalances.find( + rb => rb.denom === coreBalance.denom || rb.denom === coreBalance.denom.split('/').pop() + ); + const metadata = metadatas.metadatas.find(m => m.base === coreBalance.denom); + + return { + denom: resolvedBalance?.denom || coreBalance.denom, + coreDenom: coreBalance.denom, + amount: coreBalance.amount, + metadata: metadata || null, + }; + }); + + // Combine 'mfx' with other balances + return mfxCombinedBalance ? [mfxCombinedBalance, ...otherBalances] : otherBalances; + }, [balances, resolvedBalances, metadatas]); + + const isLoadingGroupInfo = isBalancesLoading || resolvedLoading || isMetadatasLoading; + return (
)}
diff --git a/next.config.js b/next.config.js index d5266d1e..21926990 100644 --- a/next.config.js +++ b/next.config.js @@ -3,10 +3,6 @@ const nextConfig = { output: 'standalone', reactStrictMode: true, swcMinify: true, - // TODO: Remove this when we are ready for prod - typescript: { - ignoreBuildErrors: true, - }, images: { domains: [ 'imgur.com', diff --git a/pages/bank.tsx b/pages/bank.tsx index fe631302..b7391304 100644 --- a/pages/bank.tsx +++ b/pages/bank.tsx @@ -1,6 +1,4 @@ import { WalletNotConnected } from '@/components'; - -import TokenList from '@/components/bank/components/tokenList'; import { chainName } from '@/config'; import { useGetFilteredTxAndSuccessfulProposals, @@ -13,7 +11,7 @@ import { import { useChain } from '@cosmos-kit/react'; import Head from 'next/head'; import React, { useMemo, useState } from 'react'; -import { HistoryBox } from '@/components'; +import { HistoryBox, TokenList } from '@/components'; import { BankIcon } from '@/components/icons'; import { CombinedBalanceInfo } from '@/utils/types'; import { MFX_TOKEN_DATA } from '@/utils/constants'; @@ -38,6 +36,9 @@ export default function Bank() { const pageSize = isMobile ? 4 : 9; + const skeletonGroupCount = 1; + const skeletonTxCount = isMobile ? 5 : 9; + const { sendTxs, totalPages, @@ -156,6 +157,7 @@ export default function Bank() { balances={combinedBalances} refetchHistory={refetchHistory} address={address ?? ''} + pageSize={pageSize} />
@@ -169,6 +171,8 @@ export default function Bank() { txLoading={txLoading} isError={isError} refetch={refetchHistory} + skeletonGroupCount={skeletonGroupCount} + skeletonTxCount={skeletonTxCount} />
diff --git a/pages/groups/index.tsx b/pages/groups/index.tsx index 482d569a..72d62356 100644 --- a/pages/groups/index.tsx +++ b/pages/groups/index.tsx @@ -82,7 +82,7 @@ export default function Groups() { groups={groupByMemberData ?? { groups: [] }} proposals={proposalsByPolicyAccount} isLoading={isLoading} - refetch={refetchGroupByMember} + refetch={refetchGroupByMember || refetchProposals} /> ) : isError ? (
Error loading groups or proposals
@@ -94,7 +94,7 @@ export default function Groups() { groups={groupByMemberData ?? { groups: [] }} proposals={proposalsByPolicyAccount} isLoading={isLoading} - refetch={refetchGroupByMember} + refetch={refetchGroupByMember || refetchProposals} /> {selectedPolicyAddress && ( Date: Sat, 7 Dec 2024 15:06:24 -0700 Subject: [PATCH 2/8] fix: finish the group and modal rework --- components/admins/modals/warningModal.tsx | 2 +- components/bank/components/historyBox.tsx | 5 +- .../groups/components/groupControls.tsx | 68 ++- components/groups/components/myGroups.tsx | 39 +- components/groups/modals/groupInfo.tsx | 85 +++- .../groups/modals/memberManagementModal.tsx | 398 ++++++++++-------- components/groups/modals/updateGroupModal.tsx | 73 +++- components/groups/modals/voteDetailsModal.tsx | 201 ++++++--- components/react/views/Contacts.tsx | 14 +- pages/groups/index.tsx | 13 - 10 files changed, 575 insertions(+), 323 deletions(-) diff --git a/components/admins/modals/warningModal.tsx b/components/admins/modals/warningModal.tsx index 95e8cb20..3cc71e05 100644 --- a/components/admins/modals/warningModal.tsx +++ b/components/admins/modals/warningModal.tsx @@ -4,7 +4,7 @@ import { cosmos, strangelove_ventures } from '@liftedinit/manifestjs'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; import { MsgRemoveValidator } from '@liftedinit/manifestjs/dist/codegen/strangelove_ventures/poa/v1/tx'; import { useChain } from '@cosmos-kit/react'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { PiWarning } from 'react-icons/pi'; import { createPortal } from 'react-dom'; diff --git a/components/bank/components/historyBox.tsx b/components/bank/components/historyBox.tsx index 60465555..c13b75e9 100644 --- a/components/bank/components/historyBox.tsx +++ b/components/bank/components/historyBox.tsx @@ -168,9 +168,12 @@ export function HistoryBox({ return (
-

+

{isGroup ? 'Group Transactions' : 'Transaction History'}

+

+ {isGroup ? 'Transactions' : 'History'} +

{totalPages > 1 && (
diff --git a/components/groups/components/groupControls.tsx b/components/groups/components/groupControls.tsx index ee9c64b7..c42df4b5 100644 --- a/components/groups/components/groupControls.tsx +++ b/components/groups/components/groupControls.tsx @@ -65,6 +65,7 @@ export default function GroupProposals({ const [selectedProposal, setSelectedProposal] = useState(null); const [members, setMembers] = useState([]); + const [showVoteModal, setShowVoteModal] = useState(false); const [searchTerm, setSearchTerm] = useState(''); @@ -91,12 +92,7 @@ export default function GroupProposals({ const proposalToOpen = proposals.find(p => p.id.toString() === proposalId); if (proposalToOpen) { setSelectedProposal(proposalToOpen); - setTimeout(() => { - const modal = document.getElementById(`vote_modal_${proposalId}`) as HTMLDialogElement; - if (modal) { - modal.showModal(); - } - }, 0); + setShowVoteModal(true); } else { console.warn(`Proposal with ID ${proposalId} not found`); // remove the invalid proposalId from the URL @@ -108,22 +104,16 @@ export default function GroupProposals({ const handleRowClick = (proposal: ProposalSDKType) => { setSelectedProposal(proposal); + setShowVoteModal(true); // Update URL without navigating router.push(`/groups?policyAddress=${policyAddress}&proposalId=${proposal.id}`, undefined, { shallow: true, }); - setTimeout(() => { - const modal = document.getElementById(`vote_modal_${proposal.id}`) as HTMLDialogElement; - if (modal) { - modal.showModal(); - } else { - console.error(`Modal not found for proposal ${proposal.id}`); - } - }, 0); }; - const closeModal = () => { + const handleCloseVoteModal = () => { setSelectedProposal(null); + setShowVoteModal(false); // Remove proposalId from URL when closing the modal router.push(`/groups?policyAddress=${policyAddress}`, undefined, { shallow: true }); }; @@ -241,7 +231,7 @@ export default function GroupProposals({ const { tallies, isLoading: isTalliesLoading } = useMultipleTallyCounts(proposals.map(p => p.id)); return ( -
+
{/* Header section */} @@ -310,19 +300,19 @@ export default function GroupProposals({ Title Time Left Type Status @@ -383,13 +373,15 @@ export default function GroupProposals({ {proposal.id.toString()} - + {proposal.title} - + {timeLeft} - + {proposal.messages.length > 0 ? proposal.messages.map((message, index) => (
@@ -398,7 +390,7 @@ export default function GroupProposals({ )) : 'No messages'} - + {isTalliesLoading ? ( ) : ( @@ -418,8 +410,8 @@ export default function GroupProposals({
-
-
+
+
{/* Modals */} - + {selectedProposal && ( + + )}
); } diff --git a/components/groups/components/myGroups.tsx b/components/groups/components/myGroups.tsx index d42f70c5..56ee0291 100644 --- a/components/groups/components/myGroups.tsx +++ b/components/groups/components/myGroups.tsx @@ -174,8 +174,11 @@ export function YourGroups({ const isLoadingGroupInfo = isBalancesLoading || resolvedLoading || isMetadatasLoading; + const [activeInfoModalId, setActiveInfoModalId] = useState(null); + const [activeMemberModalId, setActiveMemberModalId] = useState(null); + return ( -
+
))} @@ -398,6 +405,8 @@ export function YourGroups({ address={address ?? ''} policyAddress={group.policies[0]?.address ?? ''} onUpdate={refetch} + showInfoModal={activeInfoModalId === group.id.toString()} + setShowInfoModal={show => setActiveInfoModalId(show ? group.id.toString() : null)} /> {}} + onUpdate={refetch} + setShowMemberManagementModal={show => + setActiveMemberModalId(show ? group.id.toString() : null) + } + showMemberManagementModal={activeMemberModalId === group.id.toString()} /> ))} @@ -428,10 +441,18 @@ function GroupRow({ group, proposals, onSelectGroup, + activeMemberModalId, + setActiveMemberModalId, + activeInfoModalId, + setActiveInfoModalId, }: { group: ExtendedQueryGroupsByMemberResponseSDKType['groups'][0]; proposals: ProposalSDKType[]; onSelectGroup: (policyAddress: string, groupName: string, threshold: string) => void; + activeMemberModalId: string | null; + setActiveMemberModalId: (id: string | null) => void; + activeInfoModalId: string | null; + setActiveInfoModalId: (id: string | null) => void; }) { const policyAddress = (group.policies && group.policies[0]?.address) || ''; const groupName = group.ipfsMetadata?.title || 'Untitled Group'; @@ -449,22 +470,12 @@ function GroupRow({ const openInfoModal = (e: React.MouseEvent) => { e.stopPropagation(); - const modal = document.getElementById( - `group-info-modal-${group.id}` - ) as HTMLDialogElement | null; - if (modal) { - modal.showModal(); - } + setActiveInfoModalId(group.id.toString()); }; const openMemberModal = (e: React.MouseEvent) => { e.stopPropagation(); - const modal = document.getElementById( - `member-management-modal-${group.id}` - ) as HTMLDialogElement | null; - if (modal) { - modal.showModal(); - } + setActiveMemberModalId(group.id.toString()); }; return ( diff --git a/components/groups/modals/groupInfo.tsx b/components/groups/modals/groupInfo.tsx index a0de7fe9..4d68b59c 100644 --- a/components/groups/modals/groupInfo.tsx +++ b/components/groups/modals/groupInfo.tsx @@ -6,15 +6,39 @@ import { ThresholdDecisionPolicySDKType } from '@liftedinit/manifestjs/dist/code import { useFeeEstimation, useTx } from '@/hooks'; import { chainName } from '@/config'; import { cosmos } from '@liftedinit/manifestjs'; +import { useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; + interface GroupInfoProps { modalId: string; group: ExtendedGroupType | null; policyAddress: string; address: string; onUpdate: () => void; + showInfoModal: boolean; + setShowInfoModal: (show: boolean) => void; } -export function GroupInfo({ modalId, group, policyAddress, address, onUpdate }: GroupInfoProps) { +export function GroupInfo({ + modalId, + group, + policyAddress, + address, + onUpdate, + showInfoModal, + setShowInfoModal, +}: GroupInfoProps) { + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && showInfoModal) { + setShowInfoModal(false); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [showInfoModal, setShowInfoModal]); + const [showUpdateModal, setShowUpdateModal] = useState(false); const { tx, isSigning, setIsSigning } = useTx(chainName); const { leaveGroup } = cosmos.group.v1.MessageComposer.withTypeUrl; const { estimateFee } = useFeeEstimation(chainName); @@ -113,8 +137,7 @@ export function GroupInfo({ modalId, group, policyAddress, address, onUpdate }: onSuccess: () => { setIsSigning(false); onUpdate(); - const modal = document.getElementById(modalId) as HTMLDialogElement; - if (modal) modal.close(); + setShowInfoModal(false); }, }); } catch (error) { @@ -124,8 +147,27 @@ export function GroupInfo({ modalId, group, policyAddress, address, onUpdate }: } }; - return ( - + const modalContent = ( +
@@ -133,7 +175,10 @@ export function GroupInfo({ modalId, group, policyAddress, address, onUpdate }:

{group.ipfsMetadata?.title ?? 'No title'}

-
@@ -154,10 +199,7 @@ export function GroupInfo({ modalId, group, policyAddress, address, onUpdate }: @@ -181,7 +223,20 @@ export function GroupInfo({ modalId, group, policyAddress, address, onUpdate }: {renderAuthors()}
-
+ setShowInfoModal(false)} + >
); + + if (typeof document !== 'undefined') { + return createPortal(modalContent, document.body); + } + + return null; } function InfoItem({ diff --git a/components/groups/modals/memberManagementModal.tsx b/components/groups/modals/memberManagementModal.tsx index edcf101b..4da59d3e 100644 --- a/components/groups/modals/memberManagementModal.tsx +++ b/components/groups/modals/memberManagementModal.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import { Formik, Form, Field, FieldProps } from 'formik'; +import { createPortal } from 'react-dom'; import * as Yup from 'yup'; import { cosmos } from '@liftedinit/manifestjs'; @@ -24,6 +25,8 @@ interface MemberManagementModalProps { policyAddress: string; address: string; onUpdate: () => void; + setShowMemberManagementModal: (show: boolean) => void; + showMemberManagementModal: boolean; } export function MemberManagementModal({ @@ -34,7 +37,19 @@ export function MemberManagementModal({ policyAddress, address, onUpdate, + setShowMemberManagementModal, + showMemberManagementModal, }: MemberManagementModalProps) { + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && showMemberManagementModal) { + setShowMemberManagementModal(false); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [showMemberManagementModal]); const { tx, isSigning, setIsSigning } = useTx(chainName); const { estimateFee } = useFeeEstimation(chainName); @@ -171,185 +186,224 @@ export function MemberManagementModal({ const submitFormRef = useRef<(() => void) | null>(null); - return ( - -
-
-
- -
-
-

Members

- -
+ const modalContent = ( + +
+ - +

Members

+ +
+ + + {({ values, isValid, setFieldValue, handleSubmit, touched }) => { + submitFormRef.current = handleSubmit; + return ( + <> +
+ { + if (activeIndex !== null) { + const fieldName = `members.${activeIndex}.address`; + setFieldValue(fieldName, selectedAddress); + } + setIsContactsOpen(false); + (document.getElementById(modalId) as HTMLDialogElement)?.close(); + }} + currentAddress={address} + /> +
+
+
+
#
+
Name
+
Address
+
- -
-
#
-
Name
-
Address
-
-
-
- {values.members.map((member, index) => ( -
-
{index + 1}
- {/* Name Field with Daisy UI Tooltip */} -
- - {({ field, meta }: FieldProps) => ( -
- - {meta.touched && meta.error && ( -
- {/* Invisible element to anchor the tooltip */} -
-
- )} -
- )} -
-
- {/* Address Field with Daisy UI Tooltip */} -
- - {({ field, meta }: FieldProps) => ( -
- - {member.isNew && !member.markedForDeletion && ( - - )} - {meta.touched && meta.error && ( -
-
-
- )} -
- )} -
- -
-
- -
-
- ))} -
-
- - -
-
- - ); - }} -
-
+
{index + 1}
+ {/* Name Field with Daisy UI Tooltip */} +
+ + {({ field, meta }: FieldProps) => ( +
+ + {meta.touched && meta.error && ( +
+ {/* Invisible element to anchor the tooltip */} +
+
+ )} +
+ )} +
+
+ {/* Address Field with Daisy UI Tooltip */} +
+ + {({ field, meta }: FieldProps) => ( +
+ + {member.isNew && !member.markedForDeletion && ( + + )} + {meta.touched && meta.error && ( +
+
+
+ )} +
+ )} +
+ +
+
+ +
+
+ ))} +
+
+ + +
+ + + ); + }} +
-
+ setShowMemberManagementModal(false)} + >
); + + // Only render if we're in the browser + if (typeof document !== 'undefined') { + return createPortal(modalContent, document.body); + } + + return null; } diff --git a/components/groups/modals/updateGroupModal.tsx b/components/groups/modals/updateGroupModal.tsx index 51e93a2e..41eb86e8 100644 --- a/components/groups/modals/updateGroupModal.tsx +++ b/components/groups/modals/updateGroupModal.tsx @@ -12,17 +12,22 @@ import { import { cosmos } from '@liftedinit/manifestjs'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; import { ExtendedGroupType } from '@/hooks'; +import { createPortal } from 'react-dom'; export function UpdateGroupModal({ group, policyAddress, address, onUpdate, + showUpdateModal, + setShowUpdateModal, }: { group: ExtendedGroupType; policyAddress: string; address: string; onUpdate: () => void; + showUpdateModal: boolean; + setShowUpdateModal: (show: boolean) => void; }) { const { tx, isSigning, setIsSigning } = useTx(chainName); const { estimateFee } = useFeeEstimation(chainName); @@ -296,8 +301,38 @@ export function UpdateGroupModal({ return hasMetadataChanges || hasThresholdChange || hasVotingPeriodChange; }; - return ( - + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && showUpdateModal) { + e.stopPropagation(); + setShowUpdateModal(false); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [showUpdateModal, setShowUpdateModal]); + + const modalContent = ( + {({ setFieldValue, values, isValid, dirty, errors, touched }) => ( <> -
-
-
- -
+
e.stopPropagation()} + > +
+

Update Group

@@ -472,12 +511,22 @@ export function UpdateGroupModal({
{/* Action buttons */}
- - - +
setShowUpdateModal(false)} + aria-hidden="true" + /> )}
); + + // Only render if we're in the browser and modal is shown + if (typeof document !== 'undefined' && showUpdateModal) { + return createPortal(modalContent, document.body); + } + + return null; } diff --git a/components/groups/modals/voteDetailsModal.tsx b/components/groups/modals/voteDetailsModal.tsx index 0e91bced..1d278d11 100644 --- a/components/groups/modals/voteDetailsModal.tsx +++ b/components/groups/modals/voteDetailsModal.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState, useMemo } from 'react'; import dynamic from 'next/dynamic'; +import { createPortal } from 'react-dom'; import { MemberSDKType, @@ -32,27 +33,31 @@ interface VoteMap { [key: string]: VoteOption; } +interface VoteDetailsModalProps { + tallies: QueryTallyResultResponseSDKType; + votes: VoteSDKType[]; + members: MemberSDKType[]; + proposal: ProposalSDKType; + showVoteModal: boolean; + setShowVoteModal: (show: boolean) => void; + onClose: () => void; + refetchVotes: () => void; + refetchTally: () => void; + refetchProposals: () => void; +} + function VoteDetailsModal({ tallies, votes, members, proposal, + showVoteModal, + setShowVoteModal, onClose, - modalId, refetchVotes, refetchTally, refetchProposals, -}: { - tallies: QueryTallyResultResponseSDKType; - votes: VoteSDKType[]; - members: MemberSDKType[]; - proposal: ProposalSDKType; - onClose: () => void; - modalId: string; - refetchVotes: () => void; - refetchTally: () => void; - refetchProposals: () => void; -}) { +}: VoteDetailsModalProps) { const voteMap = useMemo( () => votes?.reduce((acc, vote) => { @@ -444,12 +449,47 @@ function VoteDetailsModal({ } }; - return ( - <> - -
+ useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && showVoteModal) { + e.stopPropagation(); + setShowVoteModal(false); + onClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [showVoteModal, setShowVoteModal, onClose]); + + const modalContent = ( + +
+
e.stopPropagation()} + >
-
@@ -681,7 +721,64 @@ function VoteDetailsModal({ )}
- + +
+
+ +
+

Proposal Messages

+
+ {proposal?.messages?.map((message: any, index: number) => { + const messageType = message['@type']; + const fieldsToShow = importantFields[messageType] || defaultFields; + + return ( +
+

+ {messageType.split('.').pop().replace('Msg', '')} +

+
+ {fieldsToShow.map(field => renderMessageField(field, message[field]))} +
+
+ ); + })} +
+
+
{ + e.preventDefault(); + setShowVoteModal(false); + onClose(); + }} + > + +
+
{ @@ -692,39 +789,43 @@ function VoteDetailsModal({ />
-
- -
-
- - -
-
- -
-

Proposal Messages

-
- {proposal?.messages?.map((message: any, index: number) => { - const messageType = message['@type']; - const fieldsToShow = importantFields[messageType] || defaultFields; - - return ( -
-

- {messageType.split('.').pop().replace('Msg', '')} -

-
{fieldsToShow.map(field => renderMessageField(field, message[field]))}
-
- ); - })} -
-
-
- -
-
- +
+
{ + e.preventDefault(); + setShowVoteModal(false); + onClose(); + }} + > + +
+ ); + + if (typeof document !== 'undefined' && showVoteModal) { + return createPortal(modalContent, document.body); + } + + return null; } export default VoteDetailsModal; diff --git a/components/react/views/Contacts.tsx b/components/react/views/Contacts.tsx index 0ca63ad4..6513027a 100644 --- a/components/react/views/Contacts.tsx +++ b/components/react/views/Contacts.tsx @@ -18,7 +18,7 @@ export const Contacts = ({ selectionMode = false, onSelect, currentAddress, - showMemberManagementModal = false, + showMessageEditModal = false, }: { onClose: () => void; @@ -26,7 +26,7 @@ export const Contacts = ({ selectionMode?: boolean; onSelect?: (address: string) => void; currentAddress?: string; - showMemberManagementModal?: boolean; + showMessageEditModal?: boolean; }) => { const { contacts, addContact, updateContact, removeContact, importContacts, exportContacts } = @@ -221,11 +221,6 @@ export const Contacts = ({ if (onSelect) { onSelect(currentAddress); onClose(); - if (showMemberManagementModal) { - ( - document.getElementById('member-management-modal') as HTMLDialogElement - ).showModal(); - } if (showMessageEditModal) { (document.getElementById('message_edit_modal') as HTMLDialogElement).showModal(); } @@ -323,11 +318,6 @@ export const Contacts = ({ if (selectionMode && onSelect) { onSelect(contact.address); onClose(); - if (showMemberManagementModal) { - ( - document.getElementById('member-management-modal') as HTMLDialogElement - ).showModal(); - } if (showMessageEditModal) { ( document.getElementById('message_edit_modal') as HTMLDialogElement diff --git a/pages/groups/index.tsx b/pages/groups/index.tsx index 72d62356..2257d138 100644 --- a/pages/groups/index.tsx +++ b/pages/groups/index.tsx @@ -96,19 +96,6 @@ export default function Groups() { isLoading={isLoading} refetch={refetchGroupByMember || refetchProposals} /> - {selectedPolicyAddress && ( - g.policies[0]?.address === selectedPolicyAddress - ) ?? null - } - address={address ?? ''} - onUpdate={refetchGroupByMember} - /> - )} )}
From b951d326160e1a955ce040ca031372dd77666af0 Mon Sep 17 00:00:00 2001 From: Joseph Chalabi Date: Sun, 8 Dec 2024 17:20:51 -0700 Subject: [PATCH 3/8] feat: add mint/burn mfx and upgrade flows to admin --- bun.lockb | Bin 545012 -> 558900 bytes .../admins/components/chainUpgrader.tsx | 56 +++ components/admins/components/index.tsx | 2 + .../admins/components/stakeHolderPayout.tsx | 50 +++ .../admins/components/validatorList.tsx | 104 ++++- .../admins/modals/cancelUpgradeModal.tsx | 177 ++++++++ components/admins/modals/index.tsx | 2 + components/admins/modals/upgradeModal.tsx | 379 ++++++++++++++++++ components/bank/components/historyBox.tsx | 35 +- components/bank/components/tokenList.tsx | 12 +- components/factory/components/MyDenoms.tsx | 64 ++- components/factory/forms/BurnForm.tsx | 4 +- components/factory/modals/index.ts | 1 + .../factory/modals/multiMfxBurnModal.tsx | 121 ++++-- .../factory/modals/multiMfxMintModal.tsx | 97 +++-- .../groups/components/groupControls.tsx | 2 +- components/groups/components/myGroups.tsx | 11 +- components/groups/modals/voteDetailsModal.tsx | 1 + components/groups/modals/voteModal.tsx | 14 +- hooks/useQueries.ts | 127 +++++- package.json | 1 + pages/admins.tsx | 41 +- pages/groups/index.tsx | 2 - tailwind.config.js | 19 + 24 files changed, 1174 insertions(+), 148 deletions(-) create mode 100644 components/admins/components/chainUpgrader.tsx create mode 100644 components/admins/components/stakeHolderPayout.tsx create mode 100644 components/admins/modals/cancelUpgradeModal.tsx create mode 100644 components/admins/modals/upgradeModal.tsx diff --git a/bun.lockb b/bun.lockb index f882fe4b37d8b63ee2649130a61b118e3c254803..b99d9c6b928e3b93c1d997453c7a048a1519ebc7 100755 GIT binary patch delta 116555 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 CJHlN6 delta 106835 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 { + const [isOpen, setIsOpen] = useState(false); + const [isCancelOpen, setIsCancelOpen] = useState(false); + const { plan } = useCurrentPlan(); + + return ( +
+
+

+ {plan ? 'Upgrade in progress' : 'No upgrade in progress'} +

+
+
+

Submit Chain Upgrade Proposal

+

+ Submit a proposal to instantiate a chain upgrade. +

+
+ +
+ + +
+ setIsOpen(false)} + /> + setIsCancelOpen(false)} + /> +
+ ); +}; diff --git a/components/admins/components/index.tsx b/components/admins/components/index.tsx index bb4c3844..4b418158 100644 --- a/components/admins/components/index.tsx +++ b/components/admins/components/index.tsx @@ -1 +1,3 @@ export * from './validatorList'; +export * from './stakeHolderPayout'; +export * from './chainUpgrader'; diff --git a/components/admins/components/stakeHolderPayout.tsx b/components/admins/components/stakeHolderPayout.tsx new file mode 100644 index 00000000..67d6c1af --- /dev/null +++ b/components/admins/components/stakeHolderPayout.tsx @@ -0,0 +1,50 @@ +import { MultiMintModal, MultiBurnModal } from '@/components/factory/modals'; +import { MFX_TOKEN_DATA } from '@/utils/constants'; +import { useState } from 'react'; + +interface StakeHolderPayoutProps { + admin: string; + address: string; +} + +export const StakeHolderPayout = ({ admin, address }: StakeHolderPayoutProps) => { + const [isOpenMint, setIsOpenMint] = useState(false); + const [isOpenBurn, setIsOpenBurn] = useState(false); + + return ( +
+
+

Stake Holder Payout

+

+ Burn MFX or mint MFX to pay out stake holders. +

+
+ +
+ + +
+ setIsOpenMint(false)} + admin={admin} + address={address} + denom={MFX_TOKEN_DATA} + /> + setIsOpenBurn(false)} + admin={admin} + address={address} + denom={MFX_TOKEN_DATA} + /> +
+ ); +}; diff --git a/components/admins/components/validatorList.tsx b/components/admins/components/validatorList.tsx index 3639aee2..cac4c7a9 100644 --- a/components/admins/components/validatorList.tsx +++ b/components/admins/components/validatorList.tsx @@ -6,6 +6,8 @@ import ProfileAvatar from '@/utils/identicon'; import Image from 'next/image'; import { TruncatedAddressWithCopy } from '@/components/react/addressCopy'; import { SearchIcon, TrashIcon } from '@/components/icons'; +import useIsMobile from '@/hooks/useIsMobile'; + export interface ExtendedValidatorSDKType extends ValidatorSDKType { consensus_power?: bigint; logo_url?: string; @@ -46,6 +48,11 @@ export default function ValidatorList({ })) : []; + const [currentPage, setCurrentPage] = useState(1); + const isMobile = useIsMobile(); + + const pageSize = isMobile ? 4 : 5; + const filteredValidators = useMemo(() => { const validators = active ? activeValidators : pendingValidators; return validators.filter(validator => @@ -53,6 +60,12 @@ export default function ValidatorList({ ); }, [active, activeValidators, pendingValidators, searchTerm]); + const totalPages = Math.ceil(filteredValidators.length / pageSize); + const paginatedValidators = filteredValidators.slice( + (currentPage - 1) * pageSize, + currentPage * pageSize + ); + const handleRemove = (validator: ExtendedValidatorSDKType) => { setValidatorToRemove(validator); setOpenWarningModal(true); @@ -68,7 +81,7 @@ export default function ValidatorList({ }; return ( -
+
@@ -124,10 +137,10 @@ export default function ValidatorList({ Moniker - + Address - + Consensus Power @@ -140,20 +153,20 @@ export default function ValidatorList({ .fill(0) .map((_, index) => ( - +
-
-
+
+
- -
+ +
- -
+ +
- -
+ +
))} @@ -186,7 +199,7 @@ export default function ValidatorList({ - {filteredValidators.map(validator => ( + {paginatedValidators.map(validator => ( + + {totalPages > 1 && ( +
e.stopPropagation()} + role="navigation" + aria-label="Pagination" + > + + + {[...Array(totalPages)].map((_, index) => { + const pageNum = index + 1; + if ( + pageNum === 1 || + pageNum === totalPages || + (pageNum >= currentPage - 1 && pageNum <= currentPage + 1) + ) { + return ( + + ); + } else if (pageNum === currentPage - 2 || pageNum === currentPage + 2) { + return ( + + ); + } + return null; + })} + + +
+ )}
); } diff --git a/components/admins/modals/cancelUpgradeModal.tsx b/components/admins/modals/cancelUpgradeModal.tsx new file mode 100644 index 00000000..7aac4ca8 --- /dev/null +++ b/components/admins/modals/cancelUpgradeModal.tsx @@ -0,0 +1,177 @@ +import React, { useEffect } from 'react'; +import { createPortal } from 'react-dom'; +import { cosmos } from '@liftedinit/manifestjs'; +import { useFeeEstimation, useTx } from '@/hooks'; +import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; +import { chainName } from '@/config'; +import { MsgCancelUpgrade } from '@liftedinit/manifestjs/dist/codegen/cosmos/upgrade/v1beta1/tx'; +import { PlanSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/upgrade/v1beta1/upgrade'; + +interface BaseModalProps { + isOpen: boolean; + onClose: () => void; + admin: string; + address: string | null; + plan: PlanSDKType; +} + +function InfoItem({ label, value }: { label: string; value?: string | number | bigint }) { + return ( +
+ {label} +
+ {value?.toString() || 'N/A'} +
+
+ ); +} + +export function CancelUpgradeModal({ isOpen, onClose, admin, address, plan }: BaseModalProps) { + const { cancelUpgrade } = cosmos.upgrade.v1beta1.MessageComposer.withTypeUrl; + const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; + const { tx, isSigning, setIsSigning } = useTx(chainName); + const { estimateFee } = useFeeEstimation(chainName); + + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && isOpen) { + onClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [isOpen, onClose]); + + const handleCancelUpgrade = async () => { + setIsSigning(true); + try { + const msgUpgrade = cancelUpgrade({ + authority: admin, + }); + + const anyMessage = Any.fromPartial({ + typeUrl: msgUpgrade.typeUrl, + value: MsgCancelUpgrade.encode(msgUpgrade.value).finish(), + }); + + const groupProposalMsg = submitProposal({ + groupPolicyAddress: admin, + messages: [anyMessage], + metadata: '', + proposers: [address ?? ''], + title: `Cancel Upgrade`, + summary: `This proposal will cancel the upgrade`, + exec: 0, + }); + + const fee = await estimateFee(address ?? '', [groupProposalMsg]); + await tx([groupProposalMsg], { + fee, + onSuccess: () => { + setIsSigning(false); + onClose(); + }, + }); + } catch (error) { + console.error('Error canceling upgrade:', error); + } finally { + setIsSigning(false); + } + }; + + const modalContent = ( + +
+
+

Cancel Upgrade

+
+ +
+
+ + {plan && ( +
+

+ Current Upgrade Plan +

+
+ + + + +
+
+ )} + +
+ + +
+
+
+ +
+
+ ); + + if (typeof document !== 'undefined') { + return createPortal(modalContent, document.body); + } + + return null; +} diff --git a/components/admins/modals/index.tsx b/components/admins/modals/index.tsx index 89a638a7..6ebbb987 100644 --- a/components/admins/modals/index.tsx +++ b/components/admins/modals/index.tsx @@ -1,2 +1,4 @@ export * from './validatorModal'; export * from './warningModal'; +export * from './upgradeModal'; +export * from './cancelUpgradeModal'; diff --git a/components/admins/modals/upgradeModal.tsx b/components/admins/modals/upgradeModal.tsx new file mode 100644 index 00000000..5dc9b200 --- /dev/null +++ b/components/admins/modals/upgradeModal.tsx @@ -0,0 +1,379 @@ +import React, { useEffect, useState, useMemo } from 'react'; +import { createPortal } from 'react-dom'; +import { cosmos } from '@liftedinit/manifestjs'; +import { useTx, useFeeEstimation, useGitHubReleases, GitHubRelease } from '@/hooks'; +import { chainName } from '@/config'; +import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; +import { MsgSoftwareUpgrade } from '@liftedinit/manifestjs/dist/codegen/cosmos/upgrade/v1beta1/tx'; +import { Formik, Form } from 'formik'; +import Yup from '@/utils/yupExtensions'; +import { TextInput } from '@/components/react/inputs'; +import { PiCaretDownBold } from 'react-icons/pi'; +import { SearchIcon } from '@/components/icons'; + +interface BaseModalProps { + isOpen: boolean; + onClose: () => void; + admin: string; + address: string; +} + +interface UpgradeInfo { + name: string; + upgradeable: boolean; + commitHash: string; +} + +const mockRelease = { + url: 'https://api.github.com/repos/liftedinit/manifest-ledger/releases/1', + assets_url: 'https://api.github.com/repos/liftedinit/manifest-ledger/releases/1/assets', + upload_url: + 'https://uploads.github.com/repos/liftedinit/manifest-ledger/releases/1/assets{?name,label}', + html_url: 'https://github.com/liftedinit/manifest-ledger/releases/tag/v1.2.0', + id: 123456789, + node_id: 'MDg6UmVsZWFzZTEyMzQ1Njc4OQ==', + tag_name: 'v1.2.0', + target_commitish: 'abc123def456', // Commit hash for the release + name: 'v1.2.0 - Upgradeable Version', + draft: false, + prerelease: false, + created_at: '2024-12-01T10:00:00Z', + published_at: '2024-12-01T12:00:00Z', + body: '### Upgrade Details:\n- **Upgrade Handler Name**: `upgrade-v1.2.0`\n- **Upgradeable**: `true`\n- **Commit Hash**: `abc123def456`\n- **Description**: This version introduces an important upgrade to handle breaking changes in the system and adds new features for scalability.\n\n### Changelog:\n- Fixed critical bug in consensus mechanism.\n- Improved block processing time.\n- New features added for validator management.', + author: { + login: 'liftedinit', + id: 12345678, + node_id: 'MDQ6VXNlcjEyMzQ1Njc4', + avatar_url: 'https://avatars.githubusercontent.com/u/12345678?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/liftedinit', + html_url: 'https://github.com/liftedinit', + followers_url: 'https://api.github.com/users/liftedinit/followers', + following_url: 'https://api.github.com/users/liftedinit/following{/other_user}', + gists_url: 'https://api.github.com/users/liftedinit/gists{/gist_id}', + starred_url: 'https://api.github.com/users/liftedinit/starred{/owner}{/repo}', + subscriptions_url: 'https://api.github.com/users/liftedinit/subscriptions', + organizations_url: 'https://api.github.com/users/liftedinit/orgs', + repos_url: 'https://api.github.com/users/liftedinit/repos', + events_url: 'https://api.github.com/users/liftedinit/events{/privacy}', + received_events_url: 'https://api.github.com/users/liftedinit/received_events', + type: 'User', + site_admin: false, + }, + assets: [], + tarball_url: 'https://api.github.com/repos/liftedinit/manifest-ledger/tarball/v1.2.0', + zipball_url: 'https://api.github.com/repos/liftedinit/manifest-ledger/zipball/v1.2.0', +}; + +const parseReleaseBody = (body: string): UpgradeInfo | null => { + try { + const nameMatch = body.match(/\*\*Upgrade Handler Name\*\*:\s*`([^`]+)`/); + const upgradeableMatch = body.match(/\*\*Upgradeable\*\*:\s*`([^`]+)`/); + const commitHashMatch = body.match(/\*\*Commit Hash\*\*:\s*`([^`]+)`/); + + if (!nameMatch || !upgradeableMatch || !commitHashMatch) { + return null; + } + + return { + name: nameMatch[1], + upgradeable: upgradeableMatch[1].toLowerCase() === 'true', + commitHash: commitHashMatch[1], + }; + } catch (error) { + console.error('Error parsing release body:', error); + return null; + } +}; + +const UpgradeSchema = Yup.object().shape({ + height: Yup.string().required('Height is required').matches(/^\d+$/, 'Must be a valid number'), +}); + +export function UpgradeModal({ isOpen, onClose, admin, address }: BaseModalProps) { + const [searchTerm, setSearchTerm] = useState(''); + const { releases, isReleasesLoading } = useGitHubReleases(); + + // Filter releases that are upgradeable + const upgradeableReleases = useMemo(() => { + const allReleases = [...(releases || []), mockRelease]; // Remove mockRelease when going live + return allReleases + .map(release => ({ + ...release, + upgradeInfo: parseReleaseBody(release.body), + })) + .filter(release => release.upgradeInfo?.upgradeable); + }, [releases]); + + const filteredReleases = upgradeableReleases.filter(release => + release.tag_name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && isOpen) { + onClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [isOpen, onClose]); + + const { softwareUpgrade } = cosmos.upgrade.v1beta1.MessageComposer.withTypeUrl; + const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; + const { tx, isSigning, setIsSigning } = useTx(chainName); + const { estimateFee } = useFeeEstimation(chainName); + + const handleUpgrade = async (values: { name: string; height: string; info: string }) => { + setIsSigning(true); + const msgUpgrade = softwareUpgrade({ + plan: { + name: values.name, + height: BigInt(values.height), + time: new Date(0), + info: values.info, + }, + authority: admin, + }); + + const anyMessage = Any.fromPartial({ + typeUrl: msgUpgrade.typeUrl, + value: MsgSoftwareUpgrade.encode(msgUpgrade.value).finish(), + }); + + const groupProposalMsg = submitProposal({ + groupPolicyAddress: admin, + messages: [anyMessage], + metadata: '', + proposers: [address ?? ''], + title: `Upgrade the chain`, + summary: `This proposal will upgrade the chain`, + exec: 0, + }); + + const fee = await estimateFee(address ?? '', [groupProposalMsg]); + await tx([groupProposalMsg], { + fee, + onSuccess: () => { + setIsSigning(false); + }, + }); + setIsSigning(false); + }; + + const initialValues = { + name: '', + height: '', + info: '', + selectedVersion: null as (GitHubRelease & { upgradeInfo?: UpgradeInfo | null }) | null, + }; + + const modalContent = ( + + { + handleUpgrade({ + name: values.selectedVersion?.upgradeInfo?.name || '', + height: values.height, + info: values.selectedVersion?.upgradeInfo?.commitHash || '', + }); + }} + validateOnChange={true} + validateOnBlur={true} + > + {({ isValid, dirty, values, handleChange, handleSubmit, setFieldValue, resetForm }) => ( +
+
+ +
+

+ Chain Upgrade +

+ +
+
+
+ +
+
+ + +
+
+
+ + + +
+ +
+ + +
+
+
+ )} + +
+ +
+ + ); + + // Only render if we're in the browser + if (typeof document !== 'undefined') { + return createPortal(modalContent, document.body); + } + + return null; +} diff --git a/components/bank/components/historyBox.tsx b/components/bank/components/historyBox.tsx index c13b75e9..465778fa 100644 --- a/components/bank/components/historyBox.tsx +++ b/components/bank/components/historyBox.tsx @@ -76,6 +76,8 @@ export function HistoryBox({ const { metadatas } = useTokenFactoryDenomsMetadata(); + const isMobile = useIsMobile(); + function formatDateShort(dateString: string): string { const date = new Date(dateString); return date.toLocaleString('en-US', { @@ -230,25 +232,28 @@ export function HistoryBox({ {isLoading ? (
-
+
{[...Array(skeletonGroupCount)].map((_, groupIndex) => (
-
+
{[...Array(skeletonTxCount)].map((_, txIndex) => (
-
-
+
+
+
+
+
-
+
))}
@@ -311,16 +316,19 @@ export function HistoryBox({ const metadata = metadatas?.metadatas.find( m => m.base === amt.denom ); + const display = metadata?.display ?? metadata?.symbol ?? ''; return metadata?.display.startsWith('factory') ? metadata?.display?.split('/').pop()?.toUpperCase() - : truncateString( - metadata?.display ?? metadata?.symbol ?? '', - 10 - ).toUpperCase(); + : display.length > 4 + ? display.slice(0, 4).toUpperCase() + '...' + : display.toUpperCase(); })}

-
e.stopPropagation()}> +
e.stopPropagation()} + > {tx.data.from_address.startsWith('manifest1') ? (
-

+

{getTransactionPlusMinus(tx, address)} + {tx.data.amount .map(amt => { const metadata = metadatas?.metadatas.find( diff --git a/components/bank/components/tokenList.tsx b/components/bank/components/tokenList.tsx index 57341884..99c03c40 100644 --- a/components/bank/components/tokenList.tsx +++ b/components/bank/components/tokenList.tsx @@ -134,16 +134,18 @@ export function TokenList({ {[...Array(pageSize)].map((_, i) => (

-
-
-
+
+
+
-
+
+
+
diff --git a/components/factory/components/MyDenoms.tsx b/components/factory/components/MyDenoms.tsx index 0adc8820..2fad0947 100644 --- a/components/factory/components/MyDenoms.tsx +++ b/components/factory/components/MyDenoms.tsx @@ -31,7 +31,7 @@ export default function MyDenoms({ const [openUpdateDenomMetadataModal, setOpenUpdateDenomMetadataModal] = useState(false); const isMobile = useIsMobile(); - const pageSize = isMobile ? 6 : 8; + const pageSize = isMobile ? 5 : 8; const router = useRouter(); const [selectedDenom, setSelectedDenom] = useState(null); @@ -169,31 +169,37 @@ export default function MyDenoms({ {isLoading - ? Array(isMobile ? 6 : 8) + ? Array(isMobile ? 5 : 8) .fill(0) .map((_, index) => (
-
+
+
+
+
- +
@@ -370,24 +376,6 @@ export default function MyDenoms({ } }} /> - -
); } @@ -430,10 +418,14 @@ function TokenRow({
- + {denom.display.startsWith('factory') - ? denom.display.split('/').pop()?.toUpperCase() - : truncateString(denom.display, 12)} + ? (denom.display.split('/').pop()?.length || 0) > 9 + ? `${denom.display.split('/').pop()?.slice(0, 5)}...`.toUpperCase() + : denom.display.split('/').pop()?.toUpperCase() + : denom.display.length > 9 + ? `${denom.display.slice(0, 5)}...` + : truncateString(denom.display, 12)}
@@ -444,7 +436,13 @@ function TokenRow({
{formatAmount(totalSupply)} - {truncateString(denom?.display ?? 'No ticker provided', 10).toUpperCase()} + {denom.display.startsWith('factory') + ? (denom.display.split('/').pop()?.length || 0) > 9 + ? `${denom.display.split('/').pop()?.slice(0, 5)}...`.toUpperCase() + : denom.display.split('/').pop()?.toUpperCase() + : denom.display.length > 9 + ? `${denom.display.slice(0, 5)}...` + : truncateString(denom.display, 12)}
diff --git a/components/factory/forms/BurnForm.tsx b/components/factory/forms/BurnForm.tsx index ebb94673..aa2e45f7 100644 --- a/components/factory/forms/BurnForm.tsx +++ b/components/factory/forms/BurnForm.tsx @@ -118,7 +118,7 @@ export default function BurnForm({ messages: [encodedMessage], metadata: '', proposers: [address ?? ''], - title: `Manifest Module Control: Burn MFX`, + title: `Burn MFX`, summary: `This proposal includes a burn action for MFX.`, exec: 0, }); @@ -176,7 +176,7 @@ export default function BurnForm({ messages: [encodedMessage], metadata: '', proposers: [address ?? ''], - title: `Manifest Module Control: Multi Burn MFX`, + title: `Multi Burn MFX`, summary: `This proposal includes multiple burn actions for MFX.`, exec: 0, }); diff --git a/components/factory/modals/index.ts b/components/factory/modals/index.ts index 1554bc63..b2b2ddfb 100644 --- a/components/factory/modals/index.ts +++ b/components/factory/modals/index.ts @@ -1,3 +1,4 @@ export * from './denomInfo'; export * from './updateDenomMetadata'; export * from './multiMfxMintModal'; +export * from './multiMfxBurnModal'; diff --git a/components/factory/modals/multiMfxBurnModal.tsx b/components/factory/modals/multiMfxBurnModal.tsx index 8f3da005..83890780 100644 --- a/components/factory/modals/multiMfxBurnModal.tsx +++ b/components/factory/modals/multiMfxBurnModal.tsx @@ -1,4 +1,5 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; +import { createPortal } from 'react-dom'; import { Formik, Form, FieldArray, Field, FieldProps } from 'formik'; import Yup from '@/utils/yupExtensions'; @@ -8,10 +9,12 @@ import { PlusIcon, MinusIcon } from '@/components/icons'; import { MdContacts } from 'react-icons/md'; import { useTx, useFeeEstimation } from '@/hooks'; import { chainName } from '@/config'; -import { cosmos, osmosis, liftedinit } from '@liftedinit/manifestjs'; +import { cosmos, liftedinit } from '@liftedinit/manifestjs'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; -import { ExtendedMetadataSDKType, parseNumberToBigInt } from '@/utils'; +import { parseNumberToBigInt } from '@/utils'; +import { MetadataSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/bank'; +import { TailwindModal } from '@/components/react'; interface BurnPair { address: string; @@ -23,9 +26,7 @@ interface MultiBurnModalProps { onClose: () => void; admin: string; address: string; - denom: ExtendedMetadataSDKType | null; - exponent: number; - refetch: () => void; + denom: MetadataSDKType | null; } const BurnPairSchema = Yup.object().shape({ @@ -45,20 +46,25 @@ const MultiBurnSchema = Yup.object().shape({ }), }); -export function MultiBurnModal({ - isOpen, - onClose, - admin, - address, - denom, - exponent, - refetch, -}: MultiBurnModalProps) { +export function MultiBurnModal({ isOpen, onClose, admin, address, denom }: MultiBurnModalProps) { const [burnPairs, setBurnPairs] = useState([{ address: '', amount: '' }]); const { tx, isSigning, setIsSigning } = useTx(chainName); const { estimateFee } = useFeeEstimation(chainName); const { burnHeldBalance } = liftedinit.manifest.v1.MessageComposer.withTypeUrl; const { submitProposal } = cosmos.group.v1.MessageComposer.withTypeUrl; + const [isContactsOpen, setIsContactsOpen] = useState(false); + const [selectedIndex, setSelectedIndex] = useState(null); + + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && isOpen) { + onClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [isOpen]); const updateBurnPair = (index: number, field: 'address' | 'amount', value: string) => { const newPairs = [...burnPairs]; @@ -83,7 +89,7 @@ export function MultiBurnModal({ burnCoins: [ { denom: denom?.base ?? '', - amount: parseNumberToBigInt(pair.amount, exponent).toString(), + amount: parseNumberToBigInt(pair.amount, denom?.denom_units?.[0].exponent).toString(), }, ], }) @@ -101,7 +107,7 @@ export function MultiBurnModal({ messages: encodedMessages, metadata: '', proposers: [address], - title: `Manifest Module Control: Multi Burn MFX`, + title: `Multi Burn MFX`, summary: `This proposal includes a multi-burn action for MFX.`, exec: 0, }); @@ -110,7 +116,6 @@ export function MultiBurnModal({ await tx([msg], { fee, onSuccess: () => { - refetch(); onClose(); }, }); @@ -121,13 +126,26 @@ export function MultiBurnModal({ } }; - return ( + const modalContent = (
@@ -138,7 +156,7 @@ export function MultiBurnModal({ ✕
-

Multi Burn MFX

@@ -154,7 +172,7 @@ export function MultiBurnModal({
Burn Pairs
@@ -257,15 +275,18 @@ export function MultiBurnModal({
-
-
+ { + if (selectedIndex !== null) { + updateBurnPair(selectedIndex, 'address', selectedAddress); + setFieldValue(`burnPairs.${selectedIndex}.address`, selectedAddress); + } + setIsContactsOpen(false); + setSelectedIndex(null); + }} + /> )}
-
- + +
); + + // Only render if we're in the browser + if (typeof document !== 'undefined') { + return createPortal(modalContent, document.body); + } + + return null; } diff --git a/components/factory/modals/multiMfxMintModal.tsx b/components/factory/modals/multiMfxMintModal.tsx index 62733d3e..203052ae 100644 --- a/components/factory/modals/multiMfxMintModal.tsx +++ b/components/factory/modals/multiMfxMintModal.tsx @@ -1,8 +1,9 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Formik, Form, FieldArray, Field, FieldProps } from 'formik'; import Yup from '@/utils/yupExtensions'; import { NumberInput, TextInput } from '@/components/react/inputs'; import { TailwindModal } from '@/components/react'; +import { createPortal } from 'react-dom'; import { MdContacts } from 'react-icons/md'; import { PlusIcon, MinusIcon } from '@/components/icons'; @@ -11,7 +12,8 @@ import { chainName } from '@/config'; import { cosmos, liftedinit } from '@liftedinit/manifestjs'; import { Any } from '@liftedinit/manifestjs/dist/codegen/google/protobuf/any'; import { MsgPayout } from '@liftedinit/manifestjs/dist/codegen/liftedinit/manifest/v1/tx'; -import { ExtendedMetadataSDKType, parseNumberToBigInt } from '@/utils'; +import { parseNumberToBigInt } from '@/utils'; +import { MetadataSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/bank'; //TODO: find max mint amount from team for mfx. Find tx size limit for max payout pairs interface PayoutPair { address: string; @@ -23,9 +25,7 @@ interface MultiMintModalProps { onClose: () => void; admin: string; address: string; - denom: ExtendedMetadataSDKType | null; - exponent: number; - refetch: () => void; + denom: MetadataSDKType | null; } const PayoutPairSchema = Yup.object().shape({ @@ -46,15 +46,7 @@ const MultiMintSchema = Yup.object().shape({ }), }); -export function MultiMintModal({ - isOpen, - onClose, - admin, - address, - denom, - exponent, - refetch, -}: MultiMintModalProps) { +export function MultiMintModal({ isOpen, onClose, admin, address, denom }: MultiMintModalProps) { const [payoutPairs, setPayoutPairs] = useState([{ address: '', amount: '' }]); const { tx, isSigning, setIsSigning } = useTx(chainName); const { estimateFee } = useFeeEstimation(chainName); @@ -63,6 +55,17 @@ export function MultiMintModal({ const [isContactsOpen, setIsContactsOpen] = useState(false); const [selectedIndex, setSelectedIndex] = useState(null); + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && isOpen) { + onClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [isOpen]); + const updatePayoutPair = (index: number, field: 'address' | 'amount', value: string) => { const newPairs = [...payoutPairs]; newPairs[index] = { ...newPairs[index], [field]: value }; @@ -86,7 +89,10 @@ export function MultiMintModal({ address: pair.address, coin: { denom: denom?.base ?? '', - amount: parseNumberToBigInt(pair.amount, exponent).toString(), + amount: parseNumberToBigInt( + pair.amount, + denom?.denom_units?.[0].exponent ?? 0 + ).toString(), }, })), }); @@ -101,7 +107,7 @@ export function MultiMintModal({ messages: [encodedMessage], metadata: '', proposers: [address], - title: `Manifest Module Control: Multi Mint MFX`, + title: `Multi Mint MFX`, summary: `This proposal includes a multi-mint action for MFX.`, exec: 0, }); @@ -110,7 +116,6 @@ export function MultiMintModal({ await tx([msg], { fee, onSuccess: () => { - refetch(); onClose(); }, }); @@ -121,12 +126,26 @@ export function MultiMintModal({ } }; - return ( + const modalContent = (
@@ -172,7 +191,7 @@ export function MultiMintModal({ {values.payoutPairs.map((pair, index) => (
{index > 0 && (
#{index + 1}
@@ -196,7 +215,7 @@ export function MultiMintModal({ setSelectedIndex(index); setIsContactsOpen(true); }} - className="btn btn-primary btn-sm text-white absolute right-2 top-1/2 transform -translate-y-1/2" + className="btn btn-primary btn-sm text-white" > @@ -255,13 +274,17 @@ export function MultiMintModal({
-
-
- - + +
); + + // Only render if we're in the browser + if (typeof document !== 'undefined') { + return createPortal(modalContent, document.body); + } + + return null; } diff --git a/components/groups/components/groupControls.tsx b/components/groups/components/groupControls.tsx index c42df4b5..af4e5f8b 100644 --- a/components/groups/components/groupControls.tsx +++ b/components/groups/components/groupControls.tsx @@ -231,7 +231,7 @@ export default function GroupProposals({ const { tallies, isLoading: isTalliesLoading } = useMultipleTallyCounts(proposals.map(p => p.id)); return ( -
+
{/* Header section */} diff --git a/components/groups/components/myGroups.tsx b/components/groups/components/myGroups.tsx index 56ee0291..2497002e 100644 --- a/components/groups/components/myGroups.tsx +++ b/components/groups/components/myGroups.tsx @@ -44,7 +44,7 @@ export function YourGroups({ const [currentPage, setCurrentPage] = useState(1); const isMobile = useIsMobile(); - const pageSize = isMobile ? 6 : 8; + const pageSize = isMobile ? 4 : 8; const [selectedGroup, setSelectedGroup] = useState<{ policyAddress: string; @@ -244,7 +244,7 @@ export function YourGroups({
- +
@@ -253,7 +253,7 @@ export function YourGroups({
- +

- Multi Burn MFX + Burn MFX

Burn Pairs
-
@@ -206,22 +192,12 @@ export function MultiBurnModal({ isOpen, onClose, admin, address, denom }: Multi showError={false} label="Address" {...field} + value={admin} + disabled={true} placeholder="manifest1..." className={`input input-bordered w-full ${ meta.touched && meta.error ? 'input-error' : '' }`} - rightElement={ - - } /> {meta.touched && meta.error && (
- {isSigning ? ( - - ) : ( - 'Multi Burn' - )} + {isSigning ? : 'Burn'}
{ + const { selectedEndpoint } = useEndpointStore(); + + const rpcQueryClient = useQuery({ + queryKey: ['rpcQueryClient', selectedEndpoint?.rpc], + queryFn: () => + createRpcQueryClient({ + rpcEndpoint: selectedEndpoint?.rpc || '', + }), + enabled: !!selectedEndpoint?.rpc, + staleTime: Infinity, + }); + + return { + rpcQueryClient: rpcQueryClient.data, + }; +};