From 0d5916734727c90dcce0abde8935daae48b0b535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Jur=C4=8Do?= <62913177+peterjurco@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:32:58 +0100 Subject: [PATCH] Rebrand Proposal details (#502) * Rebrand proposal details * Add testing capabilities * Fixes after the review * Use react components for error & success icons * Use useId() * Rename 'Target Address' to 'Target Contract Address' --- public/arrow-left.svg | 7 +- public/check-circle-fill.svg | 7 - public/check-circle.svg | 4 - public/error-circle-fill.svg | 7 - public/error-circle.svg | 4 - public/voted-against.svg | 7 +- public/voted-for.svg | 7 +- src/chain-data/context.tsx | 41 ++- .../button/variants/secondary.module.scss | 1 + src/components/icons/check-circle-fill.tsx | 12 +- src/components/icons/check-circle.tsx | 15 + src/components/icons/error-circle-fill.tsx | 29 ++ src/components/icons/error-circle.tsx | 15 + src/components/icons/index.ts | 18 +- src/components/layout/layout.module.scss | 2 +- src/components/timer/timer.module.scss | 66 ++-- .../proposal-details.module.scss | 148 ++++++--- .../proposal-details/proposal-details.tsx | 118 ++++--- .../proposal-list/proposal-list.module.scss | 1 - .../proposal-status.module.scss | 33 +- .../proposal-status/proposal-status.tsx | 8 +- .../proposal-tag/proposal-tag.module.scss | 2 + .../vote-slider/vote-slider.module.scss | 69 +++- .../vote-slider/vote-slider.tsx | 56 ++-- .../vote-status/vote-status.module.scss | 9 +- src/pages/proposals/test-proposals.json | 306 +++++++++--------- src/styles/fonts.module.scss | 20 +- src/styles/variables.module.scss | 2 +- src/utils/image-list.ts | 4 - 29 files changed, 635 insertions(+), 383 deletions(-) delete mode 100644 public/check-circle-fill.svg delete mode 100644 public/check-circle.svg delete mode 100644 public/error-circle-fill.svg delete mode 100644 public/error-circle.svg create mode 100644 src/components/icons/check-circle.tsx create mode 100644 src/components/icons/error-circle-fill.tsx create mode 100644 src/components/icons/error-circle.tsx diff --git a/public/arrow-left.svg b/public/arrow-left.svg index f083d3a5..f7887040 100644 --- a/public/arrow-left.svg +++ b/public/arrow-left.svg @@ -1,4 +1,3 @@ - - - - + + + \ No newline at end of file diff --git a/public/check-circle-fill.svg b/public/check-circle-fill.svg deleted file mode 100644 index 2887fd53..00000000 --- a/public/check-circle-fill.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/public/check-circle.svg b/public/check-circle.svg deleted file mode 100644 index 426a8af0..00000000 --- a/public/check-circle.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/public/error-circle-fill.svg b/public/error-circle-fill.svg deleted file mode 100644 index c157a577..00000000 --- a/public/error-circle-fill.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/public/error-circle.svg b/public/error-circle.svg deleted file mode 100644 index 7a498aab..00000000 --- a/public/error-circle.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/public/voted-against.svg b/public/voted-against.svg index e3106ff9..049ec407 100644 --- a/public/voted-against.svg +++ b/public/voted-against.svg @@ -1,3 +1,4 @@ - - - + + + + \ No newline at end of file diff --git a/public/voted-for.svg b/public/voted-for.svg index 42901994..5291cb94 100644 --- a/public/voted-for.svg +++ b/public/voted-for.svg @@ -1,3 +1,4 @@ - - - + + + + \ No newline at end of file diff --git a/src/chain-data/context.tsx b/src/chain-data/context.tsx index edc7d93c..11c3f1d9 100644 --- a/src/chain-data/context.tsx +++ b/src/chain-data/context.tsx @@ -1,9 +1,10 @@ import { createContext, useState, useMemo, useContext, ReactNode } from 'react'; -import { initialSettableChainData, initialChainData, SettableChainData } from './state'; +import { initialSettableChainData, initialChainData, SettableChainData, Proposal } from './state'; import { useAccount, useNetwork } from 'wagmi'; import { getDaoAddresses, updateNetworkName } from '../contracts'; import { useEthersProvider, useEthersSigner } from './adapters'; -import { ethers } from 'ethers'; +import { BigNumber, ethers } from 'ethers'; +import testProposals from '../pages/proposals/test-proposals.json'; export const ChainDataContext = createContext(initialSettableChainData); @@ -48,6 +49,41 @@ const ChainDataContextProvider = (props: { children: ReactNode }) => { export default ChainDataContextProvider; +const convertBigNumbersAndDates = (obj: any): any => { + if (Array.isArray(obj)) { + return obj.map(convertBigNumbersAndDates); + } else if (obj && typeof obj === 'object') { + return Object.keys(obj).reduce((acc, key) => { + const value = obj[key]; + if (value && value.type === 'BigNumber' && value.hex) { + acc[key] = BigNumber.from(value.hex); + } else if (key === 'deadline' || key === 'startDate') { + acc[key] = new Date(value); + } else { + acc[key] = convertBigNumbersAndDates(value); + } + return acc; + }, {} as any); + } + return obj; +}; + +const convertToProposals = ( + data: any +): { primary: { [key: string]: Proposal }; secondary: { [key: string]: Proposal } } => { + const convertSection = (section: { [key: string]: any }) => { + return Object.keys(section).reduce((acc, key) => { + acc[key] = convertBigNumbersAndDates(section[key]); + return acc; + }, {} as { [key: string]: Proposal }); + }; + + return { + primary: convertSection(data.primary), + secondary: convertSection(data.secondary), + }; +}; + export const useChainData = () => { const data = useContext(ChainDataContext); const { provider, signer } = useContext(ProviderSignerContext) || {}; @@ -59,6 +95,7 @@ export const useChainData = () => { const networkName = updateNetworkName(chain?.network || ''); return { ...data, + // proposals: convertToProposals(testProposals), isConnected: true, provider, signer, diff --git a/src/components/button/variants/secondary.module.scss b/src/components/button/variants/secondary.module.scss index 2bb7301b..7470a3d4 100644 --- a/src/components/button/variants/secondary.module.scss +++ b/src/components/button/variants/secondary.module.scss @@ -1,6 +1,7 @@ @mixin button-secondary { position: relative; color: $color-dark-blue-400; + background-color: $color-base-light; border: 1px solid transparent; @include gradient-border($gradient-soft-light); diff --git a/src/components/icons/check-circle-fill.tsx b/src/components/icons/check-circle-fill.tsx index 2688070d..0f344a5e 100644 --- a/src/components/icons/check-circle-fill.tsx +++ b/src/components/icons/check-circle-fill.tsx @@ -1,9 +1,13 @@ -import { ComponentProps } from 'react'; +import { ComponentProps, useId } from 'react'; + +export const CheckCircleFillIcon = (props: ComponentProps<'svg'>) => { + // Need to have unique id if we are using the icon multiple times + // https://stackoverflow.com/questions/70985078/when-i-display-none-one-svg-another-independent-svg-gets-rendered-differen + const maskId = useId(); -export const CheckCircleFillIcon = ({ ...props }: ComponentProps<'svg'>) => { return ( - + ) => { ); diff --git a/src/components/icons/check-circle.tsx b/src/components/icons/check-circle.tsx new file mode 100644 index 00000000..79cd6f92 --- /dev/null +++ b/src/components/icons/check-circle.tsx @@ -0,0 +1,15 @@ +import { ComponentProps } from 'react'; + +export const CheckCircleIcon = (props: ComponentProps<'svg'>) => { + return ( + + + + + ); +}; diff --git a/src/components/icons/error-circle-fill.tsx b/src/components/icons/error-circle-fill.tsx new file mode 100644 index 00000000..549f449d --- /dev/null +++ b/src/components/icons/error-circle-fill.tsx @@ -0,0 +1,29 @@ +import { ComponentProps, useId } from 'react'; + +export const ErrorCircleFillIcon = (props: ComponentProps<'svg'>) => { + // Need to have unique id if we are using the icon multiple times + // https://stackoverflow.com/questions/70985078/when-i-display-none-one-svg-another-independent-svg-gets-rendered-differen + const maskId = useId(); + return ( + + + + + + + + ); +}; diff --git a/src/components/icons/error-circle.tsx b/src/components/icons/error-circle.tsx new file mode 100644 index 00000000..f2647765 --- /dev/null +++ b/src/components/icons/error-circle.tsx @@ -0,0 +1,15 @@ +import { ComponentProps } from 'react'; + +export const ErrorCircleIcon = (props: ComponentProps<'svg'>) => { + return ( + + + + + ); +}; diff --git a/src/components/icons/index.ts b/src/components/icons/index.ts index 8ae524e9..815f1d5b 100644 --- a/src/components/icons/index.ts +++ b/src/components/icons/index.ts @@ -1,9 +1,23 @@ -import { CheckboxRadioIcon } from './checkbox-radio'; +import { CheckCircleIcon } from './check-circle'; import { CheckCircleFillIcon } from './check-circle-fill'; +import { CheckboxRadioIcon } from './checkbox-radio'; import { CheckIcon } from './check'; import { CloseIcon } from './close'; import { CrossIcon } from './cross'; +import { ErrorCircleIcon } from './error-circle'; +import { ErrorCircleFillIcon } from './error-circle-fill'; import { HelpOutlineIcon } from './help-outline'; import { InfoCircleIcon } from './info-circle'; -export { CheckCircleFillIcon, CheckboxRadioIcon, CheckIcon, CloseIcon, CrossIcon, HelpOutlineIcon, InfoCircleIcon }; +export { + CheckCircleIcon, + CheckCircleFillIcon, + CheckIcon, + CheckboxRadioIcon, + CloseIcon, + CrossIcon, + ErrorCircleIcon, + ErrorCircleFillIcon, + HelpOutlineIcon, + InfoCircleIcon, +}; diff --git a/src/components/layout/layout.module.scss b/src/components/layout/layout.module.scss index 3c711b87..80ee0011 100644 --- a/src/components/layout/layout.module.scss +++ b/src/components/layout/layout.module.scss @@ -16,7 +16,7 @@ } .main { - max-width: 468px; + max-width: 500px; width: 100%; box-sizing: border-box; padding: 0 16px; diff --git a/src/components/timer/timer.module.scss b/src/components/timer/timer.module.scss index 99667640..a72e681f 100644 --- a/src/components/timer/timer.module.scss +++ b/src/components/timer/timer.module.scss @@ -8,50 +8,60 @@ &.large { flex-direction: column; + align-items: flex-start; - @media (min-width: $min-md) { - align-items: flex-end; + .timerContainer { + width: 100%; + justify-content: center; + border-top: 1px solid $color-dark-blue-10; + border-bottom: 1px solid $color-dark-blue-10; + padding: 8px 0 12px 0; + margin: 8px 0; } - @media (max-width: $max-md) { - align-items: flex-start; + .status { + margin-left: 8px; } - @media (max-width: $max-xs) { - display: block; - } - - .timerContainer { - border-top: 1px solid $secondary-black-color; - border-bottom: 1px solid $secondary-black-color; - padding-top: $space-xs; - padding-bottom: 2px; - margin-top: $space-xxs; - margin-right: 0; - margin-left: 0; - - @media (max-width: $max-xs) { - justify-content: center; - } + .unit { + @include font-body-17; } + .timedOutNumber, .timerNumber { - font-size: $text-large; + @include font-heading-10; + padding: 4px 0; } .timerWrap { - min-width: 35px; + min-width: 40px; flex-direction: column; + gap: 0px; margin: 0; - - & > * { - margin-right: 0; - } + align-items: center; } .timerColon { - font-size: $text-large; - margin: 0 $space-xs; + @include font-heading-10; + margin: 0 4px; + } + + @media (min-width: $md) { + align-items: flex-end; + + .timedOutNumber, + .timerNumber { + @include font-heading-7; + } + + .unit { + @include font-tagline-12; + } + + .timerColon { + @include font-heading-7; + margin: 0 8px; + } } } } diff --git a/src/pages/proposal-commons/proposal-details/proposal-details.module.scss b/src/pages/proposal-commons/proposal-details/proposal-details.module.scss index 07fab4d1..6fabba12 100644 --- a/src/pages/proposal-commons/proposal-details/proposal-details.module.scss +++ b/src/pages/proposal-commons/proposal-details/proposal-details.module.scss @@ -1,22 +1,31 @@ @import '../../../styles/variables.module.scss'; +@import '../../../styles/fonts.module.scss'; .backBtn { - button { - color: $secondary-color; - text-decoration: none !important; + color: $color-dark-blue-50; + text-decoration: none !important; + @include font-body-15; + + img { + margin: 0 4px; + } + + @media (min-width: $md) { + @include font-body-9; + + img { + margin: 0 12px; + width: 12px; + height: 12px; + } } } .proposalDetailsSubheader { display: flex; align-items: center; - margin-bottom: $space-xs; - & > * { - margin-right: $space-md; - } - & > *:last-child { - margin-right: 0; - } + margin-bottom: 16px; + margin-top: 40px; } .scrollX { @@ -24,31 +33,34 @@ } .proposalDetailsHeader { - margin-bottom: $space-lg; + margin-bottom: 64px; - @media (min-width: $min-md) { + @media (min-width: $md) { display: flex; justify-content: space-between; } } .proposalDetailsTitle { - font-size: $heading-4; - line-height: $lh-heading-4; - font-weight: 600; - margin-right: $space-xl; - margin-bottom: $space-lg; + @include font-heading-7; + margin-bottom: 12px; - @media (max-width: $max-md) { - font-size: $heading-5; - line-height: $lh-heading-5; - margin-right: 0; + @media (min-width: $sm) { + @include font-heading-6; + margin-top: 24px; + } + + @media (min-width: $md) { + @include font-heading-4; + margin-right: 24px; } } .proposalTag { - @media (max-width: $max-md) { - margin-bottom: $space-xl; + margin-bottom: 32px; + + @media (min-width: $md) { + margin-bottom: 0; } } @@ -56,32 +68,46 @@ display: flex; flex-direction: column; - @media (min-width: $min-md) { + @media (min-width: $md) { align-items: flex-end; } } .proposalDetailsVoteSection { text-align: center; - margin: $space-lg 2 * $space-xxl 2 * $space-xl; + margin: 24px 32px; + + button { + width: 110px; + } - & > *:last-child { - margin-top: $space-xl; + @media (min-width: $sm) { + display: flex; + flex-direction: column; + align-items: center; } - @media (max-width: $max-md) { - margin-right: 0; - margin-left: 0; + @media (min-width: $md) { + button { + width: 150px; + } } } -.proposalDetailsSummary { - margin: 0 2 * $space-xl 2 * $space-xl; +.summary { + margin: 80px 0 40px 0; - @media (max-width: $max-md) { - font-size: $text-small; - line-height: $lh-small; - margin: 0 0 $space-xxl; + h5 { + @include font-heading-9; + margin-bottom: 32px; + + @media (min-width: $sm) { + @include font-heading-8; + } + + @media (min-width: $md) { + @include font-heading-7; + } } } @@ -92,30 +118,52 @@ } .proposalDetailsItem { - margin-top: $space-xl; - & > * { - margin-bottom: $space-xxs; - } - & > *:last-child { - margin-bottom: 0; + overflow-wrap: break-word; + @include font-body-9; + + @media (min-width: $md) { + @include font-body-6; } } -.multiline { - white-space: pre-wrap; +.label { + @include font-body-7; + color: $color-dark-blue-50; + margin-top: 24px; + + @media (min-width: $md) { + margin-top: 32px; + } } .voteButtonHelperText { - color: $error-color; - font-size: $text-small; - margin-top: $space-md; + color: $color-action-error-800; + margin-top: 16px; } .link { - font-size: $text-normal; + color: $color-dark-blue-500; +} - @media (max-width: $max-md) { - font-size: $text-small; +.voteButtonWrapper { + display: flex; + justify-content: center; + align-items: center; + padding-left: 24px; //offset for the help icon on the right + + @media (min-width: $md) { + padding-left: 28px; + } +} + +.helpIcon { + margin-left: 8px; + width: 16px; + height: 16px; + + @media (min-width: $md) { + width: 20px; + height: 20px; } } diff --git a/src/pages/proposal-commons/proposal-details/proposal-details.tsx b/src/pages/proposal-commons/proposal-details/proposal-details.tsx index 4a489598..4d57ba06 100644 --- a/src/pages/proposal-commons/proposal-details/proposal-details.tsx +++ b/src/pages/proposal-commons/proposal-details/proposal-details.tsx @@ -13,14 +13,12 @@ import Timer from '../../../components/timer'; import Button from '../../../components/button'; import ProposalTag from '../proposal-tag'; import { TooltipChecklist } from '../../../components/tooltip'; -import BorderedBox, { Header } from '../../../components/bordered-box/bordered-box'; import { getEtherscanAddressUrl, useApi3AgentAddresses, useApi3Voting } from '../../../contracts'; import { decodeProposalTypeAndVoteId, isEvmScriptValid } from '../../../logic/proposals/encoding'; import { proposalDetailsSelector, voteSliderSelector } from '../../../logic/proposals/selectors'; import { useProposalById } from '../../../logic/proposals/hooks'; import VoteForm from './vote-form/vote-form'; import ProposalStatus from '../proposal-list/proposal-status'; -import globalStyles from '../../../styles/global-styles.module.scss'; import styles from './proposal-details.module.scss'; import { canVoteSelector } from '../../../logic/proposals/selectors'; import NotFoundPage from '../../not-found'; @@ -113,7 +111,7 @@ const ProposalDetailsContent = (props: ProposalDetailsProps) => { const urlCreator = getEtherscanAddressUrl(chainId, proposal.creator); const urlTargetAddress = getEtherscanAddressUrl(chainId, decodedEvmScript.targetAddress); const backButton = { - text: `Back to ${proposal.open ? 'Governance' : 'History'}`, + text: 'Back', url: proposal.open ? '/governance' : '/history', }; @@ -137,7 +135,7 @@ const ProposalDetailsContent = (props: ProposalDetailsProps) => { return (
- + - voting help + voting help
{proposal.delegateAt && ( @@ -203,64 +207,54 @@ const ProposalDetailsContent = (props: ProposalDetailsProps) => { />
- -
Summary
- - } - content={ -
-
-              
-            
- -
-

Creator

-

- {urlCreator ? ( - - - - ) : ( - proposal.creator - )} -

-
-
-

Target Address

-

- {urlTargetAddress ? ( - - - - ) : ( - decodedEvmScript.targetAddress - )} -

-
- {proposal.metadata.targetSignature && ( -
-

Target Contract Signature

-

{proposal.metadata.targetSignature}

-
+ +
+
Summary
+
+ +
+ +
+

Creator

+

+ {urlCreator ? ( + + + + ) : ( + proposal.creator )} - {decodedEvmScript.value.gt(0) && ( -

-

Value (Wei)

-

{decodedEvmScript.value.toString()}

-
+

+
+
+

Target Contract Address

+

+ {urlTargetAddress ? ( + + + + ) : ( + decodedEvmScript.targetAddress )} -

-

Parameters

-
- -
-
+

+
+ {proposal.metadata.targetSignature && ( +
+

Target Contract Signature

+

{proposal.metadata.targetSignature}

- } - noMobileBorders - /> + )} + {decodedEvmScript.value.gt(0) && ( +
+

Value (Wei)

+

{decodedEvmScript.value.toString()}

+
+ )} +
+

Parameters

+ +
+
); }; diff --git a/src/pages/proposal-commons/proposal-list/proposal-list.module.scss b/src/pages/proposal-commons/proposal-list/proposal-list.module.scss index 0c7ac76c..1ec01469 100644 --- a/src/pages/proposal-commons/proposal-list/proposal-list.module.scss +++ b/src/pages/proposal-commons/proposal-list/proposal-list.module.scss @@ -92,7 +92,6 @@ .proposalItemTag { margin-left: 16px; color: $color-dark-blue-400; - cursor: default; } .proposalVoteBar { diff --git a/src/pages/proposal-commons/proposal-list/proposal-status/proposal-status.module.scss b/src/pages/proposal-commons/proposal-list/proposal-status/proposal-status.module.scss index 53e5a679..e3312459 100644 --- a/src/pages/proposal-commons/proposal-list/proposal-status/proposal-status.module.scss +++ b/src/pages/proposal-commons/proposal-list/proposal-status/proposal-status.module.scss @@ -1,4 +1,5 @@ @import '../../../../styles/variables.module.scss'; +@import '../../../../styles/fonts.module.scss'; .proposalStatus { text-align: center; @@ -25,20 +26,40 @@ align-items: center; margin-right: $space-xs; - img { + svg { margin-top: 0; } } .execute { - button { - padding: 0; - font-size: 18px; - line-height: 28px; - } + padding: 0; + font-size: 18px; + line-height: 28px; } .flex { display: flex; align-items: center; } + +.large { + @include font-subtitle-8; + + &.flex { + justify-content: center; + } + + .icon svg { + width: 20px; + height: 20px; + } + + @media (min-width: $md) { + @include font-subtitle-2; + + .icon svg { + width: 27px; + height: 27px; + } + } +} diff --git a/src/pages/proposal-commons/proposal-list/proposal-status/proposal-status.tsx b/src/pages/proposal-commons/proposal-list/proposal-status/proposal-status.tsx index e5ec8808..f8b47316 100644 --- a/src/pages/proposal-commons/proposal-list/proposal-status/proposal-status.tsx +++ b/src/pages/proposal-commons/proposal-list/proposal-status/proposal-status.tsx @@ -25,6 +25,7 @@ const ProposalStatus = (props: Props) => { className={classNames(styles.proposalStatus, { [styles.failing]: proposalStatus === 'Failing', [styles.passing]: proposalStatus === 'Passing', + [styles.large]: large, })} > {proposalStatus} @@ -35,17 +36,18 @@ const ProposalStatus = (props: Props) => { const showIcon = proposalStatus === 'Executed' || proposalStatus === 'Rejected'; return ( -
+
{showIcon && ( - {proposalStatus === 'Rejected' && } - {proposalStatus === 'Executed' && } + {proposalStatus === 'Rejected' && } + {proposalStatus === 'Executed' && } )} {proposalStatus === 'Execute' ? (