From 77b297a9ef757fe401adebf14752de1d494e6320 Mon Sep 17 00:00:00 2001 From: Jan Jaroszczak Date: Tue, 26 Mar 2024 17:04:37 +0100 Subject: [PATCH] Vote Context --- .../src/components/atoms/TextArea.tsx | 29 ++- .../frontend/src/components/atoms/types.ts | 1 + .../components/molecules/Field/TextArea.tsx | 10 +- .../components/molecules/VoteActionForm.tsx | 205 +++++++++--------- .../VoteContext/VoteContextCheckResult.tsx | 106 +++++++++ .../VoteContext/VoteContextModal.tsx | 75 +++++++ .../VoteContextStoringInformation.tsx | 154 +++++++++++++ .../VoteContext/VoteContextTerms.tsx | 60 +++++ .../organisms/VoteContext/VoteContextText.tsx | 77 +++++++ .../VoteContext/VoteContextWrapper.tsx | 64 ++++++ .../components/organisms/VoteContext/index.ts | 6 + .../src/components/organisms/index.ts | 1 + .../src/consts/governanceAction/fields.ts | 30 +++ govtool/frontend/src/context/modal.tsx | 7 +- govtool/frontend/src/hooks/forms/index.ts | 1 + .../forms/useCreateGovernanceActionForm.ts | 2 +- .../src/hooks/forms/useVoteActionForm.tsx | 47 +--- .../src/hooks/forms/useVoteContextForm.tsx | 123 +++++++++++ govtool/frontend/src/i18n/locales/en.ts | 11 +- 19 files changed, 863 insertions(+), 146 deletions(-) create mode 100644 govtool/frontend/src/components/organisms/VoteContext/VoteContextCheckResult.tsx create mode 100644 govtool/frontend/src/components/organisms/VoteContext/VoteContextModal.tsx create mode 100644 govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx create mode 100644 govtool/frontend/src/components/organisms/VoteContext/VoteContextTerms.tsx create mode 100644 govtool/frontend/src/components/organisms/VoteContext/VoteContextText.tsx create mode 100644 govtool/frontend/src/components/organisms/VoteContext/VoteContextWrapper.tsx create mode 100644 govtool/frontend/src/components/organisms/VoteContext/index.ts create mode 100644 govtool/frontend/src/hooks/forms/useVoteContextForm.tsx diff --git a/govtool/frontend/src/components/atoms/TextArea.tsx b/govtool/frontend/src/components/atoms/TextArea.tsx index 59ee06bfa..823a8acdb 100644 --- a/govtool/frontend/src/components/atoms/TextArea.tsx +++ b/govtool/frontend/src/components/atoms/TextArea.tsx @@ -8,11 +8,9 @@ import { TextAreaProps } from "./types"; const TextAreaBase = styled(TextareaAutosize)( () => ` font-family: "Poppins"; - font-size: 16px; font-weight: 400; ::placeholder { font-family: "Poppins"; - font-size: 16px; font-weight: 400; color: #a6a6a6; } @@ -20,7 +18,17 @@ const TextAreaBase = styled(TextareaAutosize)( ); export const TextArea = forwardRef( - ({ errorMessage, maxLength = 500, onBlur, onFocus, ...props }, ref) => { + ( + { + errorMessage, + maxLength = 500, + onBlur, + onFocus, + isModifiedLayout, + ...props + }, + ref, + ) => { const { isMobile } = useScreenDimension(); const textAraeRef = useRef(null); @@ -51,19 +59,32 @@ export const TextArea = forwardRef( [handleBlur, handleFocus], ); + const getTexAreaHeight = () => { + if (isModifiedLayout && isMobile) return "312px"; + if (isModifiedLayout) return "208px"; + if (isMobile) return "104px"; + return "128px"; + }; + return ( ); diff --git a/govtool/frontend/src/components/atoms/types.ts b/govtool/frontend/src/components/atoms/types.ts index ff2e603b8..ec5b668c4 100644 --- a/govtool/frontend/src/components/atoms/types.ts +++ b/govtool/frontend/src/components/atoms/types.ts @@ -66,6 +66,7 @@ export type FormHelpfulTextProps = { export type TextAreaProps = TextareaAutosizeProps & { errorMessage?: string; + isModifiedLayout?: boolean; }; export type InfoTextProps = { diff --git a/govtool/frontend/src/components/molecules/Field/TextArea.tsx b/govtool/frontend/src/components/molecules/Field/TextArea.tsx index 20688cbe8..9f544aba5 100644 --- a/govtool/frontend/src/components/molecules/Field/TextArea.tsx +++ b/govtool/frontend/src/components/molecules/Field/TextArea.tsx @@ -55,6 +55,14 @@ export const TextArea = forwardRef( } as unknown as HTMLTextAreaElement), [handleBlur, handleFocus], ); + + const getCounterBottomSxValue = () => { + if (props.isModifiedLayout && errorMessage) return 30; + if (props.isModifiedLayout) return 10; + if (errorMessage) return 52.5; + return 35; + }; + return ( ( { - const [isContext, setIsContext] = useState(false); + const [voteContextText, setVoteContextText] = useState(); + const [voteContextHash, setVoteContextHash] = useState(); + const [voteContextUrl, setVoteContextUrl] = useState(); + const [showWholeVoteContext, setShowWholeVoteContext] = + useState(false); + const { state } = useLocation(); const { isMobile } = useScreenDimension(); const { openModal } = useModal(); @@ -54,16 +49,19 @@ export const VoteActionForm = ({ const { areFormErrors, - clearErrors, confirmVote, - control, - errors, isDirty, isVoteLoading, registerInput, setValue, vote, - } = useVoteActionForm(); + } = useVoteActionForm(voteContextHash, voteContextUrl); + + const setVoteContextData = (url: string, hash: string, text: string) => { + setVoteContextUrl(url); + setVoteContextHash(hash); + setVoteContextText(text); + }; useEffect(() => { if (state && state.vote) { @@ -75,14 +73,6 @@ export const VoteActionForm = ({ } }, [state, voteFromEP, setValue]); - useEffect(() => { - clearErrors(); - }, [isContext]); - - const handleContext = useCallback(() => { - setIsContext((prev) => !prev); - }, []); - const renderCancelButton = useMemo( () => ( + ) : ( + )} void; + closeModal: () => void; + setStep: Dispatch>; + errorMessage?: string; +}; + +export const VoteContextCheckResult = ({ + submitVoteContext, + closeModal, + setStep, + errorMessage, +}: VoteContextCheckResultProps) => { + const { t } = useTranslation(); + const { isMobile } = useScreenDimension(); + + const { watch } = useVoteContextForm(); + const isContinueDisabled = !watch("voteContextText"); + + return ( + + Status icon + + {errorMessage ? "Data validation failed" : "Success"} + + + {errorMessage ?? "Data check has been successful"} + + {!errorMessage ? ( + + ) : ( + + + + + )} + + ); +}; diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextModal.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextModal.tsx new file mode 100644 index 000000000..9694d7d3d --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextModal.tsx @@ -0,0 +1,75 @@ +import { useState } from "react"; +import { useForm, FormProvider } from "react-hook-form"; + +import { ModalWrapper } from "@atoms"; +import { useModal } from "@context"; +import { + VoteContextStoringInformation, + VoteContextCheckResult, + VoteContextTerms, + VoteContextText, +} from "@organisms"; +import { VoteContextFormValues } from "@hooks"; + +type VoteContextModalState = { + onSubmit: (url: string, hash: string | null, voteContextText: string) => void; +}; + +export const VoteContextModal = () => { + const [step, setStep] = useState(1); + const [savedHash, setSavedHash] = useState(""); + const [errorMessage, setErrorMessage] = useState( + undefined, + ); + + const { state, closeModal } = useModal(); + + const methods = useForm({ mode: "onChange" }); + const { getValues } = methods; + + const submitVoteContext = () => { + if (state && savedHash) { + state.onSubmit( + getValues("storingURL"), + savedHash, + getValues("voteContextText"), + ); + } + closeModal(); + }; + + return ( + + + {step === 1 && ( + + )} + {step === 2 && ( + + )} + {step === 3 && ( + + )} + {step === 4 && ( + + )} + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx new file mode 100644 index 000000000..96d91b3a8 --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextStoringInformation.tsx @@ -0,0 +1,154 @@ +import { Dispatch, SetStateAction, useEffect } from "react"; +import { Box } from "@mui/material"; +import OpenInNewIcon from "@mui/icons-material/OpenInNew"; + +import { Button, Spacer, Typography } from "@atoms"; +import { ICONS } from "@consts"; +import { useTranslation, useScreenDimension, useVoteContextForm } from "@hooks"; +import { Step } from "@molecules"; +import { ControlledField, VoteContextWrapper } from "@organisms"; +import { URL_REGEX, openInNewTab } from "@utils"; + +type VoteContextStoringInformationProps = { + setStep: Dispatch>; + setSavedHash: Dispatch>; + setErrorMessage: Dispatch>; + onCancel: () => void; +}; + +export const VoteContextStoringInformation = ({ + setStep, + setSavedHash, + setErrorMessage, + onCancel, +}: VoteContextStoringInformationProps) => { + const { t } = useTranslation(); + const { screenWidth } = useScreenDimension(); + + const { + control, + errors, + validateURL, + watch, + generateMetadata, + onClickDownloadJson, + } = useVoteContextForm(setSavedHash, setStep, setErrorMessage); + + // TODO: Change link to correct + const openGuideAboutStoringInformation = () => + openInNewTab("https://sancho.network/"); + + const isContinueDisabled = !watch("storingURL"); + + useEffect(() => { + generateMetadata(); + }, []); + + return ( + + + {t("createGovernanceAction.storingInformationTitle")} + + + + {t("createGovernanceAction.storingInformationDescription")} + + + } + sx={{ + width: "fit-content", + ml: screenWidth < 1024 ? 0 : 1.75, + mt: screenWidth < 1024 ? 1.5 : 0, + }} + variant="outlined" + > + {t("govActions.voteContextJsonldFileName")} + + } + componentsLayoutStyles={{ + alignItems: screenWidth < 1024 ? undefined : "center", + flexDirection: screenWidth < 1024 ? "column" : "row", + }} + label={t("createGovernanceAction.storingInformationStep1Label")} + stepNumber={1} + /> + + + } + onClick={openGuideAboutStoringInformation} + size="extraLarge" + sx={{ width: "fit-content" }} + variant="text" + > + {t("createGovernanceAction.storingInformationStep2Link")} + + } + label={t("createGovernanceAction.storingInformationStep2Label")} + stepNumber={2} + /> + + + } + label={t("createGovernanceAction.storingInformationStep3Label")} + stepNumber={3} + /> + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextTerms.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextTerms.tsx new file mode 100644 index 000000000..22ac471c5 --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextTerms.tsx @@ -0,0 +1,60 @@ +import { Dispatch, SetStateAction } from "react"; +import { Box, Link } from "@mui/material"; + +import { Spacer, Typography } from "@atoms"; +import { useScreenDimension, useTranslation, useVoteContextForm } from "@hooks"; +import { ControlledField, VoteContextWrapper } from "@organisms"; +import { openInNewTab } from "@utils"; + +type StoreDataInfoProps = { + setStep: Dispatch>; + onCancel: () => void; +}; + +export const VoteContextTerms = ({ setStep, onCancel }: StoreDataInfoProps) => { + const { t } = useTranslation(); + const { control, errors, watch } = useVoteContextForm(); + const { isMobile } = useScreenDimension(); + + // TODO: change link when available + const openLink = () => openInNewTab("https://docs.sanchogov.tools"); + + const isContinueDisabled = !watch("terms"); + + return ( + setStep(3)} + isContinueDisabled={isContinueDisabled} + onCancel={onCancel} + > + + {t("createGovernanceAction.storeDataTitle")} + + + {t("createGovernanceAction.storeDataLink")} + + + + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextText.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextText.tsx new file mode 100644 index 000000000..f241de814 --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextText.tsx @@ -0,0 +1,77 @@ +import { Dispatch, SetStateAction } from "react"; + +import { orange } from "@consts"; +import { Typography } from "@atoms"; +import { VoteContextWrapper } from "@organisms"; +import { useTranslation, useVoteContextForm } from "@/hooks"; +import { ControlledField } from ".."; + +type VoteContextTextProps = { + setStep: Dispatch>; + onCancel: () => void; +}; + +export const VoteContextText = ({ + setStep, + onCancel, +}: VoteContextTextProps) => { + const { t } = useTranslation(); + + const { control, errors, watch } = useVoteContextForm(); + const isContinueDisabled = !watch("voteContextText"); + + const fieldProps = { + key: "voteContextText", + layoutStyles: { mb: 3 }, + name: "voteContextText", + placeholder: t("govActions.provideContext"), + rules: { + required: { + value: true, + message: t("createGovernanceAction.fields.validations.required"), + }, + maxLength: { + value: 500, + message: t("createGovernanceAction.fields.validations.maxLength", { + maxLength: 500, + }), + }, + }, + }; + + return ( + setStep(2)} + isContinueDisabled={isContinueDisabled} + onCancel={onCancel} + > + + {t("optional")} + + + {t("govActions.provideContextAboutYourVote")} + + + {/* TODO: Update text when design is finalised */} + Additional information about your vote + + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/VoteContext/VoteContextWrapper.tsx b/govtool/frontend/src/components/organisms/VoteContext/VoteContextWrapper.tsx new file mode 100644 index 000000000..91ceb8ed5 --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/VoteContextWrapper.tsx @@ -0,0 +1,64 @@ +import { FC, PropsWithChildren } from "react"; +import { Box } from "@mui/material"; + +import { useScreenDimension, useTranslation } from "@hooks"; +import { Button } from "@atoms"; + +type VoteContextWrapperProps = { + onContinue: () => void; + isContinueDisabled?: boolean; + onCancel: () => void; +}; + +export const VoteContextWrapper: FC< + PropsWithChildren +> = ({ onContinue, isContinueDisabled, onCancel, children }) => { + const { isMobile } = useScreenDimension(); + const { t } = useTranslation(); + + return ( + <> + + {children} + + + + + + + ); +}; diff --git a/govtool/frontend/src/components/organisms/VoteContext/index.ts b/govtool/frontend/src/components/organisms/VoteContext/index.ts new file mode 100644 index 000000000..b288c8de3 --- /dev/null +++ b/govtool/frontend/src/components/organisms/VoteContext/index.ts @@ -0,0 +1,6 @@ +export * from "./VoteContextModal"; +export * from "./VoteContextStoringInformation"; +export * from "./VoteContextCheckResult"; +export * from "./VoteContextTerms"; +export * from "./VoteContextText"; +export * from "./VoteContextWrapper"; diff --git a/govtool/frontend/src/components/organisms/index.ts b/govtool/frontend/src/components/organisms/index.ts index cccbc8400..f8da1ecb0 100644 --- a/govtool/frontend/src/components/organisms/index.ts +++ b/govtool/frontend/src/components/organisms/index.ts @@ -29,4 +29,5 @@ export * from "./RetireAsSoleVoterBoxContent"; export * from "./Slider"; export * from "./StatusModal"; export * from "./TopNav"; +export * from "./VoteContext"; export * from "./VotingPowerModal"; diff --git a/govtool/frontend/src/consts/governanceAction/fields.ts b/govtool/frontend/src/consts/governanceAction/fields.ts index b0395cab4..b172bf784 100644 --- a/govtool/frontend/src/consts/governanceAction/fields.ts +++ b/govtool/frontend/src/consts/governanceAction/fields.ts @@ -163,3 +163,33 @@ export const GOVERNANCE_ACTION_CONTEXT = { }, }, }; + +export const VOTE_TEST_CONTEXT = { + "@language": "en-us", + CIP100: + "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md#", + CIP108: + "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0108/README.md#", + hashAlgorithm: "CIP100:hashAlgorithm", + body: { + "@id": "CIP108:body", + "@context": { + text: "CIP108:text", + }, + }, + authors: { + "@id": "CIP100:authors", + "@container": "@set" as const, + "@context": { + name: "http://xmlns.com/foaf/0.1/name", + witness: { + "@id": "CIP100:witness", + "@context": { + witnessAlgorithm: "CIP100:witnessAlgorithm", + publicKey: "CIP100:publicKey", + signature: "CIP100:signature", + }, + }, + }, + }, +}; diff --git a/govtool/frontend/src/context/modal.tsx b/govtool/frontend/src/context/modal.tsx index 7dbe36e15..cebfab96b 100644 --- a/govtool/frontend/src/context/modal.tsx +++ b/govtool/frontend/src/context/modal.tsx @@ -5,6 +5,7 @@ import { ChooseWalletModal, ExternalLinkModal, StatusModal, + VoteContextModal, VotingPowerModal, } from "@organisms"; import { basicReducer, callAll, BasicReducer } from "@utils"; @@ -25,7 +26,8 @@ export type ModalType = | "chooseWallet" | "statusModal" | "externalLink" - | "votingPower"; + | "votingPower" + | "voteContext"; const modals: Record = { none: { @@ -43,6 +45,9 @@ const modals: Record = { votingPower: { component: , }, + voteContext: { + component: , + }, }; type Optional = Pick, K> & Omit; diff --git a/govtool/frontend/src/hooks/forms/index.ts b/govtool/frontend/src/hooks/forms/index.ts index a1b5ca4e4..f86aeba6c 100644 --- a/govtool/frontend/src/hooks/forms/index.ts +++ b/govtool/frontend/src/hooks/forms/index.ts @@ -4,3 +4,4 @@ export * from "./useRegisterAsdRepForm"; export * from "./useUpdatedRepMetadataForm"; export * from "./useUrlAndHashFormController"; export * from "./useVoteActionForm"; +export * from "./useVoteContextForm"; diff --git a/govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts b/govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts index ff0d505b6..e8ff4f660 100644 --- a/govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts +++ b/govtool/frontend/src/hooks/forms/useCreateGovernanceActionForm.ts @@ -135,7 +135,7 @@ export const useCreateGovernanceActionForm = ( type: "statusModal", state: { ...storageInformationErrorModals[ - error.message as MetadataHashValidationErrors + error.message as MetadataHashValidationErrors ], onSubmit: backToForm, onCancel: backToDashboard, diff --git a/govtool/frontend/src/hooks/forms/useVoteActionForm.tsx b/govtool/frontend/src/hooks/forms/useVoteActionForm.tsx index 107dc7cfd..50d85c5e6 100644 --- a/govtool/frontend/src/hooks/forms/useVoteActionForm.tsx +++ b/govtool/frontend/src/hooks/forms/useVoteActionForm.tsx @@ -6,54 +6,31 @@ import { useLocation, useNavigate } from "react-router-dom"; import { PATHS } from "@consts"; import { useCardano, useSnackbar } from "@context"; -import { HASH_REGEX, URL_REGEX } from "@utils"; -import { useTranslation } from "@hooks"; -import { UrlAndHashFormValues } from "./useUrlAndHashFormController"; -export interface VoteActionFormValues extends UrlAndHashFormValues { +export interface VoteActionFormValues { vote: string; } export const useVoteActionFormController = () => { - const { t } = useTranslation(); - const validationSchema = useMemo( () => Yup.object().shape({ vote: Yup.string().oneOf(["yes", "no", "abstain"]).required(), - url: Yup.string() - .trim() - .max(64, t("forms.errors.urlTooLong")) - .test( - "url-validation", - t("forms.errors.urlInvalidFormat"), - (value) => !value || URL_REGEX.test(value), - ), - hash: Yup.string() - .trim() - .test( - "hash-length-validation", - t("forms.errors.hashInvalidLength"), - (value) => !value || value.length === 64, - ) - .test( - "hash-format-validation", - t("forms.errors.hashInvalidFormat"), - (value) => !value || HASH_REGEX.test(value), - ), - storeData: Yup.boolean(), }), [], ); return useForm({ - defaultValues: { url: "", hash: "", vote: "" }, + defaultValues: { vote: "" }, mode: "onChange", resolver: yupResolver(validationSchema), }); }; -export const useVoteActionForm = () => { +export const useVoteActionForm = ( + voteContextHash?: string, + voteContextUrl?: string, +) => { const [isLoading, setIsLoading] = useState(false); const { buildSignSubmitConwayCertTx, buildVote, isPendingTransaction } = useCardano(); @@ -67,23 +44,22 @@ export const useVoteActionForm = () => { formState: { errors, isDirty }, setValue, register: registerInput, - clearErrors, } = useVoteActionFormController(); const watch = useWatch({ control, }); - const areFormErrors = !!errors.vote || !!errors.url || !!errors.hash; + const areFormErrors = !!errors.vote; const confirmVote = useCallback( async (values: VoteActionFormValues) => { setIsLoading(true); - const { url, hash, vote } = values; + const { vote } = values; - const urlSubmitValue = url ?? ""; - const hashSubmitValue = hash ?? ""; + const urlSubmitValue = voteContextUrl ?? ""; + const hashSubmitValue = voteContextHash ?? ""; try { const isPendingTx = isPendingTransaction(); @@ -118,14 +94,11 @@ export const useVoteActionForm = () => { ); return { - control, - errors, confirmVote: handleSubmit(confirmVote), setValue, vote: watch.vote, registerInput, isDirty, - clearErrors, areFormErrors, isVoteLoading: isLoading, }; diff --git a/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx b/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx new file mode 100644 index 000000000..27cfccede --- /dev/null +++ b/govtool/frontend/src/hooks/forms/useVoteContextForm.tsx @@ -0,0 +1,123 @@ +import { Dispatch, SetStateAction, useCallback, useState } from "react"; +import { NodeObject } from "jsonld"; +import { useFormContext } from "react-hook-form"; +import { blake2bHex } from "blakejs"; + +import { + CIP_108, + MetadataHashValidationErrors, + VOTE_TEST_CONTEXT, +} from "@consts"; +import { + canonizeJSON, + downloadJson, + generateJsonld, + validateMetadataHash, +} from "@utils"; +import { captureException } from "@sentry/react"; + +export type VoteContextFormValues = { + voteContextText: string; + terms?: boolean; + storingURL: string; +}; + +export const useVoteContextForm = ( + setSavedHash?: Dispatch>, + setStep?: Dispatch>, + setErrorMessage?: Dispatch>, +) => { + const [hash, setHash] = useState(null); + const [json, setJson] = useState(null); + + const { + control, + formState: { errors, isValid }, + getValues, + handleSubmit, + setValue, + watch, + register, + reset, + } = useFormContext(); + + const generateMetadata = useCallback(async () => { + const data = getValues(); + + const acceptedKeys = ["voteContextText"]; + + const filteredData = Object.entries(data) + .filter(([key]) => acceptedKeys.includes(key)) + .map(([key, value]) => [CIP_108 + key, value]); + + const body = { + ...Object.fromEntries(filteredData), + }; + + const jsonld = await generateJsonld(body, VOTE_TEST_CONTEXT); + const canonizedJson = await canonizeJSON(jsonld); + const canonizedJsonHash = blake2bHex(canonizedJson, undefined, 32); + + // That allows to validate metadata hash + setHash(canonizedJsonHash); + setJson(jsonld); + + return jsonld; + }, [getValues]); + + const onClickDownloadJson = useCallback(() => { + if (!json) return; + downloadJson(json, "Vote_Context"); + }, [json]); + + const validateHash = useCallback( + async (storingUrl: string, localHash: string | null) => { + try { + if (!localHash) { + throw new Error(MetadataHashValidationErrors.INVALID_HASH); + } + await validateMetadataHash(storingUrl, localHash); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if ( + Object.values(MetadataHashValidationErrors).includes(error.message) + ) { + if (setErrorMessage) setErrorMessage(error.message); + if (setStep) setStep(4); + } + throw error; + } + }, + [], + ); + + const onSubmit = useCallback( + async (data: VoteContextFormValues) => { + try { + await validateHash(data.storingURL, hash); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + captureException(error); + } finally { + if (setSavedHash) setSavedHash(hash); + if (setStep) setStep(4); + } + }, + [hash], + ); + + return { + control, + validateURL: handleSubmit(onSubmit), + errors, + generateMetadata, + getValues, + isValid, + onClickDownloadJson, + register, + reset, + setValue, + watch, + hash, + }; +}; diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index 19e49e5f0..04cbf8af8 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -323,6 +323,7 @@ export const en = { changeVote: "Change vote", changeYourVote: "Change your vote", chooseHowToVote: "Choose how you want to vote:", + contextAboutYourVote: "Context about your vote", dataMissing: "Data Missing", dataMissingTooltipExplanation: "Please click “View Details” for more information.", @@ -333,12 +334,14 @@ export const en = { forGovAction: "for this Governance Action", governanceActionId: "Governance Action ID:", governanceActionType: "Governance Action Type:", + goToVote: "Go to Vote", motivation: "Motivation", myVote: "My Vote:", noResultsForTheSearch: "No results for the search.", onChainTransactionDetails: "On-chain Transaction Details", optional: "(optional)", - provideContext: "Provide context about your vote", + provideContext: "Provide context", + provideContextAboutYourVote: "Provide context about your vote", rationale: "Rationale", seeExternalData: "See external data", selectDifferentOption: "Select a different option to change your vote", @@ -353,6 +356,7 @@ export const en = { viewOtherDetails: "View other details", viewProposalDetails: "View proposal details", vote: "Vote", + voteContextJsonldFileName: "Vote_Context.jsonld", votedOnByMe: "Voted on by me", voteOnGovActions: "Vote on Governance Action", voteSubmitted: "Vote submitted", @@ -361,6 +365,8 @@ export const en = { votesSubmitted: "Votes submitted", votesSubmittedOnChain: "Votes submitted on-chain by DReps, SPOs and Constitutional Committee members.", + youCanProvideContext: + "You can provide context about your vote. This information will be viewable by other users.", youHaventVotedYet: "You haven't voted on any Governance Actions yet. Check the 'To vote on' section to vote on Governance Actions.", withCategoryNotExist: { @@ -605,10 +611,12 @@ export const en = { cancel: "Cancel", clear: "Clear", clickToCopyLink: "Click to copy link", + close: "Close", confirm: "Confirm", continue: "Continue", delegate: "Delegate", filter: "Filter", + goBack: "Go back", here: "here", inProgress: "In progress", learnMore: "Learn more", @@ -623,6 +631,7 @@ export const en = { seeTransaction: "See transaction", select: "Select", share: "Share", + showLess: "Show less", showMore: "Show more", skip: "Skip", sort: "Sort",