diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 098c328..479ff42 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -163,7 +163,9 @@ type Request @entity { "True if a dispute was raised." disputed: Boolean! "ID of the dispute, if any." - disputeID: BigInt! + disputeID: BigInt + "External ID of the dispute, this is always there since it's just requestID. Please use disputed field to check if the dispute was created." + externalDisputeID: BigInt! "Time when the request was made. Used to track when the challenge period ends." submissionTime: BigInt! "True if the request was executed and/or any raised disputes were resolved." @@ -172,6 +174,8 @@ type Request @entity { requester: User! "The party that challenged the request" challenger: User + "Time when the request was challenged." + challengeTime: BigInt "The arbitrator trusted to solve disputes for this request." arbitrator: Bytes! "The extra data for the trusted arbitrator of this request." diff --git a/subgraph/src/Curate.ts b/subgraph/src/Curate.ts index ad2835b..fdfeccf 100644 --- a/subgraph/src/Curate.ts +++ b/subgraph/src/Curate.ts @@ -283,6 +283,7 @@ export function handleRequestChallenged(event: DisputeRequest): void { request.disputed = true; request.challenger = ensureUser(event.transaction.from.toHexString()).id; + request.challengeTime = event.block.timestamp; request.disputeID = event.params._arbitrableDisputeID; request.save(); diff --git a/subgraph/src/entities/Request.ts b/subgraph/src/entities/Request.ts index 3de0a7e..9d7d921 100644 --- a/subgraph/src/entities/Request.ts +++ b/subgraph/src/entities/Request.ts @@ -1,5 +1,5 @@ import { log } from "@graphprotocol/graph-ts"; -import { Item, Registry, Request } from "../../generated/schema"; +import { Item, Request } from "../../generated/schema"; import { Curate, RequestSubmitted } from "../../generated/templates/Curate/Curate"; import { NONE, ONE, ZERO } from "../utils"; import { ensureCounter } from "./Counters"; @@ -28,10 +28,10 @@ export function createRequestFromEvent(event: RequestSubmitted): void { request.resolutionTime = ZERO; request.disputeOutcome = NONE; request.resolved = false; - request.disputeID = ZERO; request.submissionTime = event.block.timestamp; request.requestType = item.status; request.creationTx = event.transaction.hash; + request.externalDisputeID = event.params._requestID; let counter = ensureCounter(); let deposit = item.status.includes("registrationRequested") diff --git a/web/.env.devnet.public b/web/.env.devnet.public index ca58f58..78aa205 100644 --- a/web/.env.devnet.public +++ b/web/.env.devnet.public @@ -1,6 +1,6 @@ # Do not enter sensitive information here. export REACT_APP_DEPLOYMENT=devnet -export REACT_APP_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/71663/curate-test/version/latest +export REACT_APP_ARBSEPOLIA_SUBGRAPH=https://api.studio.thegraph.com/query/61738/curate-v2-devnet/version/latest export REACT_APP_CORE_SUBGRAPH=https://api.studio.thegraph.com/query/61738/kleros-v2-core-devnet/version/latest export REACT_APP_STATUS_URL=https://curate-v2-devnet.betteruptime.com/badge export REACT_APP_GENESIS_BLOCK_ARBSEPOLIA=24725439 \ No newline at end of file diff --git a/web/package.json b/web/package.json index 7f23d32..4a7edab 100644 --- a/web/package.json +++ b/web/package.json @@ -73,7 +73,7 @@ }, "dependencies": { "@filebase/client": "^0.0.5", - "@kleros/ui-components-library": "^2.12.0", + "@kleros/ui-components-library": "^2.13.1", "@middy/core": "^5.3.5", "@middy/http-json-body-parser": "^5.3.5", "@sentry/react": "^7.93.0", diff --git a/web/src/assets/svgs/icons/close-circle.svg b/web/src/assets/svgs/icons/close-circle.svg index 1d6ae84..ec2f59e 100644 --- a/web/src/assets/svgs/icons/close-circle.svg +++ b/web/src/assets/svgs/icons/close-circle.svg @@ -1,3 +1,3 @@ - + diff --git a/web/src/components/HistoryDisplay/Party/Header.tsx b/web/src/components/HistoryDisplay/Party/Header.tsx new file mode 100644 index 0000000..0acb427 --- /dev/null +++ b/web/src/components/HistoryDisplay/Party/Header.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import styled from "styled-components"; + +const StyledHeader = styled.h1` + margin: 0; +`; + +interface IHeader { + text: string; +} + +const Header: React.FC = ({ text }) => { + return {text}; +}; + +export default Header; diff --git a/web/src/components/HistoryDisplay/Party/JustificationDetails.tsx b/web/src/components/HistoryDisplay/Party/JustificationDetails.tsx new file mode 100644 index 0000000..17012be --- /dev/null +++ b/web/src/components/HistoryDisplay/Party/JustificationDetails.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import ReactMarkdown from "react-markdown"; +import styled from "styled-components"; +import { getIpfsUrl } from "utils/getIpfsUrl"; +import AttachmentIcon from "svgs/icons/attachment.svg"; +import { customScrollbar } from "styles/customScrollbar"; + +const Container = styled.div` + width: 100%; + display: flex; + flex-direction: column; +`; + +const JustificationTitle = styled.h3` + margin: 0px; + font-weight: 600; +`; + +const DescriptionContainer = styled.div` + max-height: 400px; + width: 100%; + overflow-y: scroll; + ${customScrollbar} +`; + +const StyledA = styled.a` + display: flex; + gap: 6px; + > svg { + width: 16px; + fill: ${({ theme }) => theme.primaryBlue}; + } +`; + +export type Justification = { + name: string; + description: string; + fileURI?: string; +}; + +const JustificationDetails: React.FC<{ justification: Justification }> = ({ justification }) => { + return ( + + {justification.name} + + {justification.description} + + {justification?.fileURI && ( + + + View attached file + + )} + + ); +}; + +export default JustificationDetails; diff --git a/web/src/components/HistoryDisplay/Party/JustificationModal.tsx b/web/src/components/HistoryDisplay/Party/JustificationModal.tsx new file mode 100644 index 0000000..0cddcd6 --- /dev/null +++ b/web/src/components/HistoryDisplay/Party/JustificationModal.tsx @@ -0,0 +1,127 @@ +import React, { useEffect, useState } from "react"; +import styled from "styled-components"; +import { Button } from "@kleros/ui-components-library"; + +import Modal from "components/Modal"; +import ActionButton from "components/ActionButton"; +import { SkeletonJustificationCard } from "components/StyledSkeleton"; +import { mapFromSubgraphStatus } from "components/RegistryCard/StatusBanner"; + +import { EvidencesQuery, RequestDetailsFragment } from "src/graphql/graphql"; +import { isUndefined } from "src/utils"; +import { getIpfsUrl } from "utils/getIpfsUrl"; +import fetchJsonIpfs from "utils/fetchJsonIpfs"; +import { useEvidences } from "queries/useEvidences"; + +import Header from "./Header"; +import JustificationDetails, { Justification } from "./JustificationDetails"; + +const StyledModal = styled(Modal)` + gap: 30px; +`; + +const ButtonsContainer = styled.div` + width: 100%; + display: flex; + justify-content: space-between; + flex-wrap: wrap; + margin-top: 38px; + row-gap: 8px; +`; + +const StyledLabel = styled.label` + width: 100%; +`; + +const JustificationText = styled.h3` + width: 100%; + margin: 0px; + margin-bottom: 4px; + text-align: center; +`; + +interface IJustificationModal { + request: RequestDetailsFragment; + isRemoval: boolean; + toggleModal: () => void; +} + +const JustificationModal: React.FC = ({ request, toggleModal, isRemoval }) => { + const { data: evidenceData, isLoading: isLoadingEvidences } = useEvidences(request.externalDisputeID); + const [justification, setJustification] = useState(); + const [isLoadingJustification, setIsLoadingJustification] = useState(false); + + useEffect(() => { + if (isUndefined(evidenceData)) return; + setIsLoadingJustification(true); + + const uri = getEvidenceUriForRequest(request, evidenceData.evidences, isRemoval); + + if (!uri) { + setIsLoadingJustification(false); + return; + } + + fetchJsonIpfs(getIpfsUrl(uri)) + .then((res) => { + setJustification(res as Justification); + }) + .finally(() => setIsLoadingJustification(false)); + }, [evidenceData, isRemoval, request]); + + return ( + +
+ Justification + {isLoadingEvidences || isLoadingJustification ? ( + + ) : justification ? ( + + ) : ( + No Justification provided + )} + + + {!request.resolved && ( + + )} + +
+ ); +}; + +/** + * @description returns the correct evidence relating to the request + * @need this is needed since the removal request might not have the evidence, same for challenge request. + * to get the correct evidence for the request, we match the timestamp of the request and evidence, + * if both are same , it means the evidence was created in the same block as that request and belongs to the request + * @returns the evidence uri for the request if it exists, otherwise null + */ +const getEvidenceUriForRequest = ( + request: RequestDetailsFragment, + evidences: EvidencesQuery["evidences"], + isRemoval: boolean +) => { + if (isRemoval) { + if (evidences.length > 0 && evidences[0].timestamp === request.submissionTime) { + return evidences[0].evidence; + } else { + return null; + } + } + + // in case of challenge either the first or the second one can be the challenge evidence, + // in case of registration challenge, the 1st one is the challenge evidence, + // or if the removal request did not have any justification, the 1st one could be the challenge justification + if (evidences.length > 0 && evidences[0].timestamp === request.challengeTime) return evidences[0].evidence; + if (evidences.length > 1 && evidences[1].timestamp === request.challengeTime) return evidences[1].evidence; + + return null; +}; + +export default JustificationModal; diff --git a/web/src/components/HistoryDisplay/Party/index.tsx b/web/src/components/HistoryDisplay/Party/index.tsx new file mode 100644 index 0000000..7ac418c --- /dev/null +++ b/web/src/components/HistoryDisplay/Party/index.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import styled from "styled-components"; +import AliasDisplay from "components/RegistryInfo/AliasDisplay"; +import { RequestDetailsFragment } from "src/graphql/graphql"; +import DocIcon from "svgs/icons/doc.svg"; +import { useToggle } from "react-use"; +import JustificationModal from "./JustificationModal"; + +const Container = styled.div` + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: wrap; + gap: 8px; +`; + +const StyledLabel = styled.label` + display: flex; + align-items: center; + gap: 4px; + color: ${({ theme }) => theme.primaryBlue}; + cursor: pointer; +`; + +const StyledDoc = styled(DocIcon)` + width: 16px; + height: 16px; + fill: ${({ theme }) => theme.primaryBlue}; +`; + +interface IParty { + request: RequestDetailsFragment; + isRemoval?: boolean; +} + +const Party: React.FC = ({ request, isRemoval = false }) => { + const [isOpen, toggleModal] = useToggle(false); + const aliasAddress = isRemoval ? request.requester.id : request?.challenger?.id; + + return ( + + + + + + Justification + + {isOpen && } + + ); +}; + +export default Party; diff --git a/web/src/components/HistoryDisplay/index.tsx b/web/src/components/HistoryDisplay/index.tsx index 6ed4c20..ca5ed6a 100644 --- a/web/src/components/HistoryDisplay/index.tsx +++ b/web/src/components/HistoryDisplay/index.tsx @@ -1,14 +1,15 @@ import { Card, CustomTimeline, _TimelineItem1 } from "@kleros/ui-components-library"; import React from "react"; -import styled, { Theme, useTheme } from "styled-components"; +import styled, { css, Theme, useTheme } from "styled-components"; import Header from "./Header"; import { useItemRequests } from "queries/useRequestsQuery"; import { RequestDetailsFragment, Ruling, Status } from "src/graphql/graphql"; import { formatDate } from "utils/date"; -import { shortenAddress } from "utils/shortenAddress"; import CheckIcon from "assets/svgs/icons/check-circle-outline.svg"; import ClosedIcon from "assets/svgs/icons/close-circle.svg"; import { HistorySkeletonCard } from "../StyledSkeleton"; +import Party from "./Party"; +import { landscapeStyle } from "styles/landscapeStyle"; const Container = styled(Card)` display: flex; @@ -23,6 +24,14 @@ const Container = styled(Card)` const StyledTimeline = styled(CustomTimeline)` width: 100%; margin-bottom: 30px; + .party-wrapper { + max-height: none; + ${landscapeStyle( + () => css` + max-height: 32px; + ` + )} + } `; interface IHistory { @@ -51,7 +60,7 @@ const History: React.FC = ({ itemId, isItem }) => { const constructItemsFromRequest = ( request: RequestDetailsFragment, theme: Theme, - isItem?: Boolean + isItem?: boolean ): _TimelineItem1[] => { const historyItems: _TimelineItem1[] = []; @@ -66,10 +75,10 @@ const constructItemsFromRequest = ( if (request.disputed && request.challenger) { historyItems.push({ - title: `${isItem ? "Submission" : "Registration"} Challenged`, + title: `${isItem ? "Submission" : "Registration"} Challenged - Case ${request.disputeID}`, variant: theme.secondaryPurple, - party: `- Case ${request.disputeID} by ${shortenAddress(request.challenger.id)}`, - subtitle: formatDate(request.submissionTime), + party: , + subtitle: formatDate(request.challengeTime), rightSided: true, }); } @@ -92,14 +101,14 @@ const constructItemsFromRequest = ( variant: theme.primaryBlue, subtitle: formatDate(request.submissionTime), rightSided: true, - party: `- By ${shortenAddress(request.requester.id)}`, + party: , }); if (request.disputed && request.challenger) { historyItems.push({ - title: "Removal Challenged", + title: `Removal Challenged - Case ${request.disputeID}`, variant: theme.secondaryPurple, - party: `- Case #${request.disputeID} by ${shortenAddress(request.challenger.id)}`, + party: , subtitle: formatDate(request.submissionTime), rightSided: true, }); diff --git a/web/src/components/InformationCards/ItemInformationCard/index.tsx b/web/src/components/InformationCards/ItemInformationCard/index.tsx index b2c4961..0079a16 100644 --- a/web/src/components/InformationCards/ItemInformationCard/index.tsx +++ b/web/src/components/InformationCards/ItemInformationCard/index.tsx @@ -77,6 +77,13 @@ const BottomInfo = styled.div` justify-content: space-between; `; +const AliasContainer = styled.div` + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +`; + type ItemDetails = NonNullable; interface IItemInformationCard extends ItemDetails { className?: string; @@ -96,38 +103,41 @@ const ItemInformationCard: React.FC = ({ latestRequestSubmissionTime, }) => { return ( - <> - - - {props ? : } - - - Item Id - - - - - - - {registerer?.id ? ( + + + {props ? : } + + + Item Id + + + + + + + {registerer?.id ? ( + +
Submitted by: - + + + - ) : ( - - )} - - - - - + + ) : ( + + )} + + + + ); }; export default ItemInformationCard; diff --git a/web/src/components/InformationCards/RegistryInformationCard/TopInfo.tsx b/web/src/components/InformationCards/RegistryInformationCard/TopInfo.tsx index 2d69e54..b089f6d 100644 --- a/web/src/components/InformationCards/RegistryInformationCard/TopInfo.tsx +++ b/web/src/components/InformationCards/RegistryInformationCard/TopInfo.tsx @@ -10,7 +10,7 @@ import { responsiveSize } from "src/styles/responsiveSize"; import { isUndefined } from "src/utils"; import { DEFAULT_LIST_LOGO } from "src/consts"; import { getIpfsUrl } from "utils/getIpfsUrl"; -import EtherscanIcon from "svgs/icons/etherscan.svg"; +import { shortenAddress } from "utils/shortenAddress"; const TopInfoContainer = styled.div` display: flex; @@ -54,12 +54,6 @@ const TopRightInfo = styled.div` )} `; -const StyledEtherscanIcon = styled(EtherscanIcon)` - display: flex; - height: 16px; - width: 16px; -`; - const StyledLogo = styled.img<{ isListView: boolean }>` width: ${({ isListView }) => (isListView ? "40px" : "125px")}; height: ${({ isListView }) => (isListView ? "40px" : "125px")}; @@ -76,8 +70,12 @@ const StyledP = styled.p` margin: 0; `; -const StyledLabel = styled.label` +const StyledA = styled.a` color: ${({ theme }) => theme.primaryBlue}; + text-decoration: none; + :hover { + text-decoration: underline; + } `; const SkeletonLogo = styled(Skeleton)` @@ -136,18 +134,15 @@ const TopInfo: React.FC = ({ {isUndefined(description) ? : {description}} - - Registry Address - - {id ? ( - + - - - ) : null} + {shortenAddress(id ?? "")} + + diff --git a/web/src/components/InformationCards/RegistryInformationCard/index.tsx b/web/src/components/InformationCards/RegistryInformationCard/index.tsx index 54ff3fe..a9f0553 100644 --- a/web/src/components/InformationCards/RegistryInformationCard/index.tsx +++ b/web/src/components/InformationCards/RegistryInformationCard/index.tsx @@ -32,6 +32,13 @@ const BottomInfo = styled.div` justify-content: space-between; `; +const AliasContainer = styled.div` + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +`; + interface IInformationCard extends Pick< RegistryDetails, @@ -79,9 +86,12 @@ const RegistryInformationCard: React.FC = ({ /> - - - + + Submitted by: + + + + ( @@ -53,6 +75,14 @@ export const SkeletonRegistryCard = () => ( ); +export const SkeletonJustificationCard = () => ( + + + + + +); + export const SkeletonRegistryListItem = () => ; export const SkeletonEvidenceCard = () => ; diff --git a/web/src/consts/index.ts b/web/src/consts/index.ts index 9f077c2..a882cf9 100644 --- a/web/src/consts/index.ts +++ b/web/src/consts/index.ts @@ -20,7 +20,7 @@ export const ETH_SIGNATURE_REGEX = /^0x[a-fA-F0-9]{130}$/; export const DEFAULT_LIST_LOGO = "ipfs://QmWfxEmfEWwM6LDgER2Qp2XZpK1MbDtNp7uGqCS4UPNtgJ/symbol-CURATE.png"; -export const COURT_SITE = process.env.COURT_SITE || "https://v2.kleros.builders"; +export const COURT_SITE = process.env.COURT_SITE ?? "https://v2.kleros.builders/#"; export const SUPPORTED_FILE_TYPES = ["application/pdf", "text/rtf", "text/markdown", "text/plain"]; diff --git a/web/src/context/GraphqlBatcher.tsx b/web/src/context/GraphqlBatcher.tsx index 5ee4c7f..31ecc26 100644 --- a/web/src/context/GraphqlBatcher.tsx +++ b/web/src/context/GraphqlBatcher.tsx @@ -15,13 +15,14 @@ interface IQuery { document: TypedDocumentNode; variables: Record; chainId?: number; + isCore?: boolean; } const Context = createContext(undefined); const fetcher = async (queries: IQuery[]) => { - const promises = queries.map(async ({ id, document, variables, chainId }) => { - const url = getGraphqlUrl(false, chainId ?? arbitrumSepolia.id); + const promises = queries.map(async ({ id, document, variables, chainId, isCore = false }) => { + const url = getGraphqlUrl(isCore, chainId ?? arbitrumSepolia.id); try { return request(url, document, variables).then((result) => ({ id, result })); } catch (error) { diff --git a/web/src/hooks/queries/useEvidences.ts b/web/src/hooks/queries/useEvidences.ts new file mode 100644 index 0000000..89e01ba --- /dev/null +++ b/web/src/hooks/queries/useEvidences.ts @@ -0,0 +1,32 @@ +import { useQuery } from "@tanstack/react-query"; + +import { useGraphqlBatcher } from "context/GraphqlBatcher"; + +import { graphql } from "src/graphql"; +import { type EvidencesQuery } from "src/graphql/graphql"; + +const evidencesQuery = graphql(` + query Evidences($evidenceGroupID: String) { + evidences(where: { evidenceGroup: $evidenceGroupID }, orderBy: timestamp, orderDirection: asc, first: 2) { + evidence + timestamp + } + } +`); + +export const useEvidences = (evidenceGroup?: string) => { + const isEnabled = evidenceGroup !== undefined; + const { graphqlBatcher } = useGraphqlBatcher(); + + return useQuery({ + queryKey: [`evidencesQuery${evidenceGroup}`], + enabled: isEnabled, + queryFn: async () => + await graphqlBatcher.fetch({ + id: crypto.randomUUID(), + document: evidencesQuery, + variables: { evidenceGroupID: evidenceGroup?.toString() }, + isCore: true, + }), + }); +}; diff --git a/web/src/hooks/queries/useRequestsQuery.ts b/web/src/hooks/queries/useRequestsQuery.ts index 453f2b0..a064579 100644 --- a/web/src/hooks/queries/useRequestsQuery.ts +++ b/web/src/hooks/queries/useRequestsQuery.ts @@ -8,6 +8,7 @@ export const requestDetailsFragment = graphql(` id disputed disputeID + externalDisputeID submissionTime resolved requester { @@ -16,6 +17,11 @@ export const requestDetailsFragment = graphql(` challenger { id } + item { + itemID + status + registryAddress + } arbitrator arbitratorExtraData deposit @@ -25,6 +31,7 @@ export const requestDetailsFragment = graphql(` finalRuling creationTx resolutionTx + challengeTime } `); diff --git a/web/src/styles/customScrollbar.ts b/web/src/styles/customScrollbar.ts new file mode 100644 index 0000000..eef4e58 --- /dev/null +++ b/web/src/styles/customScrollbar.ts @@ -0,0 +1,26 @@ +import { css } from "styled-components"; + +export const customScrollbar = css` + ::-webkit-scrollbar { + width: 6px; + height: 6px; + } + ::-webkit-scrollbar-track { + background: transparent; + } + ::-webkit-scrollbar-thumb { + background-color: ${({ theme }) => theme.violetPurple}; + border-radius: 10px; + transition: opacity 0.15s, background-color 0.15s, border-color 0.15s, width 0.15s; + } + ::-webkit-scrollbar-thumb:hover { + background-color: ${({ theme }) => theme.secondaryPurple}; + } + ::-webkit-scrollbar-thumb:active { + background-color: ${({ theme }) => theme.lavenderPurple}; + } + + // firefox + scrollbar-width: thin; + scrollbar-color: ${({ theme }) => theme.violetPurple} transparent; +`; diff --git a/web/src/utils/fetchJsonIpfs.ts b/web/src/utils/fetchJsonIpfs.ts new file mode 100644 index 0000000..c909b7d --- /dev/null +++ b/web/src/utils/fetchJsonIpfs.ts @@ -0,0 +1,25 @@ +/** + * + * @param uri complete uri to fetch the data from + * @returns parsed json data + * @requires the function is specifically for json content + */ +const fetchJsonIpfs = async (uri: string) => { + try { + const response = await fetch(uri, { method: "GET", headers: { "Content-Type": "application/json" } }); + + if (!response.ok) { + throw Error(`HTTP ERROR: Failed to fetch from the uri : ${uri}`); + } + + const result = await response.json(); + + return result; + } catch (err: any) { + console.log("fetchJsonIpfs:", { err }); + + return null; + } +}; + +export default fetchJsonIpfs; diff --git a/yarn.lock b/yarn.lock index baab5eb..f22d09d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4791,7 +4791,7 @@ __metadata: "@graphql-codegen/cli": "npm:^4.0.1" "@graphql-codegen/client-preset": "npm:^4.2.0" "@kleros/kleros-v2-contracts": "npm:^0.3.2" - "@kleros/ui-components-library": "npm:^2.12.0" + "@kleros/ui-components-library": "npm:^2.13.1" "@middy/core": "npm:^5.3.5" "@middy/http-json-body-parser": "npm:^5.3.5" "@netlify/functions": "npm:^1.6.0" @@ -4863,9 +4863,9 @@ __metadata: languageName: node linkType: hard -"@kleros/ui-components-library@npm:^2.12.0": - version: 2.12.0 - resolution: "@kleros/ui-components-library@npm:2.12.0" +"@kleros/ui-components-library@npm:^2.13.1": + version: 2.13.1 + resolution: "@kleros/ui-components-library@npm:2.13.1" dependencies: "@datepicker-react/hooks": "npm:^2.8.4" "@swc/helpers": "npm:^0.3.2" @@ -4882,7 +4882,7 @@ __metadata: react-dom: ^18.0.0 react-is: ^18.0.0 styled-components: ^5.3.3 - checksum: 9c53e9ce217a5dd6c0f5dc1b19c8c14fb6f4843f5cf7a0eb1b1ae9561ead583c1bcf5bc2687e7f678007363768be3de95f5d573298b00b965d13a563c4b59495 + checksum: db710e97b09a291ad5b6ff7a7a046cd3945e0403fda66ce6b33e501609ad2b712f924e84df2cba273d4ee936d274a6b7f210ff269d27d504bc81bcb712bc9da1 languageName: node linkType: hard