Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use markdown instead of serialized HTML #80

Merged
merged 4 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"react-dom": "^18.2.0",
"react-helmet-async": "^2.0.5",
"react-intersection-observer": "^9.8.1",
"react-markdown": "^9.0.1",
"react-parallax-tilt": "^1.7.217",
"react-responsive": "^9.0.2",
"react-router-dom": "^6.22.2",
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/ACLs/allowAll.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineACL } from './common'
import { CheckPermissionResults, defineACL } from './common'
import { designDecisions, VITE_CONTRACT_ACL_ALLOWALL } from '../../constants/config'
import { denyWithReason, useOneOfField } from '../InputFields'

Expand Down Expand Up @@ -34,7 +34,7 @@ export const allowAll = defineACL({
}),
isThisMine: options => 'allowAll' in options,

checkPermission: async (pollACL, daoAddress, proposalId, userAddress) => {
checkPermission: async (pollACL, daoAddress, proposalId, userAddress): Promise<CheckPermissionResults> => {
const proof = new Uint8Array()
const result = 0n !== (await pollACL.canVoteOnPoll(daoAddress, proposalId, userAddress, proof))
const canVote = result ? true : denyWithReason('some unknown reason')
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/ACLs/allowList.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineACL } from './common'
import { CheckPermissionResults, defineACL } from './common'
import { designDecisions, VITE_CONTRACT_ACL_VOTERALLOWLIST } from '../../constants/config'
import { abiEncode, isValidAddress } from '../../utils/poll.utils'
import { denyWithReason, useOneOfField, useTextArrayField } from '../InputFields'
Expand Down Expand Up @@ -80,7 +80,7 @@ export const allowList = defineACL({

isThisMine: options => 'allowList' in options,

checkPermission: async (pollACL, daoAddress, proposalId, userAddress) => {
checkPermission: async (pollACL, daoAddress, proposalId, userAddress): Promise<CheckPermissionResults> => {
const proof = new Uint8Array()
const explanation = 'This poll is only for a predefined list of addresses.'
const result = 0n !== (await pollACL.canVoteOnPoll(daoAddress, proposalId, userAddress, proof))
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/components/ACLs/common.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { Choice, DecisionWithReason, FieldConfiguration } from '../InputFields'
import { AclOptions, IPollACL } from '@oasisprotocol/blockvote-contracts'
import { BytesLike } from 'ethers'
import { ReactNode } from 'react'
import { MarkdownCode } from '../../types'

export type StatusUpdater = (status: string | undefined) => void

export type CheckPermissionResults = {
canVote: DecisionWithReason
explanation?: MarkdownCode
proof: BytesLike
error?: string
}

/**
* This data structure describes an ACL
*/
Expand Down Expand Up @@ -56,7 +64,7 @@ export type ACL<Name, ConfigInputValues, Options extends AclOptions> = Choice<Na
proposalId: string,
userAddress: string,
options: Options,
) => Promise<{ canVote: DecisionWithReason; explanation?: ReactNode; proof: BytesLike; error?: string }>
) => Promise<CheckPermissionResults>
}

export function defineACL<Name, ConfigInputValues, Options extends AclOptions>(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineACL } from './common'
import { CheckPermissionResults, defineACL } from './common'
import { DecisionWithReason, denyWithReason, useLabel, useOneOfField, useTextField } from '../InputFields'
import { abiEncode, getLocalContractDetails, isValidAddress } from '../../utils/poll.utils'
import {
Expand All @@ -9,6 +9,8 @@ import {
} from '../../constants/config'
import { StringUtils } from '../../utils/string.utils'
import { FLAG_WEIGHT_LOG10, FLAG_WEIGHT_ONE } from '../../types'
import { renderMarkdown } from '../Markdown'
import { getLink } from '../../utils/markdown.utils'

export const tokenHolder = defineACL({
value: 'acl_tokenHolder',
Expand Down Expand Up @@ -53,14 +55,7 @@ export const tokenHolder = defineACL({
visible: hasValidSapphireTokenAddress,
label: 'Selected token:',
initialValue: '',
renderer: name =>
tokenUrl ? (
<a href={tokenUrl} target={'_blank'}>
{name}
</a>
) : (
name
),
renderer: name => renderMarkdown(getLink({ label: name, href: tokenUrl })),
})

const tokenSymbol = useLabel({
Expand Down Expand Up @@ -123,27 +118,19 @@ export const tokenHolder = defineACL({

isThisMine: options => 'token' in options,

checkPermission: async (pollACL, daoAddress, proposalId, userAddress, options) => {
checkPermission: async (
pollACL,
daoAddress,
proposalId,
userAddress,
options,
): Promise<CheckPermissionResults> => {
const tokenAddress = options.token
const tokenInfo = await getLocalContractDetails(tokenAddress)
const url = configuredExplorerUrl
? StringUtils.getTokenUrl(configuredExplorerUrl, tokenAddress)
: undefined
const explanation = url ? (
<span>
You need to hold some{' '}
<a href={url} target={'_blank'}>
{tokenInfo?.name ?? 'specific'}
</a>{' '}
(on the{' '}
<a href={configuredExplorerUrl} target={'_blank'}>
{configuredNetworkName}
</a>
) to vote.
</span>
) : (
`You need to hold some ${tokenInfo?.name ?? 'specific'} token (on the ${configuredNetworkName}) to vote.`
)
const explanation = `You need to hold some ${getLink({ label: tokenInfo?.name ?? StringUtils.truncateAddress(tokenAddress), href: url })} on ${getLink({ label: configuredNetworkName, href: configuredExplorerUrl })} to vote.`
const proof = new Uint8Array()
let canVote: DecisionWithReason
try {
Expand All @@ -153,16 +140,7 @@ export const tokenHolder = defineACL({
canVote = true
} else {
canVote = denyWithReason(
url ? (
<>
you don&apos;t hold any{' '}
<a href={url} target={'_blank'}>
{tokenInfo?.name ?? StringUtils.truncateAddress(tokenAddress)}
</a>
</>
) : (
`you don't hold any ${tokenInfo?.name ?? tokenAddress}`
),
`you don't hold any ${getLink({ label: tokenInfo?.name ?? StringUtils.truncateAddress(tokenAddress), href: url })}`,
)
}
} catch {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineACL } from './common'
import { CheckPermissionResults, defineACL } from './common'
import {
addMockValidation,
Choice,
Expand Down Expand Up @@ -32,9 +32,11 @@ import type { TokenInfo, NFTInfo } from '@oasisprotocol/blockvote-contracts'
import { designDecisions, VITE_CONTRACT_ACL_STORAGEPROOF } from '../../constants/config'
import classes from './index.module.css'
import { BytesLike, getBytes, getUint, hexlify } from 'ethers'
import { ReactNode, useMemo } from 'react'
import { useMemo } from 'react'
import { StringUtils } from '../../utils/string.utils'
import { FLAG_WEIGHT_LOG10, FLAG_WEIGHT_ONE } from '../../types'
import { renderMarkdown } from '../Markdown'
import { getLink } from '../../utils/markdown.utils'

export const xchain = defineACL({
value: 'acl_xchain',
Expand Down Expand Up @@ -132,14 +134,7 @@ export const xchain = defineACL({
label: `${isToken(contractType.value as any) ? 'Token' : 'NFT'}:`,
initialValue: '',
compact: true,
renderer: name =>
tokenUrl ? (
<a href={tokenUrl} target={'_blank'}>
{name}
</a>
) : (
name
),
renderer: name => renderMarkdown(getLink({ label: name, href: tokenUrl })),
...addMockValidation,
})

Expand Down Expand Up @@ -322,14 +317,20 @@ export const xchain = defineACL({

isThisMine: options => 'xchain' in options,

checkPermission: async (pollACL, daoAddress, proposalId, userAddress, options) => {
checkPermission: async (
pollACL,
daoAddress,
proposalId,
userAddress,
options,
): Promise<CheckPermissionResults> => {
const { xchain } = options
const chainId = xchain.c
const blockHash = hexlify(xchain.b)
const tokenAddress = hexlify(xchain.a)
const slot = xchain.s

let explanation: ReactNode = ''
let explanation = ''
let error = ''
let proof: BytesLike = ''
let tokenInfo: TokenInfo | NFTInfo | undefined
Expand All @@ -353,21 +354,7 @@ export const xchain = defineACL({
try {
tokenInfo = await getContractDetails(chainId, tokenAddress)
if (!tokenInfo) throw new Error("Can't load token details")
explanation = tokenUrl ? (
<span>
This poll is only for those who have hold{' '}
<a href={tokenUrl} target={'_blank'}>
{tokenInfo?.name ?? StringUtils.truncateAddress(tokenInfo.addr)}
</a>{' '}
on{' '}
<a href={explorerUrl} target={'_blank'}>
{chainDefinition.name}
</a>{' '}
when the poll was created.
</span>
) : (
`This poll is only for those who have hold ${tokenInfo?.name} on ${chainDefinition.name} when the poll was created.`
)
explanation = `This poll is only for those who have hold ${getLink({ label: tokenInfo?.name ?? StringUtils.truncateAddress(tokenInfo.addr), href: tokenUrl })} on ${getLink({ label: chainDefinition.name, href: explorerUrl })} when the poll was created.`
let isBalancePositive = false
const holderBalance = getUint(
await fetchStorageValue(provider, blockHash, tokenAddress, slot, userAddress),
Expand All @@ -383,20 +370,7 @@ export const xchain = defineACL({
}
if (!isBalancePositive) {
canVote = denyWithReason(
tokenUrl ? (
<span>
you don&apos;t hold any{' '}
<a href={tokenUrl} target={'_blank'}>
{tokenInfo.name}
</a>{' '}
on{' '}
<a href={explorerUrl} target={'_blank'}>
{chainDefinition.name}
</a>
</span>
) : (
`you don't hold any ${tokenInfo.name} tokens on ${chainDefinition.name}`
),
`you don't hold any ${getLink({ label: tokenInfo.name ?? StringUtils.truncateAddress(tokenAddress), href: tokenUrl })} on ${getLink({ label: chainDefinition.name, href: explorerUrl })}`,
)
}
} catch (e) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/InputFields/SelectInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const SelectInput: FC<OneOfFieldControls<any>> = props => {
key={choice.value}
value={choice.value}
disabled={disabled}
// TODO: we can't display HTML reason here, so we just _hope_ that it will be a string.
// TODO: we can't display HTML reason here, so display the markdown test.
// The proper solution is to use a custom select component.
title={(getReasonForDenial(choice.enabled) as string) ?? choice.description}
>
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/components/InputFields/TextArrayInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { FC, ReactNode, useCallback } from 'react'
import React, { FC, useCallback } from 'react'
import { TextArrayControls } from './useTextArrayField'
import classes from './index.module.css'
import { StringUtils } from '../../utils/string.utils'
Expand All @@ -11,9 +11,11 @@ import { WithLabelAndDescription } from './WithLabelAndDescription'
import { WithValidation } from './WithValidation'
import { MotionDiv } from '../Animations'
import { MaybeWithTooltip } from '../Tooltip/MaybeWithTooltip'
import { MarkdownCode } from '../../types'
import { MarkdownBlock } from '../Markdown'

const TrashIcon: FC<{
label: ReactNode
label: MarkdownCode | undefined
remove: () => void
enabled?: boolean
}> = ({ label, remove, enabled }) => {
Expand All @@ -35,10 +37,10 @@ const TrashIcon: FC<{
}

const AddIcon: FC<{
label: string
label: MarkdownCode
add: () => void
enabled?: boolean
overlay?: string | undefined
overlay?: MarkdownCode | undefined
}> = ({ label, add, enabled, overlay }) => {
const handleClick = useCallback(() => {
if (enabled) add()
Expand All @@ -59,7 +61,7 @@ const AddIcon: FC<{
</clipPath>
</defs>
</svg>
{label}
<MarkdownBlock code={label} />
</div>
</MaybeWithTooltip>
)
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/InputFields/useInputField.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReactNode, useEffect, useMemo, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import {
AllProblems,
ValidatorControls,
Expand All @@ -14,6 +14,7 @@ import {
ValidatorFunction,
wrapProblem,
} from './util'
import { MarkdownCode } from '../../types'

type ValidatorBundle<DataType> = SingleOrArray<undefined | ValidatorFunction<DataType>>

Expand Down Expand Up @@ -159,7 +160,7 @@ export type InputFieldControls<DataType> = Pick<
type: string
visible: boolean
enabled: boolean
whyDisabled?: ReactNode
whyDisabled?: MarkdownCode
containerClassName?: string
value: DataType
setValue: (value: DataType) => void
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/InputFields/useTextArrayField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
flatten,
} from './util'
import { useState } from 'react'
import { MarkdownCode } from '../../types'

/**
* Parameters for defining an input field that accepts a list of strings
Expand Down Expand Up @@ -138,11 +139,11 @@ export type TextArrayControls = InputFieldControls<string[]> & {
placeholders: string[]

canAddItem: boolean
addItemLabel: string
addItemLabel: MarkdownCode
addItem: () => void

canRemoveItem: (index: number) => boolean
removeItemLabel: string
removeItemLabel: MarkdownCode
removeItem: (index: number) => void

pendingValidationIndex: number | undefined
Expand Down
Loading