Skip to content

Commit

Permalink
feat: override proposal_details components for Callisto v4
Browse files Browse the repository at this point in the history
  • Loading branch information
dadamu committed Apr 24, 2024
1 parent 5959fbb commit 0be3604
Show file tree
Hide file tree
Showing 6 changed files with 572 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, { useEffect, useState } from 'react';
import Loading from '@/components/loading';

interface MetadataLoaderProps {
metadata: string;
}

// Checks if a string is a valid URL
const isValidUrl = (url: string) => {
const pattern = /^(ftp|http|https|ipfs):\/\/[^ "]+$/;
return pattern.test(url);
};

// Checks if a string is a IPFS URL
const isIPFSUrl = (url: string) => {
const pattern = /^(ipfs):\/\/[^ "]+$/;
return pattern.test(url);
};

// Removes ipfs prefix from metadata
const removeIPFSPrefix = (metadata: string): string => {
if (metadata.startsWith('ipfs://')) {
return metadata.substring('ipfs://'.length);
}
return metadata;
};

const MetadataLoader: React.FC<MetadataLoaderProps> = ({ metadata }) => {
const [metadataContent, setMetadataContent] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(true);

useEffect(() => {
let isMounted = true;

const fetchMetadata = async () => {
try {
if (!isValidUrl(metadata)) {
setMetadataContent(metadata);
return;
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // Abort the fetch after 10 seconds

let response: Response;
if (!isIPFSUrl(metadata)) {
response = await fetch(metadata, { signal: controller.signal });
} else {
const modifiedMetadata = removeIPFSPrefix(metadata);
response = await fetch(`https://ipfs.io/ipfs/${modifiedMetadata}`, {
signal: controller.signal,
});
}
clearTimeout(timeoutId); // Clear the timeout
if (!isMounted) {
setMetadataContent(metadata);
return;
}
if (!response.ok) {
setMetadataContent(metadata);
return;
}
const text = await response.text();
setMetadataContent(text);
} catch (err) {
if (!isMounted) return; // Exit if the component is unmounted
setMetadataContent(metadata);
} finally {
setLoading(false);
}
};

fetchMetadata();

return () => {
isMounted = false; // Set isMounted to false when unmounting
};
}, [metadata]);

if (loading) {
return <Loading />;
}

if (metadataContent) {
return <code>{metadataContent}</code>;
}

return null;
};

export default MetadataLoader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import Divider from '@mui/material/Divider';
import Typography from '@mui/material/Typography';
import useAppTranslation from '@/hooks/useAppTranslation';
import numeral from 'numeral';
import * as R from 'ramda';
import { FC, useMemo, useCallback } from 'react';
import { useRecoilValue } from 'recoil';
import Box from '@/components/box';
import Markdown from '@/components/markdown';
import Name from '@/components/name';
import SingleProposal from '@/components/single_proposal';
import { useProfileRecoil } from '@/recoil/profiles/hooks';
import { readDate, readTimeFormat } from '@/recoil/settings';
import CommunityPoolSpend from '@/screens/proposal_details/components/overview/components/community_pool_spend';
import UpdateParams from '@/screens/proposal_details/components/overview/components/update_params';
import ParamsChange from '@/screens/proposal_details/components/overview/components/params_change';
import SoftwareUpgrade from '@/screens/proposal_details/components/overview/components/software_upgrade';
import type { OverviewType } from '@/screens/proposal_details/types';
import { getProposalType } from '@/screens/proposal_details/utils';
import dayjs, { formatDayJs } from '@/utils/dayjs';
import { formatNumber, formatToken } from '@/utils/format_token';
import MetadataLoader from './components/metadata_loader';
import useStyles from './styles';

const Overview: FC<{ className?: string; overview: OverviewType }> = ({ className, overview }) => {
const dateFormat = useRecoilValue(readDate);
const timeFormat = useRecoilValue(readTimeFormat);
const { classes, cx } = useStyles();
const { t } = useAppTranslation('proposals');

const types = useMemo(() => {
if (Array.isArray(overview.content)) {
const typeArray: string[] = [];
overview.content.forEach((type: { params: JSON; type: string }) =>
typeArray.push(getProposalType(R.pathOr('', ['@type'], type)))
);
return typeArray;
}
const typeArray: string[] = [];
typeArray.push(getProposalType(R.pathOr('', ['@type'], overview.content)));
return typeArray;
}, [overview.content]);

const changes = useMemo(() => {
const changeList: any[] = [];
if (Array.isArray(overview.content)) {
overview.content.forEach((type: { params: JSON; type: string }) => {
changeList.push({ params: type.params, type: R.pathOr('', ['@type'], type) });
});

return changeList;
}
return changeList;
}, [overview.content]);

const { address: proposerAddress, name: proposerName } = useProfileRecoil(overview.proposer);
const { name: recipientName } = useProfileRecoil(overview?.content?.recipient);
const proposerMoniker = proposerName || overview.proposer;
const recipientMoniker = recipientName || overview?.content?.recipient;
const amountRequested = overview.content?.amount
? formatToken(overview.content?.amount[0]?.amount, overview.content?.amount[0]?.denom)
: null;
const parsedAmountRequested = amountRequested
? `${formatNumber(
amountRequested.value,
amountRequested.exponent
)} ${amountRequested.displayDenom.toUpperCase()}`
: '';

const getExtraDetails = useCallback(() => {
let extraDetails = null;
types.forEach((type: string) => {
if (type === 'parameterChangeProposal') {
extraDetails = (
<>
<Typography variant="body1" className="label">
{t('changes')}
</Typography>
<ParamsChange changes={R.pathOr([], ['changes'], overview.content)} />
</>
);
}
if (type === 'softwareUpgradeProposal') {
extraDetails = (
<>
<Typography variant="body1" className="label">
{t('plan')}
</Typography>
<SoftwareUpgrade
height={R.pathOr('0', ['plan', 'height'], overview.content)}
info={R.pathOr('', ['plan', 'info'], overview.content)}
name={R.pathOr('', ['plan', 'name'], overview.content)}
/>
</>
);
}
if (type === 'communityPoolSpendProposal') {
extraDetails = (
<>
<Typography variant="body1" className="label">
{t('content')}
</Typography>
<CommunityPoolSpend
recipient={overview?.content?.recipient}
recipientMoniker={recipientMoniker}
amountRequested={parsedAmountRequested}
/>
</>
);
}

if (type.includes('MsgUpdateParams')) {
extraDetails = (
<>
{changes.map((change) => (
<UpdateParams changes={change} className="accordion" key={change.type} />
))}
</>
);
}
});
return extraDetails;
}, [changes, overview.content, parsedAmountRequested, recipientMoniker, t, types]);

const extra = getExtraDetails();

return (
<Box className={cx(classes.root, className)}>
<SingleProposal
id={`#${numeral(overview.id).format('0,0')}`}
title={overview.title}
status={overview.status}
/>
<Divider />
<div className={classes.content}>
{types.length > 0 && (
<>
<Typography variant="body1" className="label">
{t('type')}
</Typography>
<Typography variant="body1">
{types.map((type: string) => (
<Typography variant="body1" className="value" key={type}>
{t(type)}
</Typography>
))}
</Typography>
</>
)}
<Typography variant="body1" className="label">
{t('proposer')}
</Typography>
<Name name={proposerMoniker} address={proposerAddress} />
{overview?.submitTime && (
<>
<Typography variant="body1" className="label">
{t('submitTime')}
</Typography>
<Typography variant="body1" className="value">
{formatDayJs(dayjs.utc(overview.submitTime), dateFormat, timeFormat)}
</Typography>
</>
)}
{overview?.depositEndTime && (
<>
<Typography variant="body1" className="label">
{t('depositEndTime')}
</Typography>
<Typography variant="body1" className="value">
{formatDayJs(dayjs.utc(overview.depositEndTime), dateFormat, timeFormat)}
</Typography>
</>
)}
{overview?.votingStartTime && (
<>
<Typography variant="body1" className="label">
{t('votingStartTime')}
</Typography>
<Typography variant="body1" className="value">
{formatDayJs(dayjs.utc(overview.votingStartTime), dateFormat, timeFormat)}
</Typography>
</>
)}
{overview?.votingEndTime && (
<>
<Typography variant="body1" className="label">
{t('votingEndTime')}
</Typography>
<Typography variant="body1" className="value">
{formatDayJs(dayjs.utc(overview.votingEndTime), dateFormat, timeFormat)}
</Typography>
</>
)}
<Typography variant="body1" className="label">
{t('description')}
</Typography>
<Markdown markdown={overview.description} />
{overview?.metadata && (
<>
<Typography variant="body1" className="label">
{t('metadata')}
</Typography>
<MetadataLoader metadata={overview.metadata} />
</>
)}
{extra}
</div>
</Box>
);
};

export default Overview;
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { makeStyles } from 'tss-react/mui';

const useStyles = makeStyles()((theme) => ({
root: {
'& .label': {
color: theme.palette.custom.fonts.fontThree,
},
'& .content': {
marginBottom: theme.spacing(2),
display: 'block',
[theme.breakpoints.up('lg')]: {
display: 'flex',
},
},
'& .recipient': {
marginBottom: theme.spacing(2),
[theme.breakpoints.up('lg')]: {
display: 'block',
},
},
'& .amountRequested': {
marginBottom: theme.spacing(2),
display: 'block',
padding: '0',
[theme.breakpoints.up('lg')]: {
display: 'block',
paddingLeft: '30px',
},
},
'& .accordion': {
background: '#151519',
},
},
content: {
marginTop: theme.spacing(2),
display: 'grid',
p: {
lineHeight: 1.8,
},
'& ul': {
padding: '0.25rem 0.5rem',
[theme.breakpoints.up('lg')]: {
padding: '0.5rem 1rem',
},
},
'& li': {
padding: '0.25rem 0.5rem',
[theme.breakpoints.up('lg')]: {
padding: '0.5rem 1rem',
},
},
'& > *': {
marginBottom: theme.spacing(1),
[theme.breakpoints.up('lg')]: {
marginBottom: theme.spacing(2),
},
},
[theme.breakpoints.up('lg')]: {
gridTemplateColumns: '200px auto',
},
},
time: {
marginTop: theme.spacing(2),
display: 'grid',
'& > *': {
marginBottom: theme.spacing(1),
[theme.breakpoints.up('md')]: {
marginBottom: theme.spacing(2),
},
},
[theme.breakpoints.up('md')]: {
gridTemplateColumns: 'repeat(2, 1fr)',
},
},
}));

export default useStyles;
Loading

0 comments on commit 0be3604

Please sign in to comment.