Skip to content

Commit

Permalink
feat: Vote rationale for whales (#1703)
Browse files Browse the repository at this point in the history
* vote reason setup

* added vote reason friction

* rationale proposal section

* check vp threshold

* threshold moved to env var

* requested changes

* added reason on vote modal

* requested changes

* fix: rationale extra info

* fix: modal

* added tooltip to comment timestamp

* label rename
  • Loading branch information
ncomerci authored Mar 12, 2024
1 parent 5649981 commit 244203d
Show file tree
Hide file tree
Showing 37 changed files with 515 additions and 126 deletions.
18 changes: 7 additions & 11 deletions src/back/services/discord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { isGovernanceProcessProposal, proposalUrl } from '../../entities/Proposa
import UpdateModel from '../../entities/Updates/model'
import { UpdateAttributes } from '../../entities/Updates/types'
import { getPublicUpdates, getUpdateNumber, getUpdateUrl } from '../../entities/Updates/utils'
import { capitalizeFirstLetter, getEnumDisplayName, inBackground } from '../../helpers'
import { capitalizeFirstLetter, getEnumDisplayName, inBackground, shortenText } from '../../helpers'
import { ErrorService } from '../../services/ErrorService'
import { getProfile } from '../../utils/Catalyst'
import { ErrorCategory } from '../../utils/errorCategories'
Expand Down Expand Up @@ -55,10 +55,6 @@ function getChoices(choices: string[]): Field[] {
}))
}

function getPreviewText(text: string) {
return text.length > PREVIEW_MAX_LENGTH ? text.slice(0, PREVIEW_MAX_LENGTH) + '...' : text
}

