Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#377, #432] feat: form inputs validation #445

Merged
merged 1 commit into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,27 @@ As a minor extension, we also keep a semantic version for the `UNRELEASED`
changes.

## [Unreleased]

- Create GA review subbmision page [Issue 362](https://github.com/IntersectMBO/govtool/issues/362)
- Create GA creation form [Issue 360](https://github.com/IntersectMBO/govtool/issues/360)
- Create TextArea [Issue 110](https://github.com/IntersectMBO/govtool/issues/110)
- Choose GA type - GA Submiter [Issue 358](https://github.com/IntersectMBO/govtool/issues/358)

- Add on-chain inputs validation [Issue 377](https://github.com/IntersectMBO/govtool/issues/377)

### Added

- Added `isRegisteredAsSoleVoter` and `wasRegisteredAsSoleVoter` fields to the drep/info response [Issue 212](https://github.com/IntersectMBO/govtool/issues/212)
- Abandoning registration as DRep [Issue 151](https://github.com/IntersectMBO/govtool/issues/151)
- Abandoning GA creation [Issue 359](https://github.com/IntersectMBO/govtool/issues/359)
- Choose GA type - GA Submiter [Issue 358](https://github.com/IntersectMBO/govtool/issues/358)
- Change step 3 components [Issue 152](https://github.com/intersectMBO/govtool/issues/152)
- Add possibility to vote on behalf of myself - Sole Voter [Issue 119](https://github.com/IntersectMBO/govtool/issues/119)
- Create DRep registration page about roles [Issue 205](https://github.com/IntersectMBO/govtool/issues/205)
- Create Checkbox component. Improve Field and ControlledField [Issue 177](https://github.com/IntersectMBO/govtool/pull/177)
- Vitest unit tests added for utils functions [Issue 81](https://github.com/IntersectMBO/govtool/issues/81)
- i18next library added to FE [Issue 80](https://github.com/IntersectMBO/govtool/issues/80)

### Added
- Added `isRegisteredAsSoleVoter` and `wasRegisteredAsSoleVoter` fields to the drep/info response [Issue 212](https://github.com/IntersectMBO/govtool/issues/212)
- Add possibility to vote on behalf of myself - Sole Voter [Issue 119](https://github.com/IntersectMBO/govtool/issues/119)

### Fixed

- Fix drep type detection when changing metadata [Issue 333](https://github.com/IntersectMBO/govtool/issues/333)
- Fix make button disble when wallet tries connect [Issue 265](https://github.com/IntersectMBO/govtool/issues/265)
- Fix drep voting power calculation [Issue 231](https://github.com/IntersectMBO/govtool/issues/231)
Expand Down Expand Up @@ -58,6 +62,7 @@ changes.
- Added a grafana panel to track all the deploys on the target machines [Issue 361](https://github.com/IntersectMBO/govtool/issues/361).

### Removed

-

## [sancho-v1.0.0](https://github.com/IntersectMBO/govtool/releases/tag/sancho-v1.0.0) 2023-12-17
Expand Down
1 change: 1 addition & 0 deletions govtool/frontend/src/components/atoms/TextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
<TextAreaBase
style={{
border: `1px solid ${errorMessage ? "red" : "#6F99FF"}`,
backgroundColor: errorMessage ? "#FAEAEB" : "white",
borderRadius: "24px",
height: isMobile ? "104px" : "128px",
outline: "none",
Expand Down
13 changes: 11 additions & 2 deletions govtool/frontend/src/components/molecules/Field/TextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaFieldProps>(
{label}
</Typography>
)}
<TextAreaBase maxLength={maxLength} {...props} ref={textAreaRef} />
<TextAreaBase
errorMessage={errorMessage}
maxLength={maxLength}
{...props}
ref={textAreaRef}
/>
<FormHelpfulText
helpfulText={helpfulText}
helpfulTextStyle={helpfulTextStyle}
Expand All @@ -86,7 +91,11 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaFieldProps>(
/>
<Typography
color="#8E908E"
sx={{ bottom: 35, position: "absolute", right: 15 }}
sx={{
bottom: errorMessage ? 52.5 : 35,
position: "absolute",
right: 15,
}}
variant="caption"
>
{props?.value?.toString()?.length ?? 0}/{maxLength}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Dispatch, SetStateAction } from "react";

import { ActionRadio, Spacer, Typography } from "@atoms";
import { GOVERNANCE_ACTION_TYPES } from "@consts";
import {
useCreateGovernanceActionForm,
useScreenDimension,
useTranslation,
} from "@hooks";
import { GovernanceActionType } from "@/types/governanceAction";

import { BgCard } from "./BgCard";
import { BgCard } from "../BgCard";

type ChooseGovernanceActionTypeProps = {
setStep: Dispatch<SetStateAction<number>>;
Expand All @@ -32,25 +31,26 @@ export const ChooseGovernanceActionType = ({
};

// TODO: Add tooltips when they will be available
const renderGovernanceActionTypes = () => {
return GOVERNANCE_ACTION_TYPES.map((type, index) => {
const isChecked = getValues("governance_action_type") === type;
return (
<div key={type}>
<ActionRadio
isChecked={isChecked}
onChange={onChangeType}
title={type}
value={type}
/>
{index + 1 < GOVERNANCE_ACTION_TYPES.length ? <Spacer y={2} /> : null}
</div>
);
});
};
const renderGovernanceActionTypes = () =>
Object.keys(GovernanceActionType).map(
(type, index, governanceActionTypes) => {
const isChecked = getValues("governance_action_type") === type;
return (
<div key={type}>
<ActionRadio
isChecked={isChecked}
onChange={onChangeType}
title={type}
value={type}
/>
{index + 1 < governanceActionTypes.length ? <Spacer y={2} /> : null}
</div>
);
}
);

const onChangeType = (value: string) => {
setValue("governance_action_type", value);
setValue("governance_action_type", value as GovernanceActionType);
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,32 @@ import { useFieldArray } from "react-hook-form";
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";

import { Button, InfoText, Spacer, Typography } from "@atoms";
import { GOVERNANCE_ACTIONS_FIELDS } from "@consts";
import { GOVERNANCE_ACTION_FIELDS } from "@consts";
import { useCreateGovernanceActionForm, useTranslation } from "@hooks";
import { Field } from "@molecules";

import { BgCard } from "./BgCard";
import { ControlledField } from "./ControlledField";
import { BgCard } from "../BgCard";
import { ControlledField } from "../ControlledField";
import { GovernanceActionField } from "@/types/governanceAction";
import { URL_REGEX } from "@/utils";

const LINK_PLACEHOLDER = "https://website.com/";
const MAX_NUMBER_OF_LINKS = 8;

type ChooseGovernanceActionTypeProps = {
type CreateGovernanceActionFormProps = {
setStep: Dispatch<SetStateAction<number>>;
};

export const CreateGovernanceActionForm = ({
setStep,
}: ChooseGovernanceActionTypeProps) => {
}: CreateGovernanceActionFormProps) => {
const { t } = useTranslation();
const { control, errors, getValues, register, reset, watch } =
useCreateGovernanceActionForm();

const isError = Object.keys(errors).length > 0;

const type = getValues("governance_action_type");
const {
append,
fields: links,
Expand All @@ -32,16 +38,11 @@ export const CreateGovernanceActionForm = ({
name: "links",
});

const governanceActionType = getValues("governance_action_type");
const fields =
GOVERNANCE_ACTIONS_FIELDS.find(
(field) => field.name === governanceActionType
)?.fields ?? [];

// TODO: Replace any
const isContinueButtonDisabled = Object.keys(fields).some(
(field: any) => !watch(field)
);
const isContinueButtonDisabled =
Object.keys(GOVERNANCE_ACTION_FIELDS[type!]).some(
(field: any) => !watch(field)
) || isError;

const onClickContinue = () => {
setStep(4);
Expand All @@ -53,37 +54,35 @@ export const CreateGovernanceActionForm = ({
};

const renderGovernanceActionField = () => {
return Object.entries(fields).map(([key, value]) => {
const label =
key.charAt(0).toUpperCase() + key.slice(1).replace("_", " ");

if (value.component === "input") {
return (
<ControlledField.Input
{...{ control, errors }}
helpfulText={value.tip}
key={key}
label={label}
layoutStyles={{ mb: 3 }}
name={key}
placeholder={value.placeholder}
/>
);
}
if (value.component === "textarea") {
return (
<ControlledField.TextArea
{...{ control, errors }}
helpfulText={value.tip}
key={key}
label={label}
layoutStyles={{ mb: 3 }}
name={key}
placeholder={value.placeholder}
/>
);
return Object.entries(GOVERNANCE_ACTION_FIELDS[type!]).map(
([key, field]) => {
const fieldProps = {
helpfulText: field.tipI18nKey ? t(field.tipI18nKey) : undefined,
key,
label: t(field.labelI18nKey),
layoutStyles: { mb: 3 },
name: key,
placeholder: field.placeholderI18nKey
? t(field.placeholderI18nKey)
: undefined,
rules: field.rules,
};

if (field.component === GovernanceActionField.Input) {
return (
<ControlledField.Input {...{ control, errors }} {...fieldProps} />
);
}
if (field.component === GovernanceActionField.TextArea) {
return (
<ControlledField.TextArea
{...{ control, errors }}
{...fieldProps}
/>
);
}
}
});
);
};

const addLink = useCallback(() => {
Expand All @@ -102,6 +101,7 @@ export const CreateGovernanceActionForm = ({
return (
<ControlledField.Input
{...register(`links.${index}.link`)}
errors={errors}
endAdornment={
links.length > 1 ? (
<DeleteOutlineIcon
Expand All @@ -115,6 +115,17 @@ export const CreateGovernanceActionForm = ({
label={t("forms.link") + ` ${index + 1}`}
layoutStyles={{ mb: 3 }}
placeholder={LINK_PLACEHOLDER}
name={`links.${index}.link`}
rules={{
required: {
value: true,
message: t("createGovernanceAction.fields.validations.required"),
},
pattern: {
value: URL_REGEX,
message: t("createGovernanceAction.fields.validations.url"),
},
}}
/>
);
});
Expand All @@ -136,7 +147,7 @@ export const CreateGovernanceActionForm = ({
disabled={true}
helpfulText={t("forms.createGovernanceAction.typeTip")}
label={t("forms.createGovernanceAction.typeLabel")}
value={governanceActionType}
value={type}
/>
<Spacer y={3} />
{renderGovernanceActionField()}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Dispatch, SetStateAction } from "react";
import { Box } from "@mui/material";
import DriveFileRenameOutlineOutlinedIcon from "@mui/icons-material/DriveFileRenameOutlineOutlined";

Expand All @@ -12,7 +11,8 @@ import {
import { LinkWithIcon } from "@molecules";
import { openInNewTab } from "@utils";

import { BgCard } from "./BgCard";
import { BgCard } from "../BgCard";
import { Dispatch, SetStateAction } from "react";

type ReviewCreatedGovernanceActionProps = {
setStep: Dispatch<SetStateAction<number>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { Button, Spacer, Typography } from "@atoms";
import { useCreateGovernanceActionForm, useTranslation } from "@hooks";
import { Step } from "@molecules";
import { BgCard, ControlledField } from "@organisms";
import { downloadJson, openInNewTab } from "@utils";
import { URL_REGEX, downloadJson, openInNewTab } from "@utils";

export const StorageInformation = ({
setStep,
}: {
type StorageInformationProps = {
setStep: Dispatch<SetStateAction<number>>;
}) => {
};

export const StorageInformation = ({ setStep }: StorageInformationProps) => {
const { t } = useTranslation();
const {
control,
Expand All @@ -23,6 +23,7 @@ export const StorageInformation = ({
watch,
} = useCreateGovernanceActionForm();
const [isJsonDownloaded, setIsJsonDownloaded] = useState<boolean>(false);

// TODO: change on correct file name
const fileName = getValues("governance_action_type");

Expand All @@ -36,7 +37,7 @@ export const StorageInformation = ({

const onClickBack = useCallback(() => setStep(5), []);

const onClickDowloadJson = () => {
const onClickDownloadJson = () => {
const data = getValues();
const jsonBody = generateJsonBody(data);
downloadJson(jsonBody, fileName);
Expand Down Expand Up @@ -66,7 +67,7 @@ export const StorageInformation = ({
// TODO: add onClick action when available
component={
<Button
onClick={onClickDowloadJson}
onClick={onClickDownloadJson}
size="extraLarge"
sx={{ width: "fit-content" }}
>
Expand Down Expand Up @@ -109,6 +110,18 @@ export const StorageInformation = ({
placeholder={t(
"createGovernanceAction.storingInformationURLPlaceholder"
)}
rules={{
required: {
value: true,
message: t(
"createGovernanceAction.fields.validations.required"
),
},
pattern: {
value: URL_REGEX,
message: t("createGovernanceAction.fields.validations.url"),
},
}}
/>
}
label={t("createGovernanceAction.storingInformationStep3Label")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import {
import { BgCard, ControlledField } from "@organisms";
import { openInNewTab } from "@utils";

export const StoreDataInfo = ({
setStep,
}: {
type StoreDataInfoProps = {
setStep: Dispatch<SetStateAction<number>>;
}) => {
};

export const StoreDataInfo = ({ setStep }: StoreDataInfoProps) => {
const { t } = useTranslation();
const { control, errors, watch } = useCreateGovernanceActionForm();
const { isMobile } = useScreenDimension();
Expand Down
Loading