Skip to content

Commit

Permalink
feat: improve proposal details visualization (#260)
Browse files Browse the repository at this point in the history
## Description

Closes: DPM-162, DPM-163, DPM-172, DPM-178



This PR enhances the visualization of the details of a governance proposal with the following changes:

1. Displays the proposal messages inside the scrollview instead of a horizontal list.
2. Generalizes the visualization of the modules `MsgUpdateParams` to display the changed parameters.
3. Adds the visualization of the proposal summary.
4. Shows the profile of the proposal's proposer.

###Final result:
![output](https://github.com/desmos-labs/dpm/assets/6245917/17ffb599-6fa2-4422-8bf2-ed593a5428f7)


---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

- [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] provided a link to the relevant issue or specification
- [x] reviewed "Files changed" and left comments if necessary
- [x] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed all author checklist items have been addressed
  • Loading branch information
manu0466 authored Nov 7, 2023
1 parent b467b1a commit cb6271a
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 158 deletions.
3 changes: 2 additions & 1 deletion src/assets/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@
"yes": "Yes",
"no": "No",
"description": "Description",
"summary": "Summary",
"i understand": "I understand",
"my address": "My address",
"copy address": "Copy address",
"change network": "Change network",
"generating": "Generating...",
"balance": "Balance",
"go back": "Go back"
"go back": "Go back",
}
3 changes: 2 additions & 1 deletion src/assets/locales/en/messages/common.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"signer": "Signer",
"user": "User",
"update params": "Update params"
"update params": "Update params",
"update module params": "Update {{ module }} module params",
}
90 changes: 90 additions & 0 deletions src/components/InlineProfile/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import TypographyContentLoaders from 'components/ContentLoaders/Typography';
import CopiableAddress from 'components/CopiableAddress';
import ProfileImage from 'components/ProfileImage';
import Spacer from 'components/Spacer';
import Typography from 'components/Typography';
import { makeStyle } from 'config/theme';
import useGetProfile from 'hooks/profile/useGetProfile';
import { getProfileDisplayName } from 'lib/ProfileUtils';
import { RootNavigatorParamList } from 'navigation/RootNavigator';
import ROUTES from 'navigation/routes';
import React from 'react';
import { View } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { DesmosProfile } from 'types/desmos';

interface InlineProfileProps {
/**
* Address of the profile to display.
*/
readonly address: string;
/**
* Optional prefetched profile.
*/
readonly profile?: DesmosProfile;
}

/**
* Components that displays an user profile and if clicked opens the
* profile screen.
*/
const InlineProfile: React.FC<InlineProfileProps> = ({ address, profile }) => {
const styles = useStyles();
const [toDisplayProfile, setToDisplayProfile] = React.useState(profile);
const [profileLoading, setProfileLoading] = React.useState(profile === undefined);

const navigation = useNavigation<StackNavigationProp<RootNavigatorParamList>>();
const getProfile = useGetProfile();
const showProfile = React.useCallback(() => {
navigation.navigate(ROUTES.PROFILE, {
visitingProfile: address,
});
}, [address, navigation]);

React.useEffect(() => {
(async () => {
if (profile === undefined) {
setProfileLoading(true);
const fetchedProfile = await getProfile(address);
if (fetchedProfile.isOk()) {
setToDisplayProfile(fetchedProfile.value);
}
setProfileLoading(false);
}
})();
}, [address, getProfile, profile]);

if (toDisplayProfile === undefined && profileLoading === false) {
return (
<View style={styles.root}>
<CopiableAddress address={address} />
</View>
);
}

return (
<TouchableOpacity onPress={showProfile}>
<View style={styles.root}>
<ProfileImage profile={toDisplayProfile} loading={profileLoading} size={32} />
<Spacer paddingLeft={8} />
{profileLoading ? (
<TypographyContentLoaders.Regular16 width={180} />
) : (
<Typography.Regular16>{getProfileDisplayName(toDisplayProfile!)}</Typography.Regular16>
)}
</View>
</TouchableOpacity>
);
};

export default InlineProfile;