export class DiscordService {
private static client: Client
static init() {
Expand Down Expand Up @@ -124,7 +120,7 @@ export class DiscordService {
if (!!proposalType && !!description) {
const embedDescription = !isGovernanceProcessProposal(proposalType)
? description.split('\n')[0]
: getPreviewText(description)
: shortenText(description, PREVIEW_MAX_LENGTH)

fields.push({
name: getEnumDisplayName(proposalType),
Expand Down Expand Up @@ -231,11 +227,11 @@ export class DiscordService {
url: getUpdateUrl(updateId, proposalId),
title,
fields: [
{ name: 'Project Health', value: getPreviewText(health) },
{ name: 'Introduction', value: getPreviewText(introduction) },
{ name: 'Highlights', value: getPreviewText(highlights) },
{ name: 'Blockers', value: getPreviewText(blockers) },
{ name: 'Next Steps', value: getPreviewText(next_steps) },
{ name: 'Project Health', value: shortenText(health, PREVIEW_MAX_LENGTH) },
{ name: 'Introduction', value: shortenText(introduction, PREVIEW_MAX_LENGTH) },
{ name: 'Highlights', value: shortenText(highlights, PREVIEW_MAX_LENGTH) },
{ name: 'Blockers', value: shortenText(blockers, PREVIEW_MAX_LENGTH) },
{ name: 'Next Steps', value: shortenText(next_steps, PREVIEW_MAX_LENGTH) },
],
user,
action,
Expand Down
25 changes: 18 additions & 7 deletions src/clients/SnapshotApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ export type SnapshotReceipt = {
}
}

type CastVote = {
account: Web3Provider | Wallet
address: string
proposalSnapshotId: string
choiceNumber: number
metadata?: string
reason?: string
}

export class SnapshotApi {
static Url = process.env.GATSBY_SNAPSHOT_API || 'https://hub.snapshot.org'

Expand Down Expand Up @@ -136,19 +145,21 @@ export class SnapshotApi {
)) as SnapshotReceipt
}

async castVote(
account: Web3Provider | Wallet,
address: string,
proposalSnapshotId: string,
choiceNumber: number,
metadata?: string
): Promise<SnapshotReceipt> {
async castVote({
account,
address,
proposalSnapshotId,
choiceNumber,
metadata,
reason,
}: CastVote): Promise<SnapshotReceipt> {
const voteMessage: Vote = {
space: SnapshotApi.getSpaceName(),
proposal: proposalSnapshotId,
type: SNAPSHOT_PROPOSAL_TYPE,
choice: choiceNumber,
metadata,
reason,
app: SNAPSHOT_APP_NAME,
}
return (await this.client.vote(account, address, voteMessage)) as SnapshotReceipt
Expand Down
1 change: 1 addition & 0 deletions src/clients/SnapshotGraphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export class SnapshotGraphql extends API {
metadata
vp
vp_by_strategy
reason
}
}
`
Expand Down
2 changes: 2 additions & 0 deletions src/clients/SnapshotTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type SnapshotVote = {
vp_by_strategy?: number[]
choice: number
metadata?: Record<string, unknown>
reason?: string
proposal?: {
id: string
title?: string
Expand Down Expand Up @@ -81,6 +82,7 @@ export type SnapshotProposal = {
space: SnapshotSpace
strategies?: SnapshotStrategy[]
discussion: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
plugins: any
}

Expand Down
13 changes: 13 additions & 0 deletions src/components/Comments/Comment.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,16 @@
.Comment .Comment__Content {
width: 100%;
}

.Comment__AuthorText {
margin: 0 !important;
}

.Comment__ExtraInfo {
margin: 0 !important;
flex: none !important;
color: var(--secondary-text) !important;
}
.Comment__ExtraInfo.Comment__ExtraInfo--choice {
margin: 0 7px !important;
}
49 changes: 43 additions & 6 deletions src/components/Comments/Comment.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import DOMPurify from 'dompurify'

import { FORUM_URL } from '../../constants'
import { shortenText } from '../../helpers'
import useAbbreviatedFormatter from '../../hooks/useAbbreviatedFormatter'
import useDclProfile from '../../hooks/useDclProfile'
import useFormatMessage from '../../hooks/useFormatMessage'
import Time from '../../utils/date/Time'
import locations from '../../utils/locations'
import Avatar from '../Common/Avatar'
import DateTooltip from '../Common/DateTooltip'
import Link from '../Common/Typography/Link'
import Text from '../Common/Typography/Text'
import ValidatedProfile from '../Icon/ValidatedProfile'
Expand All @@ -21,10 +25,22 @@ type Props = {
createdAt: string
cooked?: string
address?: string
isValidated?: boolean
extraInfo?: { choice: string; vp: number }
}

const CHOICE_MAX_LENGTH = 14

/* eslint-disable @typescript-eslint/no-explicit-any */
export default function Comment({ forumUsername, avatarUrl, createdAt, cooked, address }: Props) {
export default function Comment({
forumUsername,
avatarUrl,
createdAt,
cooked,
address,
isValidated,
extraInfo,
}: Props) {
const createMarkup = (html: any) => {
DOMPurify.addHook('afterSanitizeAttributes', function (node) {
if (node.nodeName && node.nodeName === 'IMG' && node.getAttribute('alt') === 'image') {
Expand All @@ -48,6 +64,10 @@ export default function Comment({ forumUsername, avatarUrl, createdAt, cooked, a
const { profile, isLoadingDclProfile } = useDclProfile(address)
const { username, hasCustomAvatar, avatarUrl: profileAvatarUrl } = profile

const t = useFormatMessage()
const formatter = useAbbreviatedFormatter()
const isChoiceShortened = (extraInfo?.choice.length || 0) > CHOICE_MAX_LENGTH

return (
<div className="Comment">
<div className="Comment__ProfileImage">
Expand All @@ -63,14 +83,31 @@ export default function Comment({ forumUsername, avatarUrl, createdAt, cooked, a
<div className="Comment__Content">
<div className="Comment__Author">
<Link href={discourseUserUrl}>
<Text weight="bold">
<Text className="Comment__AuthorText" weight="bold" as="span">
{username || forumUsername}
{address && <ValidatedProfile />}
{address && isValidated && <ValidatedProfile />}
</Text>
</Link>
<span>
<Text color="secondary">{Time.from(createdAt).fromNow()}</Text>
</span>
{extraInfo && (
<>
<Text
className="Comment__ExtraInfo Comment__ExtraInfo--choice"
as="span"
title={isChoiceShortened ? extraInfo.choice : undefined}
>
{t('page.rationale.vote_info', { choice: shortenText(extraInfo.choice, CHOICE_MAX_LENGTH) })}
</Text>
<Text className="Comment__ExtraInfo" weight="bold" as="span">
{formatter(extraInfo.vp)} VP
</Text>
</>
)}
,
<DateTooltip date={Time(createdAt).toDate()}>
<Text color="secondary" as="span">
{Time.from(createdAt).fromNow()}
</Text>
</DateTooltip>
</div>
<div className="Comment__Cooked" dangerouslySetInnerHTML={createMarkup(cooked)} />
</div>
Expand Down
11 changes: 11 additions & 0 deletions src/components/Comments/Comments.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,14 @@
font-size: 17px;
}
}

.Comment__ExtraInfoText {
margin-left: 7px !important;
color: var(--secondary-text) !important;
margin-bottom: 0 !important;
}

.Comment__ExtraInfoStrong {
margin: 0 !important;
color: var(--secondary-text) !important;
}
1 change: 1 addition & 0 deletions src/components/Comments/Comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export default function Comments({ comments, topicId, topicSlug, isLoading, topi
createdAt={comment.created_at}
cooked={comment.cooked}
address={comment.address}
isValidated={!!comment.address}
/>
))}
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/components/Common/ProposalPreviewCard/VoteModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ProposalAttributes } from '../../../entities/Proposal/types'
import { VoteByAddress } from '../../../entities/Votes/types'
import { calculateResult } from '../../../entities/Votes/utils'
import useFormatMessage from '../../../hooks/useFormatMessage'
import useProposalChoices from '../../../hooks/useProposalChoices'
import CategoryPill from '../../Category/CategoryPill'
import ChevronRight from '../../Icon/ChevronRight'
import Text from '../Typography/Text'
Expand All @@ -23,7 +24,7 @@ function VoteModule({ proposal, votes }: Props) {
const t = useFormatMessage()
const [account] = useAuthContext()
const hasVote = !!account && !isEmpty(votes?.[account])
const choices = useMemo((): string[] => proposal?.snapshot_proposal?.choices || [], [proposal])
const choices = useProposalChoices(proposal)
const vote = hasVote && !!votes?.[account].choice ? choices[votes?.[account].choice - 1] : undefined
const results = useMemo(
() => calculateResult(proposal?.snapshot_proposal?.choices || [], votes || {}),
Expand Down
4 changes: 3 additions & 1 deletion src/components/Common/Typography/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface Props {
color?: TextColor
style?: FontStyle
as?: 'span'
title?: string
}

const Text = React.forwardRef<HTMLParagraphElement, Props>(
Expand All @@ -33,6 +34,7 @@ const Text = React.forwardRef<HTMLParagraphElement, Props>(
style = DEFAULT_FONT_STYLE,
className,
as,
title,
},
ref
) => {
Expand All @@ -46,7 +48,7 @@ const Text = React.forwardRef<HTMLParagraphElement, Props>(
)
const Component = as ?? 'p'
return (
<Component className={componentClassNames} ref={ref}>
<Component className={componentClassNames} ref={ref} title={title}>
{children}
</Component>
)
Expand Down
21 changes: 21 additions & 0 deletions src/components/Icon/ReadReason.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
function ReadReason() {
return (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_15492_3354)">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.99991 14C12.4187 14 15.9999 11.0906 15.9999 7.5C15.9999 3.90937 12.4187 1 7.99991 1C3.58116 1 -8.54305e-05 3.90937 -8.54305e-05 7.5C-8.54305e-05 8.90938 0.55304 10.2125 1.49054 11.2781C1.43116 12.0437 1.13429 12.725 0.82179 13.2437C0.649915 13.5312 0.474915 13.7625 0.34679 13.9187C0.281165 13.9969 0.231165 14.0563 0.193665 14.0969C0.182682 14.1079 0.173844 14.1178 0.166522 14.126C0.161343 14.1318 0.156923 14.1367 0.15304 14.1406L0.143665 14.15C-8.54291e-05 14.2937 -0.0407104 14.5063 0.0374146 14.6938C0.11554 14.8813 0.29679 15.0031 0.499915 15.0031C1.39679 15.0031 2.29991 14.725 3.04991 14.4C3.76554 14.0875 4.37491 13.7156 4.74679 13.4438C5.74054 13.8031 6.84054 14.0031 7.99991 14.0031V14ZM5 6.5C4.44772 6.5 4 6.94772 4 7.5C4 8.05228 4.44772 8.5 5 8.5H11C11.5523 8.5 12 8.05228 12 7.5C12 6.94772 11.5523 6.5 11 6.5H5Z"
fill="#B9B7BE"
/>
</g>
<defs>
<clipPath id="clip0_15492_3354">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
)
}

export default ReadReason
19 changes: 19 additions & 0 deletions src/components/Icon/UnreadReason.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
function UnreadReason() {
return (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_15492_3368)">
<path
d="M7.99991 14C12.4187 14 15.9999 11.0906 15.9999 7.5C15.9999 3.90937 12.4187 1 7.99991 1C3.58116 1 -8.54291e-05 3.90937 -8.54291e-05 7.5C-8.54291e-05 8.90938 0.55304 10.2125 1.49054 11.2781C1.43116 12.0437 1.13429 12.725 0.82179 13.2437C0.649915 13.5312 0.474915 13.7625 0.34679 13.9187C0.281165 13.9969 0.231165 14.0563 0.193665 14.0969C0.174915 14.1156 0.162415 14.1313 0.15304 14.1406L0.143665 14.15C-8.54284e-05 14.2937 -0.0407104 14.5063 0.0374146 14.6938C0.11554 14.8813 0.29679 15.0031 0.499915 15.0031C1.39679 15.0031 2.29991 14.725 3.04991 14.4C3.76554 14.0875 4.37491 13.7156 4.74679 13.4438C5.74054 13.8031 6.84054 14.0031 7.99991 14.0031V14ZM3.99991 6.5C4.26513 6.5 4.51948 6.60536 4.70702 6.79289C4.89456 6.98043 4.99991 7.23478 4.99991 7.5C4.99991 7.76522 4.89456 8.01957 4.70702 8.20711C4.51948 8.39464 4.26513 8.5 3.99991 8.5C3.7347 8.5 3.48034 8.39464 3.29281 8.20711C3.10527 8.01957 2.99991 7.76522 2.99991 7.5C2.99991 7.23478 3.10527 6.98043 3.29281 6.79289C3.48034 6.60536 3.7347 6.5 3.99991 6.5ZM7.99991 6.5C8.26513 6.5 8.51948 6.60536 8.70702 6.79289C8.89456 6.98043 8.99991 7.23478 8.99991 7.5C8.99991 7.76522 8.89456 8.01957 8.70702 8.20711C8.51948 8.39464 8.26513 8.5 7.99991 8.5C7.7347 8.5 7.48034 8.39464 7.29281 8.20711C7.10527 8.01957 6.99991 7.76522 6.99991 7.5C6.99991 7.23478 7.10527 6.98043 7.29281 6.79289C7.48034 6.60536 7.7347 6.5 7.99991 6.5ZM10.9999 7.5C10.9999 7.23478 11.1053 6.98043 11.2928 6.79289C11.4803 6.60536 11.7347 6.5 11.9999 6.5C12.2651 6.5 12.5195 6.60536 12.707 6.79289C12.8946 6.98043 12.9999 7.23478 12.9999 7.5C12.9999 7.76522 12.8946 8.01957 12.707 8.20711C12.5195 8.39464 12.2651 8.5 11.9999 8.5C11.7347 8.5 11.4803 8.39464 11.2928 8.20711C11.1053 8.01957 10.9999 7.76522 10.9999 7.5Z"
fill="#FF2D55"
/>
</g>
<defs>
<clipPath id="clip0_15492_3368">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
)
}

export default UnreadReason
Loading

0 comments on commit 244203d

Please sign in to comment.