From 2d303bfd4fffa56444a389ee70836c20335be3ae Mon Sep 17 00:00:00 2001 From: Miro Date: Fri, 29 Aug 2025 15:03:41 +0200 Subject: [PATCH 01/11] Adds filter pills to show users which filter is applied --- .../src/components/atoms/ChipButton.tsx | 64 +++++ .../components/molecules/DataActionsBar.tsx | 249 +++++++++++------- 2 files changed, 211 insertions(+), 102 deletions(-) create mode 100644 govtool/frontend/src/components/atoms/ChipButton.tsx diff --git a/govtool/frontend/src/components/atoms/ChipButton.tsx b/govtool/frontend/src/components/atoms/ChipButton.tsx new file mode 100644 index 000000000..ca11c4db0 --- /dev/null +++ b/govtool/frontend/src/components/atoms/ChipButton.tsx @@ -0,0 +1,64 @@ +import * as React from "react"; +import { Chip, ChipProps, IconButton } from "@mui/material"; +import { IconX } from "@intersect.mbo/intersectmbo.org-icons-set"; + +interface ChipButtonProps + extends Omit { + label: React.ReactNode; + onDelete: () => void; + bgColor?: string; + deleteIconPosition?: "left" | "right"; + iconSize?: number; + testId?: string; +} + +const ChipButton: React.FC = ({ + label, + onDelete, + bgColor = "#B9CCF5", + deleteIconPosition = "left", + iconSize = 14, + testId, + sx, + ...rest +}) => ( + + + + } + sx={{ + backgroundColor: bgColor, + borderRadius: 999, + height: "auto", + py: 0.75, + pl: 1.75, + pr: 2.25, + display: "flex", + flexDirection: deleteIconPosition === "right" ? "row" : "row-reverse", + gap: 0.5, + "& .MuiChip-label": { + fontSize: 12, + fontWeight: 400, + color: "#000", + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + px: 0, + py: 0, + }, + "& .MuiChip-deleteIcon": { m: 0 }, + ...(sx as object), + }} + /> +); + +export default ChipButton; diff --git a/govtool/frontend/src/components/molecules/DataActionsBar.tsx b/govtool/frontend/src/components/molecules/DataActionsBar.tsx index 17183f3e0..7839af965 100644 --- a/govtool/frontend/src/components/molecules/DataActionsBar.tsx +++ b/govtool/frontend/src/components/molecules/DataActionsBar.tsx @@ -1,11 +1,11 @@ -import { Dispatch, FC, SetStateAction } from "react"; +import { Dispatch, FC, SetStateAction, useMemo, useState } from "react"; import { Box, InputBase, IconButton } from "@mui/material"; import Search from "@mui/icons-material/Search"; import CloseIcon from "@mui/icons-material/Close"; import { DataActionsFilters, DataActionsSorting } from "@molecules"; import { OrderActionsChip } from "./OrderActionsChip"; -import { theme } from "@/theme"; +import ChipButton from "../atoms/ChipButton"; type DataActionsBarProps = { chosenFilters?: string[]; @@ -13,10 +13,7 @@ type DataActionsBarProps = { chosenSorting: string; closeFilters?: () => void; closeSorts: () => void; - filterOptions?: { - key: string; - label: string; - }[]; + filterOptions?: { key: string; label: string }[]; filtersOpen?: boolean; filtersTitle?: string; isFiltering?: boolean; @@ -26,107 +23,155 @@ type DataActionsBarProps = { setChosenSorting: Dispatch>; setFiltersOpen?: Dispatch>; setSearchText: Dispatch>; - setSortOpen: Dispatch>; - sortOpen: boolean; - sortOptions?: { - key: string; - label: string; - }[]; + setSortOpen?: Dispatch>; + sortOpen?: boolean; + sortOptions?: { key: string; label: string }[]; }; -export const DataActionsBar: FC = ({ ...props }) => { - const { - chosenFilters = [], - chosenFiltersLength, - chosenSorting, - closeFilters = () => {}, - closeSorts, - filterOptions = [], - filtersOpen, - filtersTitle, - isFiltering = true, - searchText, - setChosenFilters = () => {}, - setChosenSorting, - setFiltersOpen, - setSearchText, - setSortOpen, - sortOpen, - sortOptions = [], - placeholder = "Search...", - } = props; - const { - palette: { boxShadow2 }, - } = theme; +export const DataActionsBar: FC = ({ + chosenFilters = [], + chosenFiltersLength, + chosenSorting, + closeFilters = () => {}, + closeSorts, + filterOptions = [], + filtersOpen, + filtersTitle, + isFiltering = true, + searchText, + setChosenFilters = () => {}, + setChosenSorting, + setFiltersOpen, + setSearchText, + setSortOpen, + sortOpen, + sortOptions = [], + placeholder = "Search...", +}) => { + const [localFiltersOpen, setLocalFiltersOpen] = useState(false); + const [localSortOpen, setLocalSortOpen] = useState(false); + + const effectiveFiltersOpen = filtersOpen ?? localFiltersOpen; + const effectiveSortOpen = sortOpen ?? localSortOpen; + const setEffectiveFiltersOpen = setFiltersOpen ?? setLocalFiltersOpen; + const setEffectiveSortOpen = setSortOpen ?? setLocalSortOpen; + + const selectedFilterItems = useMemo( + () => + (chosenFilters ?? []).map((key) => ({ + key, + label: (filterOptions ?? []).find((o) => o.key === key)?.label ?? key, + })), + [chosenFilters, filterOptions], + ); + + const handleRemoveFilter = (key: string) => + setChosenFilters?.((prev) => (prev ?? []).filter((k) => k !== key)); return ( - - setSearchText(e.target.value)} - placeholder={placeholder} - value={searchText} - startAdornment={ - - } - endAdornment={ - searchText && ( - setSearchText("")} - sx={{ ml: 1 }} - > - - - ) - } - sx={{ - bgcolor: "white", - border: 1, - borderColor: "secondaryBlue", - borderRadius: 50, - boxShadow: `2px 2px 20px 0px ${boxShadow2}`, - fontSize: 11, - fontWeight: 500, - height: 48, - padding: "16px 24px", - maxWidth: 500, - }} - /> - + - {filtersOpen && ( - - )} - {sortOpen && ( - - )} - + setSearchText(e.target.value)} + placeholder={placeholder} + value={searchText} + startAdornment={ + + } + endAdornment={ + searchText && ( + setSearchText("")} + sx={{ ml: 1 }} + > + + + ) + } + sx={{ + bgcolor: "white", + border: 1, + borderColor: "#6E87D9", + borderRadius: 50, + boxShadow: "2px 2px 20px 0 rgba(0,0,0,0.05)", + fontSize: 11, + fontWeight: 500, + height: 48, + padding: "16px 24px", + maxWidth: 500, + flex: "0 0 auto", + }} + /> + + + {effectiveFiltersOpen && ( + + )} + {effectiveSortOpen && ( + + )} + + + + {selectedFilterItems.length > 0 && ( + *": { flex: "0 0 auto", alignSelf: "flex-start" }, + }} + > + {selectedFilterItems.map(({ key, label }) => ( + handleRemoveFilter(key)} + deleteIconPosition="left" + bgColor="#B9CCF5" + size="small" + /> + ))} + + )} ); }; + +export default DataActionsBar; From 3681b72cd849945f4ee996aa7c08bc75578daed7 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Wed, 3 Sep 2025 13:47:34 +0545 Subject: [PATCH 02/11] fix(vote): update vote flow and validate context metadata; fix tests 4N and 4O --- .../4-proposal-visibility/proposalVisibility.dRep.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts index ff40cf000..58b52b277 100644 --- a/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/4-proposal-visibility/proposalVisibility.dRep.spec.ts @@ -66,9 +66,14 @@ test.describe("Logged in DRep", () => { GovernanceActionType.InfoAction ) : await govActionsPage.viewFirstProposal(); + + await govActionDetailsPage.yesVoteRadio.click(); + await govActionDetailsPage.voteBtn.click() + await govActionDetailsPage.contextInput.fill(faker.lorem.sentence(200)); await govActionDetailsPage.confirmModalBtn.click(); + await govActionDetailsPage.downloadAndStoreYourselfOptionBtn.click(); await page.getByRole("checkbox").click(); await govActionDetailsPage.confirmModalBtn.click(); }); From e6e6b0b8bc0535e0b1766ba345662f4f79d34a48 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Thu, 4 Sep 2025 13:27:54 +0545 Subject: [PATCH 03/11] chore: update vote rationale context validation logic --- .../proposalFunctionality.dRep.spec.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts index 4d8052543..b728d9148 100644 --- a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts +++ b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts @@ -259,13 +259,12 @@ test.describe("Perform voting", () => { govActionDetailsPage = await governanceActionsPage.viewFirstVotedProposal(); + await govActionDetailsPage.currentPage.getByTestId("yes-radio").isVisible(); - - await govActionDetailsPage.currentPage.getByTestId("show-more-button").click(); - await govActionDetailsPage.currentPage.waitForTimeout(2000); - const voteRationaleContext = await govActionDetailsPage.currentPage.getByTestId("vote-rationale-context"); - await expect(voteRationaleContext).toContainText(fakerContext); + const voteRationaleContext = await govActionDetailsPage.currentPage.getByTestId("vote-rationale-context").textContent(); + + expect(voteRationaleContext).toEqual(fakerContext); }); test("5I. Should view the vote details,when viewing governance action already voted by the DRep", async ({}, testInfo) => { From 5c351b38bbec629e9537c1008d41a52e40f90263 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Thu, 4 Sep 2025 14:33:40 +0545 Subject: [PATCH 04/11] fix: update Random sort option to Activity sort for DReps list display related tests --- .../playwright/lib/helpers/dRep.ts | 5 +++-- .../tests/2-delegation/delegation.spec.ts | 14 ++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/helpers/dRep.ts b/tests/govtool-frontend/playwright/lib/helpers/dRep.ts index 262429552..90a08974e 100644 --- a/tests/govtool-frontend/playwright/lib/helpers/dRep.ts +++ b/tests/govtool-frontend/playwright/lib/helpers/dRep.ts @@ -17,8 +17,9 @@ export async function fetchFirstActiveDRepDetails(page: Page) { let dRepGivenName: string; let dRepId: string; let dRepDirectoryPage: DRepDirectoryPage; + let routePath = "**/drep/list?page=0&pageSize=5&sort=Activity&**"; await page.route( - "**/drep/list?page=0&pageSize=10&sort=Random&**", + routePath, async (route) => { const response = await route.fetch(); const json = await response.json(); @@ -40,7 +41,7 @@ export async function fetchFirstActiveDRepDetails(page: Page) { ); const responsePromise = page.waitForResponse( - "**/drep/list?page=0&pageSize=10&sort=Random&**" + routePath ); await functionWaitedAssert( diff --git a/tests/govtool-frontend/playwright/tests/2-delegation/delegation.spec.ts b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.spec.ts index b5b859ac2..5b53ef11c 100644 --- a/tests/govtool-frontend/playwright/tests/2-delegation/delegation.spec.ts +++ b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.spec.ts @@ -13,7 +13,7 @@ test.beforeEach(async () => { }); enum SortOption { - Random = "Random", + Activity = "Activity", RegistrationDate = "RegistrationDate", VotingPower = "VotingPower", Status = "Status", @@ -51,7 +51,8 @@ test("2K_2. Should sort DReps", async ({ page }) => { ); }); -test("2K_3. Should sort DReps randomly", async ({ page }) => { +test("2K_3. Should sort DReps randomly (Deprecated)", async ({ page }) => { + test.skip() const dRepDirectory = new DRepDirectoryPage(page); await dRepDirectory.goto(); @@ -60,13 +61,13 @@ test("2K_3. Should sort DReps randomly", async ({ page }) => { await page.getByTestId(`${SortOption.RegistrationDate}-radio`).click(); const dRepList1: IDRep[] = await dRepDirectory.getDRepsResponseFromApi( - SortOption.Random + SortOption.Activity ); await page.getByTestId(`${SortOption.RegistrationDate}-radio`).click(); const dRepList2: IDRep[] = await dRepDirectory.getDRepsResponseFromApi( - SortOption.Random + SortOption.Activity ); // Extract dRepIds from both lists @@ -82,11 +83,12 @@ test("2K_3. Should sort DReps randomly", async ({ page }) => { expect(isOrderDifferent).toBe(true); }); -test("2O. Should load more DReps on show more", async ({ page }) => { +test("2O. Should load more DReps on show more (Deprecated)", async ({ page }) => { + test.skip(); const responsePromise = page.waitForResponse((response) => response .url() - .includes(`drep/list?page=1&pageSize=10&sort=${SortOption.Random}`) + .includes(`drep/list?page=1&pageSize=10&sort=${SortOption.Activity}`) ); const dRepDirectory = new DRepDirectoryPage(page); await dRepDirectory.goto(); From 33378f132a9e54a49b0c6774a347efb903d2668b Mon Sep 17 00:00:00 2001 From: Adam Tomaszczyk Date: Thu, 4 Sep 2025 15:42:36 +0200 Subject: [PATCH 05/11] Unify and Enhance Search Experience in Live Voting Section #4008 --- .../organisms/DashboardGovernanceActions.tsx | 81 ++------ .../DashboardGovernanceActionsVotedOn.tsx | 125 +++++------- .../organisms/GovernanceActionsToVote.tsx | 193 ++++++++++++------ .../frontend/src/context/dataActionsBar.tsx | 9 +- .../src/hooks/queries/useGetDRepVotesQuery.ts | 6 +- .../frontend/src/pages/GovernanceActions.tsx | 50 +---- 6 files changed, 215 insertions(+), 249 deletions(-) diff --git a/govtool/frontend/src/components/organisms/DashboardGovernanceActions.tsx b/govtool/frontend/src/components/organisms/DashboardGovernanceActions.tsx index 101ec0f52..1d1ac201f 100644 --- a/govtool/frontend/src/components/organisms/DashboardGovernanceActions.tsx +++ b/govtool/frontend/src/components/organisms/DashboardGovernanceActions.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback, useMemo } from "react"; +import { useState, useEffect, useCallback } from "react"; import { Box, CircularProgress, Tab, Tabs, styled } from "@mui/material"; import { useLocation, useNavigate } from "react-router-dom"; @@ -9,13 +9,7 @@ import { PDF_PATHS, } from "@consts"; import { useCardano, useDataActionsBar, useFeatureFlag } from "@context"; -import { - useGetDRepVotesQuery, - useGetProposalsQuery, - useGetVoterInfo, - useScreenDimension, - useTranslation, -} from "@hooks"; +import { useGetVoterInfo, useScreenDimension, useTranslation } from "@hooks"; import { DataActionsBar } from "@molecules"; import { GovernanceActionsToVote, @@ -29,10 +23,6 @@ type TabPanelProps = { value: number; }; -const defaultCategories = GOVERNANCE_ACTIONS_FILTERS.map( - (category) => category.key, -); - const CustomTabPanel = (props: TabPanelProps) => { const { children, value, index } = props; @@ -53,6 +43,8 @@ const CustomTabPanel = (props: TabPanelProps) => { ); }; +const temporaryProposeButtonRemove: boolean = true; + type StyledTabProps = { label: string; }; @@ -71,60 +63,16 @@ const StyledTab = styled((props: StyledTabProps) => ( })); export const DashboardGovernanceActions = () => { - const { debouncedSearchText, isAdjusting, ...dataActionsBarProps } = + const { debouncedSearchText, ...dataActionsBarProps } = useDataActionsBar(); - const { chosenFilters, chosenSorting } = dataActionsBarProps; const { voter } = useGetVoterInfo(); const { isMobile } = useScreenDimension(); const { t } = useTranslation(); const { isEnableLoading } = useCardano(); const { isProposalDiscussionForumEnabled } = useFeatureFlag(); const navigate = useNavigate(); - - const queryFilters = - chosenFilters.length > 0 ? chosenFilters : defaultCategories; - - const { proposals, isProposalsLoading } = useGetProposalsQuery({ - filters: queryFilters, - sorting: chosenSorting, - searchPhrase: debouncedSearchText, - enabled: !isAdjusting, - }); - const { data: votes, areDRepVotesLoading } = useGetDRepVotesQuery( - queryFilters, - chosenSorting, - debouncedSearchText, - ); - - // White Magic :) - const shouldFilter = - voter?.isRegisteredAsDRep || voter?.isRegisteredAsSoleVoter; - - const filteredProposals = useMemo(() => { - if (!shouldFilter || !proposals || !votes) return proposals; - - return proposals - .map((proposalCategory) => { - const filteredActions = proposalCategory.actions.filter((action) => { - const hasVote = votes.some((voteCategory) => - voteCategory.actions.some( - (voteAction) => - voteAction.proposal.txHash === action.txHash && - voteAction.proposal.index === action.index, - ), - ); - return !hasVote; - }); - - return { - ...proposalCategory, - actions: filteredActions, - }; - }) - .filter((category) => category.actions.length > 0); - }, [proposals, votes, shouldFilter]); - const { state } = useLocation(); + const [content, setContent] = useState( state?.isVotedListOnLoad ? 1 : 0, ); @@ -160,7 +108,7 @@ export const DashboardGovernanceActions = () => { filtersTitle={t("govActions.filterTitle")} sortOptions={GOVERNANCE_ACTIONS_SORTING} /> - {!proposals || !voter || isEnableLoading || isProposalsLoading ? ( + {!voter || isEnableLoading ? ( { data-testid="proposal-discussion-link" onClick={onClickPropose} sx={{ - display: isMobile ? "none" : "block", + display: + isMobile || temporaryProposeButtonRemove + ? "none" + : "block", ml: "auto", }} > @@ -218,19 +169,11 @@ export const DashboardGovernanceActions = () => { - + diff --git a/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx b/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx index 748970d17..670d50498 100644 --- a/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx +++ b/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx @@ -1,67 +1,65 @@ import { useMemo } from "react"; import { Box, Typography, CircularProgress } from "@mui/material"; -import { useCardano } from "@context"; -import { useScreenDimension, useTranslation } from "@hooks"; -import { Slider, ValidatedGovernanceVotedOnCard } from "@organisms"; -import { getFullGovActionId, getProposalTypeLabel } from "@utils"; -import { VotedProposal } from "@/models"; +import { useCardano, useDataActionsBar } from "@context"; +import { + useGetDRepVotesQuery, + useScreenDimension, + useTranslation, +} from "@hooks"; +import { ValidatedGovernanceVotedOnCard } from "@organisms"; +import { getFullGovActionId } from "@utils"; type DashboardGovernanceActionsVotedOnProps = { searchPhrase?: string; - votes: { - title: string; - actions: VotedProposal[]; - }[]; - areDRepVotesLoading: boolean; }; export const DashboardGovernanceActionsVotedOn = ({ searchPhrase, - votes, - areDRepVotesLoading, }: DashboardGovernanceActionsVotedOnProps) => { - const { isMobile } = useScreenDimension(); + const { isMobile, screenWidth } = useScreenDimension(); const { pendingTransaction } = useCardano(); const { t } = useTranslation(); + const { ...dataActionsBarProps } = useDataActionsBar(); + const { chosenSorting, chosenFilters } = dataActionsBarProps; - // TODO: Filtering here is some kind of craziness. It should be done on the backend. + const { + data: votes, + areDRepVotesLoading, + isFetching, + } = useGetDRepVotesQuery(chosenFilters, chosenSorting, searchPhrase); + // TODO: Filtering here is some kind of craziness. It should be done on the backend. const filteredData = useMemo(() => { if (!votes?.length) return []; - if (!searchPhrase) return votes; + if (!searchPhrase) return votes.flatMap((entry) => entry.actions); + const lowerSearch = searchPhrase.toLowerCase(); - return votes - .map((entry) => { - const filteredActions = entry.actions.filter((action) => { - const hash = getFullGovActionId( - action.proposal.txHash, - action.proposal.index, - ).toLowerCase(); - const title = action.proposal.title?.toLowerCase() || ""; - const motivation = action.proposal.motivation?.toLowerCase() || ""; - const rationale = action.proposal.rationale?.toLowerCase() || ""; - const abstract = action.proposal.abstract?.toLowerCase() || ""; + return votes.flatMap((entry) => + entry.actions.filter((action) => { + const hash = getFullGovActionId( + action.proposal.txHash, + action.proposal.index, + ).toLowerCase(); - return ( - hash.includes(lowerSearch) || - title.includes(lowerSearch) || - motivation.includes(lowerSearch) || - rationale.includes(lowerSearch) || - abstract.includes(lowerSearch) - ); - }); + const title = action.proposal.title?.toLowerCase() || ""; + const motivation = action.proposal.motivation?.toLowerCase() || ""; + const rationale = action.proposal.rationale?.toLowerCase() || ""; + const abstract = action.proposal.abstract?.toLowerCase() || ""; - return { - ...entry, - actions: filteredActions, - }; - }) - .filter((entry) => entry.actions.length > 0); + return ( + hash.includes(lowerSearch) || + title.includes(lowerSearch) || + motivation.includes(lowerSearch) || + rationale.includes(lowerSearch) || + abstract.includes(lowerSearch) + ); + }), + ); }, [votes, searchPhrase, pendingTransaction.vote]); - return areDRepVotesLoading ? ( + return areDRepVotesLoading || isFetching ? ( @@ -76,36 +74,25 @@ export const DashboardGovernanceActionsVotedOn = ({ {t("govActions.noResultsForTheSearch")} ) : ( - <> - {filteredData?.map((item) => ( -
- ( -
- -
- ))} + + {filteredData.map((item) => ( + + - -
+
))} - +
)} ); diff --git a/govtool/frontend/src/components/organisms/GovernanceActionsToVote.tsx b/govtool/frontend/src/components/organisms/GovernanceActionsToVote.tsx index 95729b3fa..4154474cb 100644 --- a/govtool/frontend/src/components/organisms/GovernanceActionsToVote.tsx +++ b/govtool/frontend/src/components/organisms/GovernanceActionsToVote.tsx @@ -1,84 +1,157 @@ -import { Box } from "@mui/material"; +import { Box, CircularProgress } from "@mui/material"; import { Typography } from "@atoms"; -import { useCardano } from "@context"; -import { useScreenDimension, useTranslation } from "@hooks"; -import { ProposalData } from "@models"; -import { getProposalTypeTitle } from "@utils"; -import { Slider, ValidatedGovernanceActionCard } from "@organisms"; +import { useCardano, useDataActionsBar } from "@context"; +import { + useFetchNextPageDetector, + useGetDRepVotesQuery, + useGetProposalsInfiniteQuery, + useGetVoterInfo, + useSaveScrollPosition, + useScreenDimension, + useTranslation, +} from "@hooks"; +import { removeDuplicatedProposals } from "@utils"; +import { ValidatedGovernanceActionCard } from "@organisms"; +import { useMemo, useRef } from "react"; type GovernanceActionsToVoteProps = { - filters: string[]; - sorting: string; - proposals: { title: string; actions: ProposalData[] }[]; onDashboard?: boolean; - searchPhrase?: string; }; export const GovernanceActionsToVote = ({ - filters, onDashboard = true, - proposals, - searchPhrase, - sorting, }: GovernanceActionsToVoteProps) => { - const { pendingTransaction } = useCardano(); - const { isMobile, pagePadding } = useScreenDimension(); + const { pendingTransaction, isEnableLoading } = useCardano(); + const { isMobile, screenWidth } = useScreenDimension(); + const { debouncedSearchText, ...dataActionsBarProps } = useDataActionsBar(); + const { chosenSorting, chosenFilters } = dataActionsBarProps; + const { voter } = useGetVoterInfo(); const { t } = useTranslation(); + const { + isProposalsFetching, + isProposalsFetchingNextPage, + isProposalsLoading, + proposals, + proposalsfetchNextPage, + proposalsHaveNextPage, + } = useGetProposalsInfiniteQuery({ + filters: chosenFilters, + sorting: chosenSorting, + searchPhrase: debouncedSearchText, + }); + const loadNextPageRef = useRef(null); + + useFetchNextPageDetector( + proposalsfetchNextPage, + isProposalsLoading || isProposalsFetchingNextPage, + proposalsHaveNextPage, + ); + + const { + data: votes, + areDRepVotesLoading, + isFetching: isFetchingVotes, + } = useGetDRepVotesQuery(chosenFilters, chosenSorting, debouncedSearchText); + + const saveScrollPosition = useSaveScrollPosition( + isProposalsLoading, + isProposalsFetching, + ); + + const mappedData = useMemo( + () => removeDuplicatedProposals(proposals), + [proposals, voter?.isRegisteredAsDRep, isProposalsFetchingNextPage], + ); + + // TODO: Filtering here is some kind of craziness. It should be done on the backend. + const filteredProposals = useMemo(() => { + const list = mappedData ?? []; + if (!votes?.length) return list; + + const proposalsFromVotes = votes + .flatMap((v) => v?.actions ?? []) + .map((a) => a?.proposal) + .filter(Boolean); + + const votedKeys = new Set( + proposalsFromVotes + .map((p) => ({ + id: p?.id ?? p?.id, + tx: p?.txHash ?? p?.txHash, + })) + .filter(({ id, tx }) => Boolean(id && tx)) + .map(({ id, tx }) => `${id}:${tx}`), + ); + + if (votedKeys.size === 0) return list; + + return list.filter((p) => { + const id = p?.id ?? p?.id; + const tx = p?.txHash ?? p?.txHash; + if (!id || !tx) return true; + return !votedKeys.has(`${id}:${tx}`); + }); + }, [mappedData, voter?.isRegisteredAsDRep, isProposalsFetchingNextPage]); + return ( <> - {!proposals?.length ? ( + {!filteredProposals || + isEnableLoading || + isProposalsLoading || + areDRepVotesLoading || + isFetchingVotes ? ( + + + + ) : !filteredProposals?.length ? ( {t("govActions.noResultsForTheSearch")} ) : ( - <> - {proposals?.map((item, index) => ( - - ( -
- -
- ))} - dataLength={item.actions.slice(0, 6)?.length} - filters={filters} - navigateKey={item.title} - notSlicedDataLength={item.actions?.length} - onDashboard={onDashboard} - searchPhrase={searchPhrase} - sorting={sorting} - title={getProposalTypeTitle(item.title)} + + {filteredProposals.map((item) => ( + + { + saveScrollPosition(); + }} + txHash={item.txHash} /> - {index < proposals.length - 1 && ( - - )} ))} - + {proposalsHaveNextPage && isProposalsFetchingNextPage && ( + + + + )} + )} ); diff --git a/govtool/frontend/src/context/dataActionsBar.tsx b/govtool/frontend/src/context/dataActionsBar.tsx index 2bf291edd..1d296e79e 100644 --- a/govtool/frontend/src/context/dataActionsBar.tsx +++ b/govtool/frontend/src/context/dataActionsBar.tsx @@ -75,8 +75,7 @@ const DataActionsBarProvider: FC = ({ children }) => { (!pathname.startsWith(lastPath) || lastPath === "" || lastPath === "/"); const userOpenedGADetails = gADetailsPathnameRegexp.test(pathname); const userOpenedGADetailsFromCategoryPage = - userOpenedGADetails && - lastPath.includes("governance_actions/category"); + userOpenedGADetails && lastPath.includes("governance_actions/category"); const userMovedFromGAListToCategoryPage = lastPath.endsWith("governance_actions") && pathname.includes("governance_actions/category"); @@ -84,6 +83,10 @@ const DataActionsBarProvider: FC = ({ children }) => { (gADetailsPathnameRegexp.test(lastPath) && pathname.includes("governance_actions")) || pathname.includes("governance_actions/category"); + const isSearchOrFilterSet = + debouncedSearchText.length > 0 || + chosenSorting !== "" || + chosenFilters.length > 0; useEffect(() => { isAdjusting.current = true; @@ -91,7 +94,7 @@ const DataActionsBarProvider: FC = ({ children }) => { return; } - if (userMovedFromGADetailsToListOrCategoryPage && debouncedSearchText.length > 0) { + if (userMovedFromGADetailsToListOrCategoryPage && isSearchOrFilterSet) { isAdjusting.current = false; return; } diff --git a/govtool/frontend/src/hooks/queries/useGetDRepVotesQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepVotesQuery.ts index 0878191bc..4d1082f12 100644 --- a/govtool/frontend/src/hooks/queries/useGetDRepVotesQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDRepVotesQuery.ts @@ -8,11 +8,11 @@ import { VotedProposal } from "@/models"; export const useGetDRepVotesQuery = ( type?: string[], sort?: string, - search?: string, + search?: string ) => { const { dRepID, pendingTransaction } = useCardano(); - const { data, isLoading, refetch, isRefetching } = useQuery({ + const { data, isLoading, refetch, isFetching } = useQuery({ queryKey: [ QUERY_KEYS.useGetDRepVotesKey, pendingTransaction.vote?.transactionHash, @@ -62,6 +62,6 @@ export const useGetDRepVotesQuery = ( }[], areDRepVotesLoading: isLoading, refetch, - isRefetching, + isFetching, }; }; diff --git a/govtool/frontend/src/pages/GovernanceActions.tsx b/govtool/frontend/src/pages/GovernanceActions.tsx index a461c1a6a..5dbea1e2b 100644 --- a/govtool/frontend/src/pages/GovernanceActions.tsx +++ b/govtool/frontend/src/pages/GovernanceActions.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { useNavigate } from "react-router-dom"; -import { Box, CircularProgress, Divider } from "@mui/material"; +import { Box, Divider } from "@mui/material"; import { Background, ScrollToManage, Typography } from "@atoms"; import { @@ -9,38 +9,19 @@ import { PATHS, } from "@consts"; import { useCardano, useDataActionsBar } from "@context"; -import { - useGetProposalsQuery, - useScreenDimension, - useTranslation, -} from "@hooks"; +import { useScreenDimension, useTranslation } from "@hooks"; import { DataActionsBar } from "@molecules"; import { Footer, TopNav, GovernanceActionsToVote } from "@organisms"; import { WALLET_LS_KEY, getItemFromLocalStorage } from "@utils"; -const defaultCategories = GOVERNANCE_ACTIONS_FILTERS.map( - (category) => category.key, -); - export const GovernanceActions = () => { - const { debouncedSearchText, isAdjusting, ...dataActionsBarProps } = + const { ...dataActionsBarProps } = useDataActionsBar(); - const { chosenFilters, chosenSorting } = dataActionsBarProps; const { isMobile, pagePadding } = useScreenDimension(); const { isEnabled } = useCardano(); const navigate = useNavigate(); const { t } = useTranslation(); - const queryFilters = - chosenFilters.length > 0 ? chosenFilters : defaultCategories; - - const { proposals, isProposalsLoading } = useGetProposalsQuery({ - filters: queryFilters, - sorting: chosenSorting, - searchPhrase: debouncedSearchText, - enabled: !isAdjusting, - }); - useEffect(() => { if (isEnabled && getItemFromLocalStorage(`${WALLET_LS_KEY}_stake_key`)) { navigate(PATHS.dashboardGovernanceActions); @@ -96,29 +77,8 @@ export const GovernanceActions = () => { filtersTitle={t("govActions.filterTitle")} sortOptions={GOVERNANCE_ACTIONS_SORTING} /> - {!proposals || isProposalsLoading ? ( - - - - ) : ( - <> - - - - )} + + {/* FIXME: Footer should be on top of the layout. From dd7a423e73457fd130d2eb974d18fdb49d674ee7 Mon Sep 17 00:00:00 2001 From: joseph rana Date: Fri, 5 Sep 2025 13:34:48 +0545 Subject: [PATCH 06/11] fix drep vote calculation for proposal display tests --- .../playwright/lib/pages/outcomeDetailsPage.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts b/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts index 6ead1d8be..1c25fcf6d 100644 --- a/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts +++ b/tests/govtool-frontend/playwright/lib/pages/outcomeDetailsPage.ts @@ -94,7 +94,7 @@ export default class OutcomeDetailsPage { isLoggedIn = false ) { await Promise.all( - Object.keys(outcomeType).map(async (filterKey) => { + Object.entries(outcomeType).map(async ([filterKey, filterValue]) => { const outcomePage = new OutComesPage(this.page); const { govActionDetailsPage, @@ -106,6 +106,7 @@ export default class OutcomeDetailsPage { isLoggedIn ); + if (!govActionDetailsPage) { return; } @@ -120,6 +121,11 @@ export default class OutcomeDetailsPage { metricsResponse ); + const metricsResponseJson = await metricsResponse.json(); + const totalStakeControlledByNoConfidence = Number(metricsResponseJson.always_no_confidence_voting_power) + const dRepYesVotes = filterValue === outcomeType.NoConfidence ? Number(proposalToCheck.yes_votes) + totalStakeControlledByNoConfidence : Number(proposalToCheck.yes_votes); + const dRepNoVotes = filterValue != outcomeType.NoConfidence ? Number(proposalToCheck.no_votes) + totalStakeControlledByNoConfidence : Number(proposalToCheck.no_votes) ; + const currentPageUrl = govActionDetailsPage.currentPage.url(); // check dRep votes @@ -134,7 +140,7 @@ export default class OutcomeDetailsPage { message: `DRep "Yes" voting power checked for ${currentPageUrl}`, } ).toHaveText( - `Yes${formatWithThousandSeparator(proposalToCheck.yes_votes, false)}`, + `Yes${formatWithThousandSeparator(dRepYesVotes, false)}`, { timeout: 60_000, } @@ -177,7 +183,7 @@ export default class OutcomeDetailsPage { message: `DRep "No" voting power checked for ${currentPageUrl}`, } ).toHaveText( - `No${formatWithThousandSeparator(proposalToCheck.no_votes, false)}` + `No${formatWithThousandSeparator(dRepNoVotes, false)}` ); //BUG missing testIds } From 09bf4c3be4d0499a15003143f5384820e59d9ee8 Mon Sep 17 00:00:00 2001 From: Adam Tomaszczyk Date: Fri, 5 Sep 2025 10:47:20 +0200 Subject: [PATCH 07/11] =?UTF-8?q?=F0=9F=90=9B=20Governance=20Action=20Page?= =?UTF-8?q?=20Fails=20When=20Index=20Is=20Missing=20in=20URL=20and=20walle?= =?UTF-8?q?t=20not=20connected=20#4058?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- govtool/frontend/src/pages/GovernanceActionDetails.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/govtool/frontend/src/pages/GovernanceActionDetails.tsx b/govtool/frontend/src/pages/GovernanceActionDetails.tsx index 60e9ccfc9..e43b3242d 100644 --- a/govtool/frontend/src/pages/GovernanceActionDetails.tsx +++ b/govtool/frontend/src/pages/GovernanceActionDetails.tsx @@ -44,8 +44,8 @@ export const GovernanceActionDetails = () => { const { t } = useTranslation(); const { proposalId: txHash } = useParams(); - const fullProposalId = txHash && getFullGovActionId(txHash, index); - const shortenedGovActionId = txHash && getShortenedGovActionId(txHash, index); + const fullProposalId = txHash && getFullGovActionId(txHash, +index); + const shortenedGovActionId = txHash && getShortenedGovActionId(txHash, +index); const { data, isLoading, error } = useGetProposalQuery( fullProposalId ?? "", From 18726f12dabcfdb85669792103a34efd8918a4fc Mon Sep 17 00:00:00 2001 From: Miro Date: Fri, 5 Sep 2025 16:04:21 +0200 Subject: [PATCH 08/11] Issue 4062 Filter ans Sorting Icons goes to new line --- .../components/molecules/DataActionsBar.tsx | 75 +++++----- .../organisms/DashboardDrawerMobile.tsx | 129 ++++++++++-------- 2 files changed, 114 insertions(+), 90 deletions(-) diff --git a/govtool/frontend/src/components/molecules/DataActionsBar.tsx b/govtool/frontend/src/components/molecules/DataActionsBar.tsx index 7839af965..f8de6e97c 100644 --- a/govtool/frontend/src/components/molecules/DataActionsBar.tsx +++ b/govtool/frontend/src/components/molecules/DataActionsBar.tsx @@ -74,8 +74,9 @@ export const DataActionsBar: FC = ({ alignItems="center" display="flex" justifyContent="space-between" - gap={1.5} - flexWrap="wrap" + gap={{ xs: 0.75, sm: 1.5 }} + flexWrap="nowrap" + width="100%" > = ({ fontWeight: 500, height: 48, padding: "16px 24px", - maxWidth: 500, - flex: "0 0 auto", + flex: "1 1 auto", + minWidth: 0, + maxWidth: "none", }} /> - - {effectiveFiltersOpen && ( - - )} - {effectiveSortOpen && ( - - )} - + + {effectiveFiltersOpen && ( + + )} + {effectiveSortOpen && ( + + )} + +
{selectedFilterItems.length > 0 && ( @@ -153,10 +163,7 @@ export const DataActionsBar: FC = ({ flexWrap="wrap" gap={1} alignItems="flex-start" - sx={{ - mt: 2, - "& > *": { flex: "0 0 auto", alignSelf: "flex-start" }, - }} + sx={{ mt: 2, "& > *": { flex: "0 0 auto", alignSelf: "flex-start" } }} > {selectedFilterItems.map(({ key, label }) => ( { - const { isProposalDiscussionForumEnabled, isGovernanceOutcomesPillarEnabled } = useFeatureFlag(); + const { + isProposalDiscussionForumEnabled, + isGovernanceOutcomesPillarEnabled, + } = useFeatureFlag(); const { screenWidth } = useScreenDimension(); const { voter } = useGetVoterInfo(); - const openDrawer = () => { - setIsDrawerOpen(true); - }; + const openDrawer = () => setIsDrawerOpen(true); + const closeDrawer = () => setIsDrawerOpen(false); - const closeDrawer = () => { - setIsDrawerOpen(false); - }; + const navItems = CONNECTED_NAV_ITEMS as unknown as NavItem[]; return ( + - {CONNECTED_NAV_ITEMS.map((navItem) => ( - + {navItems.map((navItem, i) => ( + { - if (navItem.newTabLink) { - openInNewTab(navItem.newTabLink); - } - setIsDrawerOpen(false); - }} + if (navItem.newTabLink) openInNewTab(navItem.newTabLink); + setIsDrawerOpen(false); + }} isConnectWallet /> - {navItem.childNavItems && ( - - {navItem.childNavItems.map((childItem) => { - if ( - !isProposalDiscussionForumEnabled && - childItem.dataTestId === "proposal-discussion-link" - ) { - return null; - } - - if ( - !isGovernanceOutcomesPillarEnabled && - (childItem.dataTestId === "governance-actions-voted-by-me-link" || - childItem.dataTestId === "governance-actions-outcomes-link") - ) { - return null; - } - return ( - { - if (childItem.newTabLink) { - openInNewTab(childItem.newTabLink); - } - setIsDrawerOpen(false); - }} - isConnectWallet - /> - ); - })} - - )} + {!!navItem.childNavItems?.length && ( + + {navItem.childNavItems.map((childItem, ci) => { + if ( + !isProposalDiscussionForumEnabled && + childItem.dataTestId === "proposal-discussion-link" + ) { + return null; + } + if ( + !isGovernanceOutcomesPillarEnabled && + (childItem.dataTestId === + "governance-actions-voted-by-me-link" || + childItem.dataTestId === + "governance-actions-outcomes-link") + ) { + return null; + } + return ( + { + if (childItem.newTabLink) + openInNewTab(childItem.newTabLink); + setIsDrawerOpen(false); + }} + isConnectWallet + /> + ); + })} + + )} - ))} + ))} + {(voter?.isRegisteredAsDRep || voter?.isRegisteredAsSoleVoter) && ( )} From 10eb266cd987c50f0ab5e5cb78e103d853f49fd9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 09:09:46 +0000 Subject: [PATCH 09/11] chore: update @intersect.mbo/govtool-outcomes-pillar-ui to v1.5.8 --- govtool/frontend/package-lock.json | 8 ++++---- govtool/frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/govtool/frontend/package-lock.json b/govtool/frontend/package-lock.json index 0d75d1dca..3264860b8 100644 --- a/govtool/frontend/package-lock.json +++ b/govtool/frontend/package-lock.json @@ -13,7 +13,7 @@ "@emotion/styled": "^11.11.0", "@emurgo/cardano-serialization-lib-asmjs": "^14.1.1", "@hookform/resolvers": "^3.3.1", - "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.5.7", + "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.5.8", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", "@intersect.mbo/pdf-ui": "1.0.13-beta", "@mui/icons-material": "^5.14.3", @@ -3392,9 +3392,9 @@ } }, "node_modules/@intersect.mbo/govtool-outcomes-pillar-ui": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/@intersect.mbo/govtool-outcomes-pillar-ui/-/govtool-outcomes-pillar-ui-1.5.7.tgz", - "integrity": "sha512-GGJCDTkwOb4T1DHZ0lQ2RzNL/BoY3y/+wkrpLIoJLtMaoVsa5i41JSx250By8gvVfmmpaLcnwhShQNkJsPRG1w==", + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@intersect.mbo/govtool-outcomes-pillar-ui/-/govtool-outcomes-pillar-ui-1.5.8.tgz", + "integrity": "sha512-uRLY+tvuV/lWxZCLpMKa72fcYKYAvE9FdfYn7s1KeLcbw8rpezFPu6dHlqVaF6zPngBu2UFwJqrB3FL6iSEs7Q==", "license": "ISC", "dependencies": { "@fontsource/poppins": "^5.0.14", diff --git a/govtool/frontend/package.json b/govtool/frontend/package.json index ad031c36c..cb57381aa 100644 --- a/govtool/frontend/package.json +++ b/govtool/frontend/package.json @@ -27,7 +27,7 @@ "@emotion/styled": "^11.11.0", "@emurgo/cardano-serialization-lib-asmjs": "^14.1.1", "@hookform/resolvers": "^3.3.1", - "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.5.7", + "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.5.8", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", "@intersect.mbo/pdf-ui": "1.0.13-beta", "@mui/icons-material": "^5.14.3", From 072fb706dfe730745ab514a2333e39db60c522ad Mon Sep 17 00:00:00 2001 From: Miro Date: Tue, 9 Sep 2025 11:29:07 +0200 Subject: [PATCH 10/11] Fix/Issue 4062 Filter ans Sorting Icons goes to new line --- .../src/components/molecules/DataActionsBar.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/govtool/frontend/src/components/molecules/DataActionsBar.tsx b/govtool/frontend/src/components/molecules/DataActionsBar.tsx index f8de6e97c..1ff86d706 100644 --- a/govtool/frontend/src/components/molecules/DataActionsBar.tsx +++ b/govtool/frontend/src/components/molecules/DataActionsBar.tsx @@ -75,7 +75,7 @@ export const DataActionsBar: FC = ({ display="flex" justifyContent="space-between" gap={{ xs: 0.75, sm: 1.5 }} - flexWrap="nowrap" + flexWrap={{ xs: "wrap", sm: "nowrap" }} width="100%" > = ({ /> } endAdornment={ - searchText && ( + searchText ? ( setSearchText("")} @@ -102,7 +102,7 @@ export const DataActionsBar: FC = ({ > - ) + ) : null } sx={{ bgcolor: "white", @@ -114,9 +114,9 @@ export const DataActionsBar: FC = ({ fontWeight: 500, height: 48, padding: "16px 24px", - flex: "1 1 auto", + flex: "1 1 0", minWidth: 0, - maxWidth: "none", + maxWidth: "100%", }} /> @@ -125,6 +125,9 @@ export const DataActionsBar: FC = ({ display: "flex", alignItems: "center", gap: { xs: 0.5, sm: 1.25 }, + flex: "0 0 auto", + flexShrink: 0, + mt: { xs: 1, sm: 0 }, }} > Date: Wed, 10 Sep 2025 12:42:36 +0000 Subject: [PATCH 11/11] chore: update @intersect.mbo/pdf-ui to 1.0.14-beta --- govtool/frontend/package-lock.json | 8 ++++---- govtool/frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/govtool/frontend/package-lock.json b/govtool/frontend/package-lock.json index 3264860b8..fa16b20ad 100644 --- a/govtool/frontend/package-lock.json +++ b/govtool/frontend/package-lock.json @@ -15,7 +15,7 @@ "@hookform/resolvers": "^3.3.1", "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.5.8", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", - "@intersect.mbo/pdf-ui": "1.0.13-beta", + "@intersect.mbo/pdf-ui": "1.0.14-beta", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", "@noble/ed25519": "^2.3.0", @@ -3426,9 +3426,9 @@ "license": "ISC" }, "node_modules/@intersect.mbo/pdf-ui": { - "version": "1.0.13-beta", - "resolved": "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-1.0.13-beta.tgz", - "integrity": "sha512-b9n5vVIQCkInvi+cecg6KEaGhnnR/anARgqj3aGcaegfSjFUu/B41SnT7MCb+MPot0X59kkMUpioO6wCiykG+A==", + "version": "1.0.14-beta", + "resolved": "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-1.0.14-beta.tgz", + "integrity": "sha512-d+soKsSE6xbNAmjqjZfs1HEsYLJeE9UTlM91kpMtujEeTTZDyg+EwbyjZ5a1ZpRZCD0D7j6tg69muSHxrz6BLg==", "dependencies": { "@emurgo/cardano-serialization-lib-asmjs": "^12.0.0-beta.2", "@fontsource/poppins": "^5.0.14", diff --git a/govtool/frontend/package.json b/govtool/frontend/package.json index cb57381aa..233ca770d 100644 --- a/govtool/frontend/package.json +++ b/govtool/frontend/package.json @@ -29,7 +29,7 @@ "@hookform/resolvers": "^3.3.1", "@intersect.mbo/govtool-outcomes-pillar-ui": "v1.5.8", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", - "@intersect.mbo/pdf-ui": "1.0.13-beta", + "@intersect.mbo/pdf-ui": "1.0.14-beta", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", "@noble/ed25519": "^2.3.0",