Skip to content

Commit

Permalink
[#377, #432] feat: form inputs validation
Browse files Browse the repository at this point in the history
  • Loading branch information
MSzalowski committed Mar 11, 2024
1 parent 2c6e008 commit e959a76
Show file tree
Hide file tree
Showing 21 changed files with 364 additions and 169 deletions.
18 changes: 12 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 All @@ -40,6 +44,7 @@ changes.
- Fixed CSP settings to allow error reports with Sentry [Issue 291](https://github.com/IntersectMBO/govtool/issues/291).

### Changed

- `drep/list` and `drep/info` endpoints now return additional data such as metadata url and hash, and voting power [Issue 223](https://github.com/IntersectMBO/govtool/issues/223)
- `drep/info` now does not return sole voters (dreps without metadata) [Issue 317](https://github.com/IntersectMBO/govtool/issues/317)
- `isRegistered` and `wasRegistered` fields in the drep/info endpoint changed to `isRegisteredAsDRep` and `wasRegisteredAsDRep` respectively [Issue 212](https://github.com/IntersectMBO/govtool/issues/212)
Expand All @@ -57,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,31 @@ 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";

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 +37,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 +53,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 Down Expand Up @@ -115,6 +113,16 @@ export const CreateGovernanceActionForm = ({
label={t("forms.link") + ` ${index + 1}`}
layoutStyles={{ mb: 3 }}
placeholder={LINK_PLACEHOLDER}
rules={{
required: {
value: true,
message: t("createGovernanceAction.fields.validations.required"),
},
pattern: {
value: /^(http|https):\/\/[^ "]+$/,
message: t("createGovernanceAction.fields.validations.url"),
},
}}
/>
);
});
Expand All @@ -136,7 +144,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 @@ -8,11 +8,11 @@ import { Step } from "@molecules";
import { BgCard, ControlledField } from "@organisms";
import { 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: /^(http|https):\/\/[^ "]+$/,
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

0 comments on commit e959a76

Please sign in to comment.