Skip to content

Commit

Permalink
feat: new meta ui refactored
Browse files Browse the repository at this point in the history
  • Loading branch information
Kodylow committed Sep 17, 2024
1 parent c445992 commit ab60569
Show file tree
Hide file tree
Showing 13 changed files with 914 additions and 438 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import {
Card,
CardBody,
Expand All @@ -9,21 +9,38 @@ import {
TabPanels,
Tab,
TabPanel,
Badge,
} from '@chakra-ui/react';
import { githubLight } from '@uiw/codemirror-theme-github';
import { json } from '@codemirror/lang-json';
import CodeMirror from '@uiw/react-codemirror';
import {
ClientConfig,
ConsensusMeta,
MetaFields,
MetaSubmissions,
ModuleKind,
ParsedConsensusMeta,
SignedApiAnnouncement,
} from '@fedimint/types';
import { useTranslation } from '@fedimint/utils';
import { useTranslation, hexToMeta } from '@fedimint/utils';
import { MetaManager } from './meta/MetaManager';
import { ConsensusMetaFields } from './meta/ViewConsensusMeta';
import { ApiAnnouncements } from './ApiAnnouncements';

import { ProposedMetas } from './meta/ProposedMetas';
import { ModuleRpc } from '../../../types';
import { useGuardianAdminApi } from '../../../../context/hooks';

export const DEFAULT_META_KEY = 0;
export const POLL_TIMEOUT_MS = 2000;

type MetaSubmissionMap = {
[key: string]: {
peers: number[];
meta: MetaFields;
};
};

interface FederationTabsCardProps {
config: ClientConfig | undefined;
ourPeer: { id: number; name: string };
Expand All @@ -39,9 +56,99 @@ export const FederationTabsCard: React.FC<FederationTabsCardProps> = ({
const [metaModuleId, setMetaModuleId] = useState<string | undefined>(
undefined
);
const [consensusMeta, setConsensusMeta] = useState<ConsensusMetaFields>();
const [editedMetaFields, setEditedMetaFields] = useState<MetaFields>([]);
const [consensusMeta, setConsensusMeta] = useState<ParsedConsensusMeta>();
const [peers, setPeers] = useState<{ id: number; name: string }[]>([]);
const [metaSubmissions, setMetaSubmissions] = useState<MetaSubmissionMap>({});
const pendingProposalsCount = Object.keys(metaSubmissions).length;
const [hasVoted, setHasVoted] = useState(false);
const [activeTab, setActiveTab] = useState(0);
const api = useGuardianAdminApi();

const pollMetaSubmissions = useCallback(async () => {
if (!metaModuleId) return;

try {
const submissions = await api.moduleApiCall<MetaSubmissions>(
Number(metaModuleId),
ModuleRpc.getSubmissions,
DEFAULT_META_KEY
);

const metas: MetaSubmissionMap = {};
let voted = false;

Object.entries(submissions).forEach(([peer, hexString]) => {
if (hexString === '7b7d') return; // Filter out empty submissions
const metaObject = hexToMeta(hexString);
const meta = Object.entries(metaObject).filter(
([, value]) => value !== undefined && value !== ''
) as [string, string][];

const metaKey = JSON.stringify(meta); // Use JSON string as a key to group identical metas

if (metas[metaKey]) {
metas[metaKey].peers.push(Number(peer));
} else {
metas[metaKey] = {
peers: [Number(peer)],
meta: meta as MetaFields,
};
}

if (Number(peer) === ourPeer.id) {
voted = true;
}
});

setMetaSubmissions(metas);
setHasVoted(voted);
} catch (err) {
console.warn('Failed to poll for meta submissions', err);
}
}, [api, metaModuleId, ourPeer.id]);

useEffect(() => {
const pollSubmissionInterval = setInterval(
pollMetaSubmissions,
POLL_TIMEOUT_MS
);
return () => {
clearInterval(pollSubmissionInterval);
};
}, [pollMetaSubmissions]);

useEffect(() => {
const pollConsensusMeta = setInterval(async () => {
try {
const meta = await api.moduleApiCall<ConsensusMeta>(
Number(metaModuleId),
ModuleRpc.getConsensus,
DEFAULT_META_KEY
);
if (!meta) return;
const parsedConsensusMeta: ParsedConsensusMeta = {
revision: meta.revision,
value: Object.entries(hexToMeta(meta.value)).filter(
([, value]) => value !== undefined && value !== ''
) as [string, string][],
};
// Compare the new meta with the current state
setConsensusMeta((currentMeta) => {
if (
JSON.stringify(currentMeta) !== JSON.stringify(parsedConsensusMeta)
) {
return parsedConsensusMeta;
}
return currentMeta;
});
} catch (err) {
console.warn('Failed to poll for consensus meta', err);
}
}, POLL_TIMEOUT_MS);
return () => {
clearInterval(pollConsensusMeta);
};
}, [api, metaModuleId]);

useEffect(() => {
if (config) {
Expand All @@ -66,14 +173,46 @@ export const FederationTabsCard: React.FC<FederationTabsCardProps> = ({

return config ? (
<Card flex='1'>
<Tabs variant='soft-rounded' colorScheme='blue'>
<Tabs
variant='soft-rounded'
colorScheme='blue'
index={activeTab}
onChange={setActiveTab}
>
<CardHeader>
<Flex direction='column' gap='4'>
<Flex justify='space-between' align='center'>
<TabList>
<Tab>{t('federation-dashboard.config.manage-meta.label')}</Tab>
<Tab>{t('federation-dashboard.config.view-config')}</Tab>
<Tab>{t('federation-dashboard.api-announcements.label')}</Tab>
<Tab minWidth='auto' px={2}>
{t('federation-dashboard.config.view-meta')}
</Tab>
<Tab minWidth='auto' px={2}>
{t('federation-dashboard.config.view-config')}
</Tab>
<Tab minWidth='auto' px={2}>
{t('federation-dashboard.api-announcements.label')}
</Tab>
{pendingProposalsCount > 0 && (
<Tab minWidth='auto' px={2}>
<Flex align='center'>
{t(
'federation-dashboard.config.manage-meta.proposed-meta-label'
)}
<Badge
ml={2}
fontSize='0.8em'
colorScheme='red'
borderRadius='full'
boxSize='1.5em'
display='flex'
alignItems='center'
justifyContent='center'
>
{pendingProposalsCount}
</Badge>
</Flex>
</Tab>
)}
</TabList>
</Flex>
</Flex>
Expand All @@ -84,11 +223,7 @@ export const FederationTabsCard: React.FC<FederationTabsCardProps> = ({
<MetaManager
metaModuleId={metaModuleId}
consensusMeta={consensusMeta}
setConsensusMeta={setConsensusMeta}
editedMetaFields={editedMetaFields}
setEditedMetaFields={setEditedMetaFields}
ourPeer={ourPeer}
peers={peers}
setActiveTab={setActiveTab}
/>
</TabPanel>
<TabPanel>
Expand All @@ -108,6 +243,16 @@ export const FederationTabsCard: React.FC<FederationTabsCardProps> = ({
config={config}
/>
</TabPanel>
<TabPanel>
<ProposedMetas
ourPeer={ourPeer}
peers={peers}
metaModuleId={metaModuleId ?? ''}
consensusMeta={consensusMeta}
metaSubmissions={metaSubmissions}
hasVoted={hasVoted}
/>
</TabPanel>
</TabPanels>
</CardBody>
</Tabs>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react';
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalFooter,
Button,
Text,
} from '@chakra-ui/react';
import { useTranslation } from '@fedimint/utils';
import { MetaFields } from '@fedimint/types';
import { Table, TableColumn } from '@fedimint/ui';
import { formatJsonValue } from './ProposedMetas';

interface ConfirmNewMetaModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
selectedMeta: MetaFields | null;
}

export const ConfirmNewMetaModal: React.FC<ConfirmNewMetaModalProps> = ({
isOpen,
onClose,
onConfirm,
selectedMeta,
}) => {
const { t } = useTranslation();

const columnsWithoutEffect: TableColumn<'metaKey' | 'value'>[] = [
{
key: 'metaKey',
heading: t('set-config.meta-fields-key'),
},
{
key: 'value',
heading: t('set-config.meta-fields-value'),
},
];

return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>
{t('federation-dashboard.config.manage-meta.confirm-modal.title')}
</ModalHeader>
<ModalBody>
<Text mb={4}>
{t(
'federation-dashboard.config.manage-meta.confirm-modal.description'
)}
</Text>
{selectedMeta && (
<Table
columns={columnsWithoutEffect}
rows={selectedMeta.map(([key, value]) => ({
key: `${key}-${value}`,
metaKey: <Text>{key}</Text>,
value: formatJsonValue(value),
}))}
/>
)}
</ModalBody>
<ModalFooter>
<Button variant='ghost' onClick={onClose}>
{t('common.cancel')}
</Button>
<Button colorScheme='green' ml={3} onClick={onConfirm}>
{t('common.confirm')}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};
Loading

0 comments on commit ab60569

Please sign in to comment.