diff --git a/web-devtools/src/utils/parseWagmiError.ts b/web-devtools/src/utils/parseWagmiError.ts new file mode 100644 index 000000000..ffd1ef597 --- /dev/null +++ b/web-devtools/src/utils/parseWagmiError.ts @@ -0,0 +1,17 @@ +import { type SimulateContractErrorType } from "@wagmi/core"; + +type ExtendedWagmiError = SimulateContractErrorType & { shortMessage?: string; metaMessages?: string[] }; + +/** + * @param error + * @description Tries to extract the human readable error message, otherwise reverts to error.message + * @returns Human readable error if possible + */ +export const parseWagmiError = (error: SimulateContractErrorType) => { + const extError = error as ExtendedWagmiError; + + const metaMessage = extError?.metaMessages?.[0]; + const shortMessage = extError?.shortMessage; + + return metaMessage ?? shortMessage ?? error.message; +}; diff --git a/web-devtools/src/utils/wrapWithToast.ts b/web-devtools/src/utils/wrapWithToast.ts index 9e6459061..7405c73c8 100644 --- a/web-devtools/src/utils/wrapWithToast.ts +++ b/web-devtools/src/utils/wrapWithToast.ts @@ -1,6 +1,8 @@ import { toast, ToastPosition, Theme } from "react-toastify"; import { type PublicClient, type TransactionReceipt } from "viem"; +import { parseWagmiError } from "./parseWagmiError"; + export const OPTIONS = { position: "top-center" as ToastPosition, autoClose: 5000, @@ -35,11 +37,11 @@ export async function wrapWithToast( }) ) .catch((error) => { - toast.error(error.shortMessage ?? error.message, OPTIONS); + toast.error(parseWagmiError(error), OPTIONS); return { status: false }; }); } export async function catchShortMessage(promise: Promise) { - return await promise.catch((error) => toast.error(error.shortMessage ?? error.message, OPTIONS)); + return await promise.catch((error) => toast.error(parseWagmiError(error), OPTIONS)); } diff --git a/web/src/components/FileViewer/index.tsx b/web/src/components/FileViewer/index.tsx index 74b8438a3..fa67a9bb1 100644 --- a/web/src/components/FileViewer/index.tsx +++ b/web/src/components/FileViewer/index.tsx @@ -28,7 +28,7 @@ const StyledDocViewer = styled(DocViewer)` * @returns renders the file */ const FileViewer: React.FC<{ url: string }> = ({ url }) => { - const docs = [{ uri: url }]; + const docs = [{ uri: url, fileName: fileNameIfIpfsUrl(url) }]; return ( = ({ url }) => { ); }; +const fileNameIfIpfsUrl = (url: string) => { + if (!url || typeof url !== "string") { + return "document"; + } + const ipfsPattern = /(?:ipfs:\/\/|https?:\/\/(?:[A-Za-z0-9.-]+)\/ipfs\/)([A-Za-z0-9]+[A-Za-z0-9\-_]*)\/?(.*)/; + + const match = ipfsPattern.exec(url); + + if (match) { + const ipfsHash = match[1]; + const path = match[2] || ""; + + const sanitizedPath = path.replace(/\//g, "_"); + + return `ipfs-${ipfsHash}${sanitizedPath ? "_" + sanitizedPath : ""}`; + } else { + return "document"; + } +}; + export default FileViewer; diff --git a/web/src/hooks/queries/useEvidences.ts b/web/src/hooks/queries/useEvidences.ts index 743869619..3123163b9 100644 --- a/web/src/hooks/queries/useEvidences.ts +++ b/web/src/hooks/queries/useEvidences.ts @@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query"; import { REFETCH_INTERVAL } from "consts/index"; import { useGraphqlBatcher } from "context/GraphqlBatcher"; +import { transformSearch } from "utils/transformSearch"; import { graphql } from "src/graphql"; import { EvidenceDetailsFragment, EvidencesQuery } from "src/graphql/graphql"; @@ -45,6 +46,8 @@ export const useEvidences = (evidenceGroup?: string, keywords?: string) => { const { graphqlBatcher } = useGraphqlBatcher(); const document = keywords ? evidenceSearchQuery : evidencesQuery; + const transformedKeywords = transformSearch(keywords); + return useQuery<{ evidences: EvidenceDetailsFragment[] }>({ queryKey: [keywords ? `evidenceSearchQuery${evidenceGroup}-${keywords}` : `evidencesQuery${evidenceGroup}`], enabled: isEnabled, @@ -53,7 +56,7 @@ export const useEvidences = (evidenceGroup?: string, keywords?: string) => { const result = await graphqlBatcher.fetch({ id: crypto.randomUUID(), document: document, - variables: { evidenceGroupID: evidenceGroup?.toString(), keywords: keywords }, + variables: { evidenceGroupID: evidenceGroup?.toString(), keywords: transformedKeywords }, }); return keywords ? { evidences: [...result.evidenceSearch] } : result; diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx index 54bb6140f..3a0d1942c 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useEffect, useMemo } from "react"; +import styled from "styled-components"; import { useParams } from "react-router-dom"; import { useAccount, usePublicClient } from "wagmi"; @@ -19,10 +20,10 @@ import { } from "hooks/contracts/generated"; import { useCourtDetails } from "hooks/queries/useCourtDetails"; import { isUndefined } from "utils/index"; +import { parseWagmiError } from "utils/parseWagmiError"; import { wrapWithToast } from "utils/wrapWithToast"; import { EnsureChain } from "components/EnsureChain"; -import styled from "styled-components"; export enum ActionType { allowance = "allowance", @@ -154,9 +155,9 @@ const StakeWithdrawButton: React.FC = ({ useEffect(() => { if (setStakeError) { - setErrorMsg(setStakeError?.shortMessage ?? setStakeError.message); + setErrorMsg(parseWagmiError(setStakeError)); } - }, [setStakeError]); + }, [setStakeError, setErrorMsg]); const { text, checkDisabled, onClick } = buttonProps[isAllowance ? ActionType.allowance : action]; return ( diff --git a/web/src/pages/Resolver/NavigationButtons/SubmitDisputeButton.tsx b/web/src/pages/Resolver/NavigationButtons/SubmitDisputeButton.tsx index 7938f045b..5db0a357f 100644 --- a/web/src/pages/Resolver/NavigationButtons/SubmitDisputeButton.tsx +++ b/web/src/pages/Resolver/NavigationButtons/SubmitDisputeButton.tsx @@ -14,13 +14,13 @@ import { useSimulateDisputeResolverCreateDisputeForTemplate, } from "hooks/contracts/generated"; import { isUndefined } from "utils/index"; +import { parseWagmiError } from "utils/parseWagmiError"; import { prepareArbitratorExtradata } from "utils/prepareArbitratorExtradata"; import { wrapWithToast } from "utils/wrapWithToast"; import { EnsureChain } from "components/EnsureChain"; -import Popup, { PopupType } from "components/Popup"; - import { ErrorButtonMessage } from "components/ErrorButtonMessage"; +import Popup, { PopupType } from "components/Popup"; import ClosedCircleIcon from "components/StyledIcons/ClosedCircleIcon"; const StyledButton = styled(Button)``; @@ -66,7 +66,7 @@ const SubmitDisputeButton: React.FC = () => { const errorMsg = useMemo(() => { if (insufficientBalance) return "Insufficient balance"; else if (error) { - return error?.shortMessage ?? error.message; + return parseWagmiError(error); } return null; }, [error, insufficientBalance]); diff --git a/web/src/utils/parseWagmiError.ts b/web/src/utils/parseWagmiError.ts new file mode 100644 index 000000000..ffd1ef597 --- /dev/null +++ b/web/src/utils/parseWagmiError.ts @@ -0,0 +1,17 @@ +import { type SimulateContractErrorType } from "@wagmi/core"; + +type ExtendedWagmiError = SimulateContractErrorType & { shortMessage?: string; metaMessages?: string[] }; + +/** + * @param error + * @description Tries to extract the human readable error message, otherwise reverts to error.message + * @returns Human readable error if possible + */ +export const parseWagmiError = (error: SimulateContractErrorType) => { + const extError = error as ExtendedWagmiError; + + const metaMessage = extError?.metaMessages?.[0]; + const shortMessage = extError?.shortMessage; + + return metaMessage ?? shortMessage ?? error.message; +}; diff --git a/web/src/utils/transformSearch.ts b/web/src/utils/transformSearch.ts new file mode 100644 index 000000000..0d1799a6c --- /dev/null +++ b/web/src/utils/transformSearch.ts @@ -0,0 +1,16 @@ +/** + * + * @param searchString + * @returns A search string to better search with fullTextSearch + */ +export const transformSearch = (searchString?: string) => { + if (!searchString) return null; + const words = searchString + .split(/\s+/) + .map((word) => word.trim()) + .filter(Boolean); + + const transformedWords = words.map((word) => `${word} | ${word}:*`); + + return transformedWords.join(" | "); +}; diff --git a/web/src/utils/wrapWithToast.ts b/web/src/utils/wrapWithToast.ts index 6480e6a58..5d078e688 100644 --- a/web/src/utils/wrapWithToast.ts +++ b/web/src/utils/wrapWithToast.ts @@ -1,6 +1,8 @@ import { toast, ToastPosition, Theme } from "react-toastify"; import { PublicClient, TransactionReceipt } from "viem"; +import { parseWagmiError } from "./parseWagmiError"; + export const OPTIONS = { position: "top-center" as ToastPosition, autoClose: 5000, @@ -39,11 +41,11 @@ export async function wrapWithToast( }) ) .catch((error) => { - toast.error(error.shortMessage ?? error.message, OPTIONS); + toast.error(parseWagmiError(error), OPTIONS); return { status: false }; }); } export async function catchShortMessage(promise: Promise) { - return await promise.catch((error) => toast.error(error.shortMessage ?? error.message, OPTIONS)); + return await promise.catch((error) => toast.error(parseWagmiError(error), OPTIONS)); }