const useStyles = makeStyle(() => ({
root: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
}));
4 changes: 4 additions & 0 deletions src/components/Messages/MessageDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { EncodeObject } from '@cosmjs/proto-signing';
import React from 'react';
import { messageDetailsComponents } from './components';
import MsgUnknownComponents from './MsgUnknown';
import MsgUpdateParamsDetails from './common/MsgUpdateParamsDetails';

export type MessageDetailsProps = {
/**
Expand All @@ -18,6 +19,9 @@ export type MessageDetailsProps = {

const MessageDetails: React.FC<MessageDetailsProps> = ({ message, toBroadcastMessage }) =>
React.useMemo(() => {
if (message.typeUrl.indexOf('MsgUpdateParams') > 0) {
return <MsgUpdateParamsDetails message={message} toBroadcastMessage={toBroadcastMessage} />;
}
const DetailsComponent = messageDetailsComponents[message.typeUrl] || MsgUnknownComponents;
return <DetailsComponent message={message} toBroadcastMessage={toBroadcastMessage} />;
}, [message, toBroadcastMessage]);
Expand Down
60 changes: 60 additions & 0 deletions src/components/Messages/common/MsgUpdateParamsDetails/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { EncodeObject } from '@desmoslabs/desmjs';
import { MessageDetailsComponent } from 'components/Messages/BaseMessage';
import BaseMessageDetails, {
MessageDetailsField,
} from 'components/Messages/BaseMessage/BaseMessageDetails';
import Typography from 'components/Typography';
import { formatCoin, formatCoins, isCoin } from 'lib/FormatUtils';
import React from 'react';
import { useTranslation } from 'react-i18next';

const MsgUpdateParamsDetails: MessageDetailsComponent<EncodeObject> = ({ message }) => {
const { t } = useTranslation('messages.common');
const moduleName = React.useMemo(() => {
const [, name] = message.typeUrl.split('.');
return `${name[0].toUpperCase()}${name.substring(1)}`;
}, [message.typeUrl]);

const fields = React.useMemo<MessageDetailsField[]>(() => {
const result: MessageDetailsField[] = [];
Object.keys(message.value.params).forEach((key) => {
const objectValue = message.value.params[key];
let serializedValue: string;

if (typeof objectValue === 'object') {
// Special case for the coin object.
if (isCoin(objectValue)) {
serializedValue = formatCoin(objectValue);
} else if (
Object.prototype.toString.call(objectValue) === '[object Array]' &&
isCoin(objectValue[0])
) {
// Special case for array of coins.
serializedValue = formatCoins(objectValue);
} else {
serializedValue = JSON.stringify(objectValue, undefined, 4);
}
} else if (objectValue === null || objectValue === undefined) {
serializedValue = 'null';
} else {
serializedValue = objectValue.toString();
}

result.push({
label: key,
value: serializedValue,
});
});
return result;
}, [message.value]);

return (
<BaseMessageDetails message={message} fields={fields}>
<Typography.Regular14>
{t('update module params', { module: moduleName })}
</Typography.Regular14>
</BaseMessageDetails>
);
};

export default MsgUpdateParamsDetails;
5 changes: 0 additions & 5 deletions src/components/Messages/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
MsgExecTypeUrl,
MsgSoftwareUpgradeTypeUrl,
MsgTransferTypeUrl,
MsgUpdateStakingModuleParamsTypeUrl,
SoftwareUpgradeProposalTypeUrl,
} from 'types/cosmos';
import MsgExecDetails from 'components/Messages/authz/MsgExecDetails';
Expand All @@ -26,7 +25,6 @@ import MsgDepositDetails from 'components/Messages/gov/MsgDepositDetails';
import MsgTransferDetails from 'components/Messages/ibc/MsgTransferDetails';
import MsgCreateValidatorDetails from 'components/Messages/staking/MsgCreateValidatorDetails';
import MsgEditValidatorDetails from 'components/Messages/staking/MsgEditValidatorDetails';
import MsgUpdateStakingModuleParams from 'components/Messages/staking/MsgUpdateParams';
import SoftwareUpgradeProposal from 'components/Messages/upgrade/v1beta1/SoftwareUpgradeProposal';
import MsgSoftwareUpgrade from 'components/Messages/upgrade/v1beta1/MsgSoftwareUpgrade';
import MsgMovePostDetails from 'components/Messages/posts/MsgMovePostDetails';
Expand Down Expand Up @@ -100,7 +98,6 @@ import MsgCreateDenomDetails from './tokenfactory/MsgCreateDenomDetails';
import MsgMintDetails from './tokenfactory/MsgMintDetails';
import MsgBurnDetails from './tokenfactory/MsgBurnDetails';
import MsgSetDenomMetadataDetails from './tokenfactory/MsgSetDenomMetadataDetails';
import MsgUpdateParamsDetails from './tokenfactory/MsgUpdateParamsDetails';

export const messageDetailsComponents: Record<string, MessageDetailsComponent<any>> = {
// x/authz
Expand Down Expand Up @@ -137,7 +134,6 @@ export const messageDetailsComponents: Record<string, MessageDetailsComponent<an
[Staking.v1beta1.MsgUndelegateTypeUrl]: MsgUndelegateComponentDetails,
[Staking.v1beta1.MsgCreateValidatorTypeUrl]: MsgCreateValidatorDetails,
[Staking.v1beta1.MsgEditValidatorTypeUrl]: MsgEditValidatorDetails,
[MsgUpdateStakingModuleParamsTypeUrl]: MsgUpdateStakingModuleParams,

// x/feegrant
[Feegrant.v1beta1.MsgGrantAllowanceTypeUrl]: MsgGrantAllowanceComponentDetails,
Expand Down Expand Up @@ -212,7 +208,6 @@ export const messageDetailsComponents: Record<string, MessageDetailsComponent<an
[TokenFactory.v1.MsgMintTypeUrl]: MsgMintDetails,
[TokenFactory.v1.MsgBurnTypeUrl]: MsgBurnDetails,
[TokenFactory.v1.MsgSetDenomMetadataTypeUrl]: MsgSetDenomMetadataDetails,
[TokenFactory.v1.MsgUpdateParamsTypeUrl]: MsgUpdateParamsDetails,

// x/upgrade
[SoftwareUpgradeProposalTypeUrl]: SoftwareUpgradeProposal,
Expand Down
66 changes: 0 additions & 66 deletions src/components/Messages/staking/MsgUpdateParams/index.tsx

This file was deleted.

This file was deleted.

12 changes: 12 additions & 0 deletions src/lib/FormatUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ export const formatCoins = (
separator: string = '\n',
): string => (amount || []).map(formatCoin).join(separator);

/**
* Checks if the given value is a Coin.
* @param value - The value to check.
*/
export const isCoin = (value: any): value is Coin =>
value &&
typeof value === 'object' &&
'denom' in value &&
'amount' in value &&
typeof value.denom === 'string' &&
typeof value.amount === 'string';

/**
* Formats the provided amount using the application's fiat representation style.
* @param amount - The amount to be formatted.
Expand Down
8 changes: 3 additions & 5 deletions src/lib/GraphQLUtils/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ import {
MsgCreateDenom,
MsgMint,
MsgSetDenomMetadata,
MsgUpdateParams,
} from '@desmoslabs/desmjs-types/desmos/tokenfactory/v1/msgs';
import { Metadata } from '@desmoslabs/desmjs-types/cosmos/bank/v1beta1/bank';

Expand Down Expand Up @@ -1591,13 +1590,12 @@ const decodeTokenFactoryMessages = (type: string, value: any): EncodeObject | un
case TokenFactory.v1.MsgUpdateParamsTypeUrl:
return {
typeUrl: TokenFactory.v1.MsgUpdateParamsTypeUrl,
value: MsgUpdateParams.fromPartial({
value: {
authority: value.authority,
params: {
defaultSendEnabled: value.params.default_send_enabled,
sendEnabled: value.params.send_enabled,
denomCreationFee: value.params.denom_creation_fee,
},
}),
},
};

default:
Expand Down
Loading

0 comments on commit cb6271a

Please sign in to comment.