diff --git a/apps/web-stratos/src/graphql/general/params.graphql b/apps/web-stratos/src/graphql/general/params.graphql index b5415d40e3..857ff3a799 100644 --- a/apps/web-stratos/src/graphql/general/params.graphql +++ b/apps/web-stratos/src/graphql/general/params.graphql @@ -11,9 +11,9 @@ query Params { distributionParams: distribution_params(limit: 1, order_by: {height: desc}) { params } - govParams: gov_params (limit: 1, order_by: {height: desc}) { - depositParams: deposit_params - tallyParams: tally_params - votingParams: voting_params + govParams: gov_params(limit: 1, order_by: {height: desc}) { + one_row_id + params + height } } diff --git a/apps/web-stratos/src/graphql/general/proposal_details.graphql b/apps/web-stratos/src/graphql/general/proposal_details.graphql index 82ddef911b..e94fb744df 100644 --- a/apps/web-stratos/src/graphql/general/proposal_details.graphql +++ b/apps/web-stratos/src/graphql/general/proposal_details.graphql @@ -7,7 +7,7 @@ query ProposalDetails($proposalId: Int) { content proposalId: id submitTime: submit_time - proposalType: proposal_type + #proposalType: proposal_type depositEndTime: deposit_end_time votingStartTime: voting_start_time votingEndTime: voting_end_time @@ -25,7 +25,7 @@ query ProposalDetailsTally($proposalId: Int) { bondedTokens: bonded_tokens } quorum: gov_params (limit: 1, order_by: {height: desc}) { - tallyParams: tally_params + tallyParams: params } } diff --git a/apps/web-stratos/src/graphql/types/general_types.ts b/apps/web-stratos/src/graphql/types/general_types.ts index 55bf8e6cd0..7e5fe19906 100644 --- a/apps/web-stratos/src/graphql/types/general_types.ts +++ b/apps/web-stratos/src/graphql/types/general_types.ts @@ -2444,28 +2444,14 @@ export type Genesis_Variance_Fields = { /** columns and relationships of "gov_params" */ export type Gov_Params = { __typename?: 'gov_params'; - deposit_params: Scalars['jsonb']; height: Scalars['bigint']; one_row_id: Scalars['Boolean']; - tally_params: Scalars['jsonb']; - voting_params: Scalars['jsonb']; -}; - - -/** columns and relationships of "gov_params" */ -export type Gov_ParamsDeposit_ParamsArgs = { - path?: InputMaybe; -}; - - -/** columns and relationships of "gov_params" */ -export type Gov_ParamsTally_ParamsArgs = { - path?: InputMaybe; + params: Scalars['jsonb']; }; /** columns and relationships of "gov_params" */ -export type Gov_ParamsVoting_ParamsArgs = { +export type Gov_ParamsParamsArgs = { path?: InputMaybe; }; @@ -2510,11 +2496,9 @@ export type Gov_Params_Bool_Exp = { _and?: InputMaybe>; _not?: InputMaybe; _or?: InputMaybe>; - deposit_params?: InputMaybe; height?: InputMaybe; one_row_id?: InputMaybe; - tally_params?: InputMaybe; - voting_params?: InputMaybe; + params?: InputMaybe; }; /** aggregate max on columns */ @@ -2531,25 +2515,19 @@ export type Gov_Params_Min_Fields = { /** Ordering options when selecting data from "gov_params". */ export type Gov_Params_Order_By = { - deposit_params?: InputMaybe; height?: InputMaybe; one_row_id?: InputMaybe; - tally_params?: InputMaybe; - voting_params?: InputMaybe; + params?: InputMaybe; }; /** select columns of table "gov_params" */ export enum Gov_Params_Select_Column { - /** column name */ - DepositParams = 'deposit_params', /** column name */ Height = 'height', /** column name */ OneRowId = 'one_row_id', /** column name */ - TallyParams = 'tally_params', - /** column name */ - VotingParams = 'voting_params' + Params = 'params' } /** aggregate stddev on columns */ @@ -3421,18 +3399,17 @@ export type Proposal = { deposit_end_time?: Maybe; description: Scalars['String']; id: Scalars['Int']; + metadata: Scalars['String']; /** An array relationship */ proposal_deposits: Array; /** An aggregate relationship */ proposal_deposits_aggregate: Proposal_Deposit_Aggregate; - proposal_route: Scalars['String']; /** An object relationship */ proposal_tally_result?: Maybe; /** An array relationship */ proposal_tally_results: Array; /** An aggregate relationship */ proposal_tally_results_aggregate: Proposal_Tally_Result_Aggregate; - proposal_type: Scalars['String']; /** An array relationship */ proposal_votes: Array; /** An aggregate relationship */ @@ -3604,11 +3581,10 @@ export type Proposal_Bool_Exp = { deposit_end_time?: InputMaybe; description?: InputMaybe; id?: InputMaybe; + metadata?: InputMaybe; proposal_deposits?: InputMaybe; - proposal_route?: InputMaybe; proposal_tally_result?: InputMaybe; proposal_tally_results?: InputMaybe; - proposal_type?: InputMaybe; proposal_votes?: InputMaybe; proposer?: InputMaybe; proposer_address?: InputMaybe; @@ -3858,8 +3834,7 @@ export type Proposal_Max_Fields = { deposit_end_time?: Maybe; description?: Maybe; id?: Maybe; - proposal_route?: Maybe; - proposal_type?: Maybe; + metadata?: Maybe; proposer_address?: Maybe; status?: Maybe; submit_time?: Maybe; @@ -3873,8 +3848,7 @@ export type Proposal_Max_Order_By = { deposit_end_time?: InputMaybe; description?: InputMaybe; id?: InputMaybe; - proposal_route?: InputMaybe; - proposal_type?: InputMaybe; + metadata?: InputMaybe; proposer_address?: InputMaybe; status?: InputMaybe; submit_time?: InputMaybe; @@ -3889,8 +3863,7 @@ export type Proposal_Min_Fields = { deposit_end_time?: Maybe; description?: Maybe; id?: Maybe; - proposal_route?: Maybe; - proposal_type?: Maybe; + metadata?: Maybe; proposer_address?: Maybe; status?: Maybe; submit_time?: Maybe; @@ -3904,8 +3877,7 @@ export type Proposal_Min_Order_By = { deposit_end_time?: InputMaybe; description?: InputMaybe; id?: InputMaybe; - proposal_route?: InputMaybe; - proposal_type?: InputMaybe; + metadata?: InputMaybe; proposer_address?: InputMaybe; status?: InputMaybe; submit_time?: InputMaybe; @@ -3920,11 +3892,10 @@ export type Proposal_Order_By = { deposit_end_time?: InputMaybe; description?: InputMaybe; id?: InputMaybe; + metadata?: InputMaybe; proposal_deposits_aggregate?: InputMaybe; - proposal_route?: InputMaybe; proposal_tally_result?: InputMaybe; proposal_tally_results_aggregate?: InputMaybe; - proposal_type?: InputMaybe; proposal_votes_aggregate?: InputMaybe; proposer?: InputMaybe; proposer_address?: InputMaybe; @@ -3950,9 +3921,7 @@ export enum Proposal_Select_Column { /** column name */ ProposalRoute = 'proposal_route', /** column name */ - ProposalType = 'proposal_type', - /** column name */ - ProposerAddress = 'proposer_address', + Metadata = 'metadata', /** column name */ Status = 'status', /** column name */ @@ -11331,14 +11300,14 @@ export type OnlineVotingPowerQuery = { activeTotal: { __typename?: 'validator_st export type ParamsQueryVariables = Exact<{ [key: string]: never; }>; -export type ParamsQuery = { stakingParams: Array<{ __typename?: 'staking_params', params: any }>, slashingParams: Array<{ __typename?: 'slashing_params', params: any }>, mintParams: Array<{ __typename?: 'mint_params', params: any }>, distributionParams: Array<{ __typename?: 'distribution_params', params: any }>, govParams: Array<{ __typename?: 'gov_params', depositParams: any, tallyParams: any, votingParams: any }> }; +export type ParamsQuery = { stakingParams: Array<{ __typename?: 'staking_params', params: any }>, slashingParams: Array<{ __typename?: 'slashing_params', params: any }>, mintParams: Array<{ __typename?: 'mint_params', params: any }>, distributionParams: Array<{ __typename?: 'distribution_params', params: any }>, govParams: Array<{ __typename?: 'gov_params', one_row_id: boolean, params: any, height: any }> }; export type ProposalDetailsQueryVariables = Exact<{ proposalId?: InputMaybe; }>; -export type ProposalDetailsQuery = { proposal: Array<{ __typename?: 'proposal', title: string, description: string, status?: string | null, content: any, proposer: string, proposalId: number, submitTime: any, proposalType: string, depositEndTime?: any | null, votingStartTime?: any | null, votingEndTime?: any | null }> }; +export type ProposalDetailsQuery = { proposal: Array<{ __typename?: 'proposal', title: string, description: string, status?: string | null, content: any, proposer: string, proposalId: number, submitTime: any, depositEndTime?: any | null, votingStartTime?: any | null, votingEndTime?: any | null }> }; export type ProposalDetailsTallyQueryVariables = Exact<{ proposalId?: InputMaybe; @@ -12330,9 +12299,9 @@ export const ParamsDocument = gql` params } govParams: gov_params(limit: 1, order_by: {height: desc}) { - depositParams: deposit_params - tallyParams: tally_params - votingParams: voting_params + one_row_id + params + height } } `; @@ -12373,7 +12342,6 @@ export const ProposalDetailsDocument = gql` content proposalId: id submitTime: submit_time - proposalType: proposal_type depositEndTime: deposit_end_time votingStartTime: voting_start_time votingEndTime: voting_end_time @@ -12424,7 +12392,7 @@ export const ProposalDetailsTallyDocument = gql` bondedTokens: bonded_tokens } quorum: gov_params(limit: 1, order_by: {height: desc}) { - tallyParams: tally_params + tallyParams: params } } `; diff --git a/apps/web-stratos/src/models/gov_params/index.ts b/apps/web-stratos/src/models/gov_params/index.ts new file mode 100644 index 0000000000..edc43eae95 --- /dev/null +++ b/apps/web-stratos/src/models/gov_params/index.ts @@ -0,0 +1,74 @@ +import * as R from 'ramda'; + +class GovParams { + public depositParams: { + minDeposit: Array<{ + denom: string; + amount: string; + }>; + maxDepositPeriod: number; + }; + + public tallyParams: { + quorum: string; + threshold: string; + vetoThreshold: string; + }; + + public votingParams: { + votingPeriod: number; + }; + + constructor(payload: object) { + this.depositParams = R.pathOr( + { + minDeposit: [], + maxDepositPeriod: 0, + }, + ['depositParams'], + payload + ); + this.tallyParams = R.pathOr( + { + quorum: '', + threshold: '', + vetoThreshold: '', + }, + ['tallyParams'], + payload + ); + this.votingParams = R.pathOr( + { + votingPeriod: 0, + }, + ['votingParams'], + payload + ); + } + + static fromJson(data: object): GovParams { + return { + depositParams: { + minDeposit: R.pathOr( + [], + ['params', 'min_deposit'], + data + ).map((x) => ({ + denom: x.denom, + amount: String(x.amount), + })), + maxDepositPeriod: R.pathOr(0, ['params', 'max_deposit_period'], data), + }, + tallyParams: { + quorum: R.pathOr('0', ['params', 'quorum'], data), + threshold: R.pathOr('0', ['params', 'threshold'], data), + vetoThreshold: R.pathOr('0', ['params', 'veto_threshold'], data), + }, + votingParams: { + votingPeriod: R.pathOr(0, ['params', 'voting_period'], data), + }, + }; + } +} + +export default GovParams; diff --git a/apps/web-stratos/src/screens/proposal_details/components/overview/index.tsx b/apps/web-stratos/src/screens/proposal_details/components/overview/index.tsx new file mode 100644 index 0000000000..0e967c65f7 --- /dev/null +++ b/apps/web-stratos/src/screens/proposal_details/components/overview/index.tsx @@ -0,0 +1,161 @@ +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, 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 ParamsChange from '@/screens/proposal_details/components/overview/components/params_change'; +import SoftwareUpgrade from '@/screens/proposal_details/components/overview/components/software_upgrade'; +import useStyles from '@/screens/proposal_details/components/overview/styles'; +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'; + +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 type = + R.pathOr('', [0, '@type'], overview.content) === '' + ? getProposalType(R.pathOr('', ['@type'], overview.content)) + : getProposalType(R.pathOr('', [0, '@type'], 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; + if (type === 'parameterChangeProposal') { + extraDetails = ( + <> + + {t('changes')} + + + + ); + } else if (type === 'softwareUpgradeProposal') { + extraDetails = ( + <> + + {t('plan')} + + + + ); + } else if (type === 'communityPoolSpendProposal') { + extraDetails = ( + <> + + {t('content')} + + + + ); + } + + return extraDetails; + }, [overview.content, parsedAmountRequested, recipientMoniker, t, type]); + + const extra = getExtraDetails(); + + return ( + + + +
+ + {t('type')} + + + {t(type)} + + + {t('proposer')} + + + {!!overview.submitTime && ( + <> + + {t('submitTime')} + + + {formatDayJs(dayjs.utc(overview.submitTime), dateFormat, timeFormat)} + + + )} + {!!overview.depositEndTime && ( + <> + + {t('depositEndTime')} + + + {formatDayJs(dayjs.utc(overview.depositEndTime), dateFormat, timeFormat)} + + + )} + {!!overview.votingStartTime && ( + <> + + {t('votingStartTime')} + + + {formatDayJs(dayjs.utc(overview.votingStartTime), dateFormat, timeFormat)} + + + )} + {!!overview.votingEndTime && ( + <> + + {t('votingEndTime')} + + + {formatDayJs(dayjs.utc(overview.votingEndTime), dateFormat, timeFormat)} + + + )} + + {t('description')} + + + {extra} +
+
+ ); +}; + +export default Overview; diff --git a/apps/web-stratos/src/screens/proposal_details/components/votes_graph/hooks.ts b/apps/web-stratos/src/screens/proposal_details/components/votes_graph/hooks.ts new file mode 100644 index 0000000000..f086ae829d --- /dev/null +++ b/apps/web-stratos/src/screens/proposal_details/components/votes_graph/hooks.ts @@ -0,0 +1,71 @@ +import Big from 'big.js'; +import { useRouter } from 'next/router'; +import * as R from 'ramda'; +import { useCallback, useState } from 'react'; +import chainConfig from '@/chainConfig'; +import { + ProposalDetailsTallyQuery, + useProposalDetailsTallyQuery, +} from '@/graphql/types/general_types'; +import type { VotesGraphState } from '@/screens/proposal_details/components/votes_graph/types'; +import { formatToken } from '@/utils/format_token'; + +const { votingPowerTokenUnit } = chainConfig(); + +const defaultTokenUnit: TokenUnit = { + value: '0', + baseDenom: '', + displayDenom: '', + exponent: 0, +}; + +export const useVotesGraph = () => { + const router = useRouter(); + const [state, setState] = useState({ + votes: { + yes: defaultTokenUnit, + no: defaultTokenUnit, + abstain: defaultTokenUnit, + veto: defaultTokenUnit, + }, + bonded: defaultTokenUnit, + quorum: '0', + }); + + const handleSetState = useCallback( + (stateChange: (prevState: VotesGraphState) => VotesGraphState) => { + setState((prevState) => { + const newState = stateChange(prevState); + return R.equals(prevState, newState) ? prevState : newState; + }); + }, + [] + ); + + useProposalDetailsTallyQuery({ + variables: { + proposalId: parseFloat((router?.query?.id as string) ?? '0'), + }, + onCompleted: (data) => { + handleSetState((prevState) => ({ ...prevState, ...foramtProposalTally(data) })); + }, + }); + + const foramtProposalTally = (data: ProposalDetailsTallyQuery) => { + const quorumRaw = data.quorum?.[0]?.tallyParams?.quorum ?? '0'; + return { + votes: { + yes: formatToken(data?.proposalTallyResult?.[0]?.yes ?? '0', votingPowerTokenUnit), + no: formatToken(data?.proposalTallyResult?.[0]?.no ?? '0', votingPowerTokenUnit), + veto: formatToken(data?.proposalTallyResult?.[0]?.noWithVeto ?? '0', votingPowerTokenUnit), + abstain: formatToken(data?.proposalTallyResult?.[0]?.abstain ?? '0', votingPowerTokenUnit), + }, + bonded: formatToken(data?.stakingPool?.[0]?.bondedTokens ?? '0', votingPowerTokenUnit), + quorum: Big(quorumRaw)?.times(100).toFixed(2), + }; + }; + + return { + state, + }; +}; diff --git a/apps/web-stratos/src/screens/proposal_details/hooks.ts b/apps/web-stratos/src/screens/proposal_details/hooks.ts new file mode 100644 index 0000000000..43de7d33f2 --- /dev/null +++ b/apps/web-stratos/src/screens/proposal_details/hooks.ts @@ -0,0 +1,95 @@ +import { useRouter } from 'next/router'; +import * as R from 'ramda'; +import { useCallback, useState } from 'react'; +import type { ProposalState } from '@/screens/proposal_details/types'; +import { ProposalDetailsQuery, useProposalDetailsQuery } from '@/graphql/types/general_types'; + +// ========================= +// overview +// ========================= +const formatOverview = (data: ProposalDetailsQuery) => { + const DEFAULT_TIME = '0001-01-01T00:00:00'; + let votingStartTime = data?.proposal?.[0]?.votingStartTime ?? DEFAULT_TIME; + votingStartTime = votingStartTime === DEFAULT_TIME ? '' : votingStartTime; + let votingEndTime = data?.proposal?.[0]?.votingEndTime ?? DEFAULT_TIME; + votingEndTime = votingEndTime === DEFAULT_TIME ? '' : votingEndTime; + + const overview = { + proposer: data?.proposal?.[0]?.proposer ?? '', + content: data?.proposal?.[0]?.content ?? '', + title: data?.proposal?.[0]?.title ?? '', + id: data?.proposal?.[0]?.proposalId ?? '', + description: data?.proposal?.[0]?.description ?? '', + status: data?.proposal?.[0]?.status ?? '', + submitTime: data?.proposal?.[0]?.submitTime ?? '', + depositEndTime: data?.proposal?.[0]?.depositEndTime ?? '', + votingStartTime, + votingEndTime, + }; + + return overview; +}; + +// ========================== +// parsers +// ========================== +const formatProposalQuery = (data: ProposalDetailsQuery) => { + const stateChange: Partial = { + loading: false, + }; + + if (!data.proposal.length) { + stateChange.exists = false; + return stateChange; + } + + stateChange.overview = formatOverview(data); + + return stateChange; +}; + +export const useProposalDetails = () => { + const router = useRouter(); + const [state, setState] = useState({ + loading: true, + exists: true, + overview: { + proposer: '', + content: { + recipient: '', + amount: [], + }, + title: '', + id: 0, + description: '', + status: '', + submitTime: '', + depositEndTime: '', + votingStartTime: '', + votingEndTime: '', + }, + }); + + const handleSetState = useCallback((stateChange: (prevState: ProposalState) => ProposalState) => { + setState((prevState) => { + const newState = stateChange(prevState); + return R.equals(prevState, newState) ? prevState : newState; + }); + }, []); + + // ========================== + // fetch data + // ========================== + useProposalDetailsQuery({ + variables: { + proposalId: parseFloat((router?.query?.id as string) ?? '0'), + }, + onCompleted: (data) => { + handleSetState((prevState) => ({ ...prevState, ...formatProposalQuery(data) })); + }, + }); + + return { + state, + }; +}; diff --git a/apps/web-stratos/src/screens/proposal_details/types.ts b/apps/web-stratos/src/screens/proposal_details/types.ts new file mode 100644 index 0000000000..2055522c4b --- /dev/null +++ b/apps/web-stratos/src/screens/proposal_details/types.ts @@ -0,0 +1,24 @@ +export interface OverviewType { + title: string; + id: number; + proposer: string; + description: string; + status: string; + submitTime: string; + depositEndTime: string; + votingStartTime: string | null; + votingEndTime: string | null; + content: { + recipient: string; + amount: Array<{ + amount: string; + denom: string; + }>; + }; +} + +export interface ProposalState { + loading: boolean; + exists: boolean; + overview: OverviewType; +}