From 7e425f02cc9ed60aa7a5ade3105cc1a2af55b6a6 Mon Sep 17 00:00:00 2001 From: Evgenii Date: Thu, 20 Jul 2023 17:41:36 +0400 Subject: [PATCH] feat: add new field to vote details with long text form IPFS based on CID in vote metadata --- README.md | 12 ++++++---- modules/network/utils/fetcherIPFS.ts | 24 +++++++++++++++++++ modules/shared/utils/getUrlFromCID.ts | 5 ++++ modules/shared/utils/regexCID.ts | 17 +++++++++++++ .../utils/replaceLinksWithComponents.tsx | 6 +++++ modules/votes/ui/VoteDetails/VoteDetails.tsx | 14 ++++++++++- .../VoteMetadataIPFSDescription.tsx | 24 +++++++++++++++++++ .../VoteMetadataIPFSDescriptionStyle.ts | 8 +++++++ .../ui/VoteMetadataIPFSDescription/index.ts | 1 + 9 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 modules/network/utils/fetcherIPFS.ts create mode 100644 modules/shared/utils/getUrlFromCID.ts create mode 100644 modules/shared/utils/regexCID.ts create mode 100644 modules/votes/ui/VoteMetadataIPFSDescription/VoteMetadataIPFSDescription.tsx create mode 100644 modules/votes/ui/VoteMetadataIPFSDescription/VoteMetadataIPFSDescriptionStyle.ts create mode 100644 modules/votes/ui/VoteMetadataIPFSDescription/index.ts diff --git a/README.md b/README.md index b89436b0..d0763005 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,7 @@ The required functionality includes: - connecting wallet (similar to Lido on Ethereum staking widget) - casting vote (Yes/No) -[More details]( -https://www.notion.so/Custom-voting-UI-feature-description-bde7fde42d3749a3afcbab3a56f26674) +[More details](https://www.notion.so/Custom-voting-UI-feature-description-bde7fde42d3749a3afcbab3a56f26674) ## Pre-requisites @@ -25,12 +24,17 @@ https://www.notion.so/Custom-voting-UI-feature-description-bde7fde42d3749a3afcba This project requires an .env file which is distributed via private communication channels. A sample can be found in .env.sample. +## Dockerfile env + +- Node.js v16.20.1 +- Yarn package manager v1.22.19 + ## Development Step 1. Copy the contents of `.env.sample` to `.env.local` ```bash -cp sample.env .env.local +cp .env.sample .env.local ``` Step 2. Fill out the `.env.local`. You may need to sign up for [Infura](https://infura.io/) or [Alchemy](https://www.alchemy.com/), if you haven't already, to be able to use Ethereum JSON RPC connection. @@ -87,4 +91,4 @@ To create a new release: 1. After the merge, the `Prepare release draft` action will run automatically. When the action is complete, a release draft is created. 1. When you need to release, go to Repo → Releases. 1. Publish the desired release draft manually by clicking the edit button - this release is now the `Latest Published`. -1. After publication, the action to create a release bump will be triggered automatically. \ No newline at end of file +1. After publication, the action to create a release bump will be triggered automatically. diff --git a/modules/network/utils/fetcherIPFS.ts b/modules/network/utils/fetcherIPFS.ts new file mode 100644 index 00000000..63d6be23 --- /dev/null +++ b/modules/network/utils/fetcherIPFS.ts @@ -0,0 +1,24 @@ +import { getUrlFromCID } from 'modules/shared/utils/getUrlFromCID' + +export const DEFAULT_PARAMS = { + method: 'GET', + headers: { + 'Content-type': 'text/plain', + }, +} + +type FetcherIPFS = (cid: string, params?: RequestInit) => Promise + +export const fetcherIPFS: FetcherIPFS = async ( + cid, + params = DEFAULT_PARAMS, +) => { + const response = await fetch(getUrlFromCID(cid), params) + + if (!response.ok) { + throw new Error('An error occurred while fetching the data.') + } + + const data = await response.text() + return data +} diff --git a/modules/shared/utils/getUrlFromCID.ts b/modules/shared/utils/getUrlFromCID.ts new file mode 100644 index 00000000..921103ce --- /dev/null +++ b/modules/shared/utils/getUrlFromCID.ts @@ -0,0 +1,5 @@ +// could be moved to env if needed +const CIDPrefix = 'https://' +const CIDSuffix = '.ipfs.w3s.link' + +export const getUrlFromCID = (cid: string) => `${CIDPrefix}${cid}${CIDSuffix}` diff --git a/modules/shared/utils/regexCID.ts b/modules/shared/utils/regexCID.ts new file mode 100644 index 00000000..31c0e544 --- /dev/null +++ b/modules/shared/utils/regexCID.ts @@ -0,0 +1,17 @@ +/** + * IPFS has 2 cid formats v0 and v1, v1 supports different encoding options: + * CIDv0: + * base58btc Qm + * CIDv1: + * base16 f + * base16upper F + * hexadecimal base32 b + * base32upper B + * base58btc z + * base64 m + * base64url u + * base64urlpad U + */ + +export const REGEX_CID = + /(Qm[1-9A-HJ-NP-Za-km-z]{44,128}|b[A-Za-z2-7]{58,128}|B[A-Z2-7]{58,128}|z[1-9A-HJ-NP-Za-km-z]{48,128}|F[0-9A-F]{50,128})$/g diff --git a/modules/shared/utils/replaceLinksWithComponents.tsx b/modules/shared/utils/replaceLinksWithComponents.tsx index aad5de88..7b0bc37a 100644 --- a/modules/shared/utils/replaceLinksWithComponents.tsx +++ b/modules/shared/utils/replaceLinksWithComponents.tsx @@ -3,6 +3,8 @@ import { AddressBadge } from '../ui/Common/AddressBadge' import { REGEX_ETH_ADDRESS } from 'modules/shared/utils/regexEthAddress' import { REGEX_URL } from 'modules/shared/utils/regexURL' +import { REGEX_CID } from 'modules/shared/utils/regexCID' +import { getUrlFromCID } from 'modules/shared/utils/getUrlFromCID' import { replaceRegexWithJSX } from './replaceRegexWithJSX' @@ -16,5 +18,9 @@ export const replaceJsxElements = (text: string) => { regex: REGEX_ETH_ADDRESS, replace: address => , }, + { + regex: REGEX_CID, + replace: cid => {cid}, + }, ]) } diff --git a/modules/votes/ui/VoteDetails/VoteDetails.tsx b/modules/votes/ui/VoteDetails/VoteDetails.tsx index 2f1620a3..8be363c1 100644 --- a/modules/votes/ui/VoteDetails/VoteDetails.tsx +++ b/modules/votes/ui/VoteDetails/VoteDetails.tsx @@ -16,11 +16,13 @@ import { import { ContentHighlightBox } from 'modules/shared/ui/Common/ContentHighlightBox' import { InfoRowFull } from 'modules/shared/ui/Common/InfoRow' import { VoteMetadataDescription } from '../VoteMetadataDescription' +import { VoteMetadataIPFSDescription } from '../VoteMetadataIPFSDescription' import { Vote, VoteStatus } from 'modules/votes/types' import { weiToNum } from 'modules/blockChain/utils/parseWei' import { formatNumber } from 'modules/shared/utils/formatNumber' import { getVoteDetailsFormatted } from 'modules/votes/utils/getVoteDetailsFormatted' +import { REGEX_CID } from 'modules/shared/utils/regexCID' type Props = { vote: Vote @@ -41,7 +43,7 @@ export function VoteDetails({ voteTime, objectionPhaseTime, creator, - metadata, + metadata = '', isEnded, executedTxHash, }: Props) { @@ -57,6 +59,7 @@ export function VoteDetails({ endDate, } = getVoteDetailsFormatted({ vote, voteTime }) + const cid = metadata.match(REGEX_CID)?.[0] return ( <> @@ -165,6 +168,15 @@ export function VoteDetails({ )} + {cid && ( + + + + + + + )} + diff --git a/modules/votes/ui/VoteMetadataIPFSDescription/VoteMetadataIPFSDescription.tsx b/modules/votes/ui/VoteMetadataIPFSDescription/VoteMetadataIPFSDescription.tsx new file mode 100644 index 00000000..954b6d28 --- /dev/null +++ b/modules/votes/ui/VoteMetadataIPFSDescription/VoteMetadataIPFSDescription.tsx @@ -0,0 +1,24 @@ +import { DescriptionText } from './VoteMetadataIPFSDescriptionStyle' +import { replaceJsxElements } from 'modules/shared/utils/replaceLinksWithComponents' +import { useSWR } from 'modules/network/hooks/useSwr' +import { fetcherIPFS } from 'modules/network/utils/fetcherIPFS' +import { InlineLoader } from '@lidofinance/lido-ui' + +type Props = { + cid: string +} + +export function VoteMetadataIPFSDescription({ cid }: Props) { + const { data = '', error, initialLoading } = useSWR(cid, fetcherIPFS) + if (initialLoading) { + return + } + if (error) { + return ( + + ⚠️ Failed to load vote content, please try again later. + + ) + } + return {replaceJsxElements(data)} +} diff --git a/modules/votes/ui/VoteMetadataIPFSDescription/VoteMetadataIPFSDescriptionStyle.ts b/modules/votes/ui/VoteMetadataIPFSDescription/VoteMetadataIPFSDescriptionStyle.ts new file mode 100644 index 00000000..624d46ae --- /dev/null +++ b/modules/votes/ui/VoteMetadataIPFSDescription/VoteMetadataIPFSDescriptionStyle.ts @@ -0,0 +1,8 @@ +import styled from 'styled-components' + +export const DescriptionText = styled.div` + hyphens: auto; + overflow-wrap: anywhere; + word-break: break-word; + white-space: pre-wrap; +` diff --git a/modules/votes/ui/VoteMetadataIPFSDescription/index.ts b/modules/votes/ui/VoteMetadataIPFSDescription/index.ts new file mode 100644 index 00000000..40e3877c --- /dev/null +++ b/modules/votes/ui/VoteMetadataIPFSDescription/index.ts @@ -0,0 +1 @@ +export * from './VoteMetadataIPFSDescription'