diff --git a/CHANGELOG.md b/CHANGELOG.md index 30c8aea4c..48280e697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ changes. ### Added -- +- Add support for displaying CIP-119 DRep images [Issue 1806](https://github.com/IntersectMBO/govtool/issues/1806) ### Fixed diff --git a/govtool/frontend/.env.example b/govtool/frontend/.env.example index 5fbb1a97f..2489d65a0 100644 --- a/govtool/frontend/.env.example +++ b/govtool/frontend/.env.example @@ -8,3 +8,5 @@ VITE_IS_DEV=true VITE_USERSNAP_SPACE_API_KEY="" VITE_IS_PROPOSAL_DISCUSSION_FORUM_ENABLED='true' VITE_PDF_API_URL="" +VITE_IPFS_GATEWAY="" +VITE_IPFS_PROJECT_ID="" \ No newline at end of file diff --git a/govtool/frontend/Dockerfile b/govtool/frontend/Dockerfile index 5186b390e..9a12dba06 100644 --- a/govtool/frontend/Dockerfile +++ b/govtool/frontend/Dockerfile @@ -10,6 +10,8 @@ ARG NPMRC_TOKEN ARG VITE_USERSNAP_SPACE_API_KEY ARG VITE_IS_PROPOSAL_DISCUSSION_FORUM_ENABLED='true' ARG VITE_PDF_API_URL +ARG VITE_IPFS_GATEWAY +ARG VITE_IPFS_PROJECT_ID # Ensure all required build arguments are set RUN \ @@ -21,7 +23,9 @@ RUN \ : "${VITE_SENTRY_DSN:?Build argument VITE_SENTRY_DSN is not set}" && \ : "${NPMRC_TOKEN:?Build argument NPMRC_TOKEN is not set}" && \ : "${VITE_USERSNAP_SPACE_API_KEY:?Build argument VITE_USERSNAP_SPACE_API_KEY is not set}" && \ - : "${VITE_IS_PROPOSAL_DISCUSSION_FORUM_ENABLED:?Build argument VITE_IS_PROPOSAL_DISCUSSION_FORUM_ENABLED is not set}" + : "${VITE_IS_PROPOSAL_DISCUSSION_FORUM_ENABLED:?Build argument VITE_IS_PROPOSAL_DISCUSSION_FORUM_ENABLED is not set}" && \ + : "${VITE_IPFS_GATEWAY:?Build argument VITE_IPFS_GATEWAY is not set}" && \ + : "${VITE_IPFS_PROJECT_ID:?Build argument VITE_IPFS_PROJECT_ID is not set}" ENV NODE_OPTIONS=--max_old_space_size=8192 diff --git a/govtool/frontend/Dockerfile.qovery b/govtool/frontend/Dockerfile.qovery index ddc6c1288..f9b562ade 100644 --- a/govtool/frontend/Dockerfile.qovery +++ b/govtool/frontend/Dockerfile.qovery @@ -10,6 +10,8 @@ ARG NPMRC_TOKEN ARG VITE_USERSNAP_SPACE_API_KEY ARG VITE_IS_PROPOSAL_DISCUSSION_FORUM_ENABLED='true' ARG VITE_PDF_API_URL +ARG VITE_IPFS_GATEWAY +ARG VITE_IPFS_PROJECT_ID ENV NODE_OPTIONS=--max_old_space_size=8192 diff --git a/govtool/frontend/public/icons/DefaultDRep.svg b/govtool/frontend/public/icons/DefaultDRep.svg new file mode 100644 index 000000000..00557579b --- /dev/null +++ b/govtool/frontend/public/icons/DefaultDRep.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/govtool/frontend/src/components/molecules/DataMissingHeader.tsx b/govtool/frontend/src/components/molecules/DataMissingHeader.tsx index 6267d8742..ef6e278b4 100644 --- a/govtool/frontend/src/components/molecules/DataMissingHeader.tsx +++ b/govtool/frontend/src/components/molecules/DataMissingHeader.tsx @@ -1,19 +1,24 @@ -import { Box, SxProps } from "@mui/material"; +import { Avatar, Box, SxProps } from "@mui/material"; import { Typography } from "@atoms"; import { MetadataValidationStatus } from "@models"; import { getMetadataDataMissingStatusTranslation } from "@/utils"; +import { ICONS } from "@/consts"; type DataMissingHeaderProps = { isDataMissing: MetadataValidationStatus | null; title?: string; titleStyle?: SxProps; + isDRep?: boolean; + image?: string | null; }; export const DataMissingHeader = ({ title, isDataMissing, titleStyle, + isDRep, + image, }: DataMissingHeaderProps) => ( + {isDRep && ( + + )} - - - {metadataStatus - ? getMetadataDataMissingStatusTranslation(metadataStatus) - : ellipsizeText(givenName ?? "", 25)} - - { - navigator.clipboard.writeText(view); - addSuccessAlert(t("alerts.copiedToClipboard")); - e.stopPropagation(); - }} - sx={{ - gap: 1, - width: "250px", - maxWidth: "100%", - "&:hover": { - opacity: 0.6, - transition: "opacity 0.3s", - }, - }} - > - - {view} + + + + + {metadataStatus + ? getMetadataDataMissingStatusTranslation(metadataStatus) + : ellipsizeText(givenName ?? "", 25)} - - + { + navigator.clipboard.writeText(view); + addSuccessAlert(t("alerts.copiedToClipboard")); + e.stopPropagation(); + }} + sx={{ + gap: 1, + width: "250px", + maxWidth: "100%", + "&:hover": { + opacity: 0.6, + transition: "opacity 0.3s", + }, + }} + > + + {view} + + + + diff --git a/govtool/frontend/src/components/organisms/DRepDetailsCardHeader.tsx b/govtool/frontend/src/components/organisms/DRepDetailsCardHeader.tsx index 77092b30f..ecbb61eba 100644 --- a/govtool/frontend/src/components/organisms/DRepDetailsCardHeader.tsx +++ b/govtool/frontend/src/components/organisms/DRepDetailsCardHeader.tsx @@ -32,7 +32,7 @@ export const DRepDetailsCardHeader = ({ const { votingPower: myVotingPower } = useGetAdaHolderVotingPowerQuery(stakeKey); - const { givenName, metadataStatus } = dRepData; + const { givenName, metadataStatus, image } = dRepData; const navigateToEditDRep = () => { navigate(PATHS.editDrepMetadata, { @@ -121,7 +121,9 @@ export const DRepDetailsCardHeader = ({ )} diff --git a/govtool/frontend/src/consts/icons.ts b/govtool/frontend/src/consts/icons.ts index bd70a2b06..4d8672516 100644 --- a/govtool/frontend/src/consts/icons.ts +++ b/govtool/frontend/src/consts/icons.ts @@ -13,6 +13,7 @@ export const ICONS = { copyWhiteIcon: "/icons/CopyWhite.svg", dashboardActiveIcon: "/icons/DashboardActive.svg", dashboardIcon: "/icons/Dashboard.svg", + defaultDRepIcon: "/icons/DefaultDRep.svg", download: "/icons/Download.svg", drawerIcon: "/icons/DrawerIcon.svg", dRepDirectoryActiveIcon: "/icons/DRepDirectoryActive.svg", diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts index fff33a0c0..32ef7bcc5 100644 --- a/govtool/frontend/src/models/api.ts +++ b/govtool/frontend/src/models/api.ts @@ -144,6 +144,9 @@ export type DrepDataDTO = { url?: string; view: string; votingPower?: number; + imageUrl: string | null; + // either base64 for IPFS image or URL for regular image + image: string | null; }; export type DRepData = DrepDataDTO & { @@ -156,6 +159,9 @@ export type DRepData = DrepDataDTO & { doNotList: boolean; metadataStatus: MetadataValidationStatus | null; metadataValid: boolean; + imageUrl: string | null; + // either base64 for IPFS image or URL for regular image + image: string | null; }; export type Vote = "yes" | "no" | "abstain"; diff --git a/govtool/frontend/src/utils/mapDtoToDrep.ts b/govtool/frontend/src/utils/mapDtoToDrep.ts index 419973c14..4ab65d9fa 100644 --- a/govtool/frontend/src/utils/mapDtoToDrep.ts +++ b/govtool/frontend/src/utils/mapDtoToDrep.ts @@ -11,7 +11,7 @@ export const mapDtoToDrep = async (dto: DrepDataDTO): Promise => { const emptyMetadata = { paymentAddress: null, givenName: "", - image: null, + imageUrl: null, objectives: null, motivations: null, qualifications: null, @@ -24,6 +24,33 @@ export const mapDtoToDrep = async (dto: DrepDataDTO): Promise => { // DBSync contains wrong representation of DRep view for script based DReps const view = fixViewForScriptBasedDRep(dto.view, dto.isScriptBased); + // We need to prefetch the image, for the IPFS support + let base64Image = null; + const isIPFSImage = dto.imageUrl?.startsWith("ipfs://") || false; + if (dto.imageUrl) { + fetch( + isIPFSImage + ? `${process.env.VITE_IPFS_GATEWAY}/${dto.imageUrl?.slice(7)}` + : dto.imageUrl, + isIPFSImage + ? { + headers: { project_id: import.meta.env.VITE_IPFS_PROJECT_ID }, + } + : {}, + ) + .then(async (res) => { + const blob = await res.blob(); + const reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onloadend = () => { + base64Image = reader.result; + }; + }) + .catch((e) => { + console.error("Fetching the DRep image failed, reason: ", e); + }); + } + if (dto.metadataHash && dto.url) { const validationResponse = await postValidate({ url: dto.url, @@ -36,6 +63,7 @@ export const mapDtoToDrep = async (dto: DrepDataDTO): Promise => { ...validationResponse.metadata, metadataStatus: validationResponse.status || null, metadataValid: validationResponse.valid, + image: isIPFSImage ? base64Image : dto.imageUrl, view, }; } @@ -44,5 +72,6 @@ export const mapDtoToDrep = async (dto: DrepDataDTO): Promise => { ...dto, ...emptyMetadata, view, + image: isIPFSImage ? base64Image : dto.imageUrl, }; }; diff --git a/govtool/frontend/src/utils/tests/dRep.test.ts b/govtool/frontend/src/utils/tests/dRep.test.ts index 104bfe019..66f41fe63 100644 --- a/govtool/frontend/src/utils/tests/dRep.test.ts +++ b/govtool/frontend/src/utils/tests/dRep.test.ts @@ -24,6 +24,8 @@ const EXAMPLE_DREP: DRepData = { qualifications: null, doNotList: false, isScriptBased: false, + imageUrl: null, + image: null, }; describe("isSameDRep function", () => { diff --git a/scripts/govtool/frontend.mk b/scripts/govtool/frontend.mk index 64f5516e8..910cbd030 100644 --- a/scripts/govtool/frontend.mk +++ b/scripts/govtool/frontend.mk @@ -25,6 +25,8 @@ build-frontend: docker-login --build-arg VITE_IS_PROPOSAL_DISCUSSION_FORUM_ENABLED="$${IS_PROPOSAL_DISCUSSION_FORUM_ENABLED}" \ --build-arg NPMRC_TOKEN="$${NPMRC_TOKEN}" \ --build-arg VITE_PDF_API_URL="$${PDF_API_URL}" \ + --build-arg VITE_IPFS_GATEWAY="$${IPFS_GATEWAY}" \ + --build-arg VITE_IPFS_PROJECT_ID="$${IPFS_PROJECT_ID}" \ $(root_dir)/govtool/frontend .PHONY: push-frontend