diff --git a/components/admins/components/__tests__/validatorList.test.tsx b/components/admins/components/__tests__/validatorList.test.tsx index d1b80fa6..f3ff1be7 100644 --- a/components/admins/components/__tests__/validatorList.test.tsx +++ b/components/admins/components/__tests__/validatorList.test.tsx @@ -47,7 +47,11 @@ describe('ValidatorList', () => { test('clicking on a validator row opens the modal', async () => { renderWithProps(); fireEvent.click(screen.getByText('Validator One')); - await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()); + await waitFor(() => { + expect(screen.getByText('Validator Details')).toBeInTheDocument(); + expect(screen.getByText('SECURITY CONTACT')).toBeInTheDocument(); + expect(screen.getByText('OPERATOR ADDRESS')).toBeInTheDocument(); + }); }); test('remove button works and shows the warning modal', async () => { diff --git a/components/admins/components/validatorList.tsx b/components/admins/components/validatorList.tsx index 0dd42dfa..3639aee2 100644 --- a/components/admins/components/validatorList.tsx +++ b/components/admins/components/validatorList.tsx @@ -28,6 +28,8 @@ export default function ValidatorList({ const [searchTerm, setSearchTerm] = useState(''); const [selectedValidator, setSelectedValidator] = useState(null); const [modalId, setModalId] = useState(null); + const [openWarningModal, setOpenWarningModal] = useState(false); + const [openValidatorModal, setOpenValidatorModal] = useState(false); const [validatorToRemove, setValidatorToRemove] = useState(null); const totalvp = Array.isArray(activeValidators) @@ -44,13 +46,6 @@ export default function ValidatorList({ })) : []; - useEffect(() => { - if (modalId) { - const modal = document.getElementById(modalId) as HTMLDialogElement; - modal?.showModal(); - } - }, [modalId]); - const filteredValidators = useMemo(() => { const validators = active ? activeValidators : pendingValidators; return validators.filter(validator => @@ -60,9 +55,7 @@ export default function ValidatorList({ const handleRemove = (validator: ExtendedValidatorSDKType) => { setValidatorToRemove(validator); - - const modal = document.getElementById(`warning-modal`) as HTMLDialogElement; - modal?.showModal(); + setOpenWarningModal(true); }; const [modalKey, setModalKey] = useState(0); @@ -71,6 +64,7 @@ export default function ValidatorList({ setSelectedValidator(validator); setModalKey(prevKey => prevKey + 1); setModalId(`validator-modal-${validator.operator_address}-${Date.now()}`); + setOpenValidatorModal(true); }; return ( @@ -254,6 +248,8 @@ export default function ValidatorList({ admin={admin} totalvp={totalvp.toString()} validatorVPArray={validatorVPArray} + openValidatorModal={openValidatorModal} + setOpenValidatorModal={setOpenValidatorModal} /> ); diff --git a/components/admins/modals/validatorModal.tsx b/components/admins/modals/validatorModal.tsx index 19507cfd..05f961dd 100644 --- a/components/admins/modals/validatorModal.tsx +++ b/components/admins/modals/validatorModal.tsx @@ -30,13 +30,28 @@ export function ValidatorDetailsModal({ admin, totalvp, validatorVPArray, + openValidatorModal, + setOpenValidatorModal, }: Readonly<{ validator: ExtendedValidatorSDKType | null; modalId: string; admin: string; totalvp: string; validatorVPArray: { vp: bigint; moniker: string }[]; + openValidatorModal: boolean; + setOpenValidatorModal: (open: boolean) => void; }>) { + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && openValidatorModal) { + handleClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [openValidatorModal]); + const [power, setPowerInput] = useState(validator?.consensus_power?.toString() || ''); const { tx, isSigning, setIsSigning } = useTx(chainName); @@ -100,12 +115,18 @@ export function ValidatorDetailsModal({ }; const handleClose = () => { - const modal = document.getElementById(modalId) as HTMLDialogElement; - modal?.close(); + if (setOpenValidatorModal) { + setOpenValidatorModal(false); + } + (document.getElementById(modalId) as HTMLDialogElement)?.close(); }; return ( - +
- +
); diff --git a/components/admins/modals/warningModal.tsx b/components/admins/modals/warningModal.tsx index 53731a9c..bb868e2d 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, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { PiWarning } from 'react-icons/pi'; interface WarningModalProps { @@ -13,6 +13,8 @@ interface WarningModalProps { address: string; moniker: string; modalId: string; + openWarningModal: boolean; + setOpenWarningModal: (open: boolean) => void; } export function WarningModal({ @@ -21,7 +23,20 @@ export function WarningModal({ modalId, address, isActive, + openWarningModal, + setOpenWarningModal, }: Readonly) { + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && openWarningModal) { + handleClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [openWarningModal]); + const { tx, isSigning, setIsSigning } = useTx(chainName); const { estimateFee } = useFeeEstimation(chainName); const { address: userAddress } = useChain(chainName); @@ -68,13 +83,29 @@ export function WarningModal({ setIsSigning(false); }; + const handleClose = () => { + if (setOpenWarningModal) { + setOpenWarningModal(false); + } + (document.getElementById(modalId) as HTMLDialogElement)?.close(); + }; + return ( - +
- +
@@ -106,7 +137,7 @@ export function WarningModal({
- +
); diff --git a/components/factory/components/MyDenoms.tsx b/components/factory/components/MyDenoms.tsx index 094a9e15..c45e4054 100644 --- a/components/factory/components/MyDenoms.tsx +++ b/components/factory/components/MyDenoms.tsx @@ -28,6 +28,7 @@ export default function MyDenoms({ }) { const [searchQuery, setSearchQuery] = useState(''); const [currentPage, setCurrentPage] = useState(1); + const [openUpdateDenomMetadataModal, setOpenUpdateDenomMetadataModal] = useState(false); const isMobile = useIsMobile(); const pageSize = isMobile ? 6 : 8; @@ -35,7 +36,7 @@ export default function MyDenoms({ const router = useRouter(); const [selectedDenom, setSelectedDenom] = useState(null); const [modalType, setModalType] = useState< - 'mint' | 'burn' | 'multimint' | 'multiburn' | 'update' | null + 'mint' | 'burn' | 'multimint' | 'multiburn' | 'update' | 'info' | null >(null); const filteredDenoms = useMemo(() => { @@ -50,13 +51,9 @@ export default function MyDenoms({ const handleDenomSelect = (denom: ExtendedMetadataSDKType) => { if (!modalType) { - // Only show denom info if no other modal is active setSelectedDenom(denom); - setModalType(null); // Ensure no other modal type is set - const modal = document.getElementById('denom-info-modal') as HTMLDialogElement; - if (modal) { - modal.showModal(); - } + setModalType('info'); + router.push(`/factory?denom=${denom.base}&action=info`, undefined, { shallow: true }); } }; @@ -73,15 +70,16 @@ export default function MyDenoms({ action === 'mint' || action === 'burn' || action === 'multimint' || - action === 'multiburn' + action === 'multiburn' || + action === 'update' || + action === 'info' ) { - setModalType(action as 'mint' | 'burn' | 'multimint' | 'multiburn'); - } else { - // Only show denom info if no other action is specified - const modal = document.getElementById('denom-info-modal') as HTMLDialogElement; - if (modal) { - modal.showModal(); + setModalType(action as 'mint' | 'burn' | 'multimint' | 'multiburn' | 'update' | 'info'); + if (action === 'update') { + setOpenUpdateDenomMetadataModal(true); } + } else { + setModalType('info'); } } } else { @@ -93,6 +91,13 @@ export default function MyDenoms({ const handleCloseModal = () => { setSelectedDenom(null); setModalType(null); + setOpenUpdateDenomMetadataModal(false); + router.push('/factory', undefined, { shallow: true }); + }; + + const handleUpdateModalClose = () => { + setOpenUpdateDenomMetadataModal(false); + setModalType(null); router.push('/factory', undefined, { shallow: true }); }; @@ -100,25 +105,20 @@ export default function MyDenoms({ e.preventDefault(); e.stopPropagation(); setSelectedDenom(denom); - // Important: Don't show the denom info modal setModalType('update'); - const modal = document.getElementById('update-denom-metadata-modal') as HTMLDialogElement; - if (modal) { - modal.showModal(); - } + setOpenUpdateDenomMetadataModal(true); + router.push(`/factory?denom=${denom.base}&action=update`, undefined, { shallow: true }); }; const handleSwitchToMultiMint = () => { setModalType('multimint'); - // Update URL router.push(`/factory?denom=${selectedDenom?.base}&action=multimint`, undefined, { shallow: true, }); }; const handleSwitchToMultiBurn = () => { - setModalType('multiburn'); // Set the modal type to multiburn - // Update URL + setModalType('multiburn'); router.push(`/factory?denom=${selectedDenom?.base}&action=multiburn`, undefined, { shallow: true, }); @@ -229,11 +229,17 @@ export default function MyDenoms({ e.stopPropagation(); setSelectedDenom(denom); setModalType('mint'); + router.push(`/factory?denom=${denom.base}&action=mint`, undefined, { + shallow: true, + }); }} onBurn={e => { e.stopPropagation(); setSelectedDenom(denom); setModalType('burn'); + router.push(`/factory?denom=${denom.base}&action=burn`, undefined, { + shallow: true, + }); }} onUpdate={e => handleUpdateModal(denom, e)} /> @@ -314,7 +320,16 @@ export default function MyDenoms({ - + { + if (!open) { + handleCloseModal(); + } + }} + denom={selectedDenom} + modalId="denom-info-modal" + /> { refetchDenoms(); + handleUpdateModalClose(); + }} + openUpdateDenomMetadataModal={openUpdateDenomMetadataModal} + setOpenUpdateDenomMetadataModal={open => { + if (!open) { + handleUpdateModalClose(); + } else { + setOpenUpdateDenomMetadataModal(true); + } }} /> void; onSwitchToMultiBurn: () => void; }) { + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && isOpen) { + onClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [isOpen]); const { poaAdmin, isPoaAdminLoading } = usePoaGetAdmin(); const { groupByAdmin, isGroupByAdminLoading } = useGroupsByAdmin( poaAdmin ?? 'manifest1afk9zr2hn2jsac63h4hm60vl9z3e5u69gndzf7c99cqge3vzwjzsfmy9qj' diff --git a/components/factory/modals/MintModal.tsx b/components/factory/modals/MintModal.tsx index 42b5d706..7bbf3ce3 100644 --- a/components/factory/modals/MintModal.tsx +++ b/components/factory/modals/MintModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { MetadataSDKType } from '@liftedinit/manifestjs/dist/codegen/cosmos/bank/v1beta1/bank'; import MintForm from '@/components/factory/forms/MintForm'; import { useGroupsByAdmin, usePoaGetAdmin } from '@/hooks'; @@ -28,6 +28,17 @@ export default function MintModal({ admin: string; isPoaAdminLoading: boolean; }) { + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && isOpen) { + onClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [isOpen]); + const [isMultiMintOpen, setIsMultiMintOpen] = useState(false); const { groupByAdmin, isGroupByAdminLoading } = useGroupsByAdmin( @@ -56,8 +67,11 @@ export default function MintModal({ return ( <> - -
+ +
@@ -76,7 +99,7 @@ export const DenomInfoModal: React.FC<{
- +
); diff --git a/components/factory/modals/updateDenomMetadata.tsx b/components/factory/modals/updateDenomMetadata.tsx index 64e96cbe..ec30c7a9 100644 --- a/components/factory/modals/updateDenomMetadata.tsx +++ b/components/factory/modals/updateDenomMetadata.tsx @@ -7,6 +7,7 @@ import { Formik, Form } from 'formik'; import Yup from '@/utils/yupExtensions'; import { TextInput, TextArea } from '@/components/react/inputs'; import { truncateString, ExtendedMetadataSDKType } from '@/utils'; +import { useEffect } from 'react'; const TokenDetailsSchema = Yup.object().shape({ display: Yup.string().required('Display is required').noProfanity(), @@ -19,16 +20,30 @@ const TokenDetailsSchema = Yup.object().shape({ }); export function UpdateDenomMetadataModal({ + openUpdateDenomMetadataModal, + setOpenUpdateDenomMetadataModal, denom, address, modalId, onSuccess, }: { + openUpdateDenomMetadataModal: boolean; + setOpenUpdateDenomMetadataModal: (open: boolean) => void; denom: ExtendedMetadataSDKType | null; address: string; modalId: string; onSuccess: () => void; }) { + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && openUpdateDenomMetadataModal) { + setOpenUpdateDenomMetadataModal(false); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [openUpdateDenomMetadataModal]); const baseDenom = denom?.base?.split('/').pop() || ''; const fullDenom = `factory/${address}/${baseDenom}`; const symbol = baseDenom.slice(1).toUpperCase(); @@ -90,7 +105,11 @@ export function UpdateDenomMetadataModal({ }; return ( - + setOpenUpdateDenomMetadataModal(false)} + > {({ isValid, dirty, values, handleChange, handleSubmit }) => ( -
-
-
- -
-

- Update Metadata for{' '} - - {denom?.display?.startsWith('factory') - ? denom?.display?.split('/').pop()?.toUpperCase() - : truncateString(denom?.display ?? 'DENOM', 12)} - -

-
- -
-
- - - - -
+
+ + + +

+ Update Metadata for{' '} + + {denom?.display?.startsWith('factory') + ? denom?.display?.split('/').pop()?.toUpperCase() + : truncateString(denom?.display ?? 'DENOM', 12)} + +

+
-