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

fix: create errors orders and fix translations #4202

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
13 changes: 11 additions & 2 deletions src/components/v5/common/ActionFormRow/ActionFormRow.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { CaretRight } from '@phosphor-icons/react';
import clsx from 'clsx';
import React from 'react';
import React, { useEffect } from 'react';
import { useController } from 'react-hook-form';

import useToggle from '~hooks/useToggle/index.ts';
import Tooltip from '~shared/Extensions/Tooltip/index.ts';

import { useInputsOrderContext } from '../ActionSidebar/partials/ActionSidebarContent/InputsOrderContext/InputsOrderContext.ts';

import { LABEL_CLASSNAME } from './consts.ts';
import { type ActionFormRowProps } from './types.ts';

Expand All @@ -26,6 +28,13 @@ const ActionFormRow = <T,>(
const {
fieldState: { error },
} = useController({ name: fieldName || '' });
const { registerInput } = useInputsOrderContext();

useEffect(() => {
if (fieldName) {
registerInput(fieldName);
}
}, [fieldName, registerInput]);
const rowToggle = useToggle();
const [isExpanded, { toggle }] = rowToggle;
const isError = !!error;
Expand All @@ -52,7 +61,7 @@ const ActionFormRow = <T,>(
'text-gray-900': !isDisabled && !isError,
'text-gray-300': isDisabled,
},
'ml-2 flex items-center gap-2 text-md',
'ml-2 flex cursor-pointer items-center gap-2 text-md',
)}
>
{title}
Expand Down
5 changes: 5 additions & 0 deletions src/components/v5/common/ActionSidebar/ActionTypeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import ActionFormRow from '../ActionFormRow/index.ts';
import { ACTION_TYPE_FIELD_NAME, NON_RESETTABLE_FIELDS } from './consts.ts';
import useActionsList from './hooks/useActionsList.ts';
import { useActiveActionType } from './hooks/useActiveActionType.ts';
import { useInputsOrderContext } from './partials/ActionSidebarContent/InputsOrderContext/InputsOrderContext.ts';
import { translateAction } from './utils.ts';

const displayName = 'v5.common.ActionTypeSelect';
Expand All @@ -26,6 +27,7 @@ interface ActionTypeSelectProps {

const ActionTypeSelect: FC<ActionTypeSelectProps> = ({ className }) => {
const actionsList = useActionsList();
const { unregisterInputs } = useInputsOrderContext();
const [nextActionType, setNextActionType] = useState<string | undefined>(
undefined,
);
Expand Down Expand Up @@ -132,6 +134,7 @@ const ActionTypeSelect: FC<ActionTypeSelectProps> = ({ className }) => {
}

onChange(action);
unregisterInputs();
}}
/>
)}
Expand All @@ -148,6 +151,8 @@ const ActionTypeSelect: FC<ActionTypeSelectProps> = ({ className }) => {
onConfirm={() => {
reset(defaultValues);
setNextActionType(undefined);
onChange(nextActionType);
unregisterInputs();
}}
icon={WarningCircle}
buttonMode="primarySolid"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import ActionSidebarDescription from '../ActionSidebarDescription/ActionSidebarD
import RemoveDraftModal from '../RemoveDraftModal/RemoveDraftModal.tsx';

import { useGetFormActionErrors } from './hooks.ts';
import InputsOrderContextProvider from './InputsOrderContext/InputsOrderContextProvider.tsx';
import { MultiSigMembersError } from './partials/MultiSigMembersError.tsx';
import NoPermissionsError from './partials/NoPermissionsError.tsx';
import NoReputationError from './partials/NoReputationError.tsx';
Expand Down Expand Up @@ -315,11 +316,13 @@ const ActionSidebarContent: FC<ActionSidebarContentProps> = ({
}}
testId="action-form"
>
<ActionSidebarFormContent
getFormOptions={getFormOptions}
actionFormProps={actionFormProps}
customError={customError}
/>
<InputsOrderContextProvider>
<ActionSidebarFormContent
getFormOptions={getFormOptions}
actionFormProps={actionFormProps}
customError={customError}
/>
</InputsOrderContextProvider>
</ActionForm>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createContext, useContext } from 'react';

export interface InputsOrderContextValue {
inputsOrder: string[];
unregisterInputs: () => void;
registerInput: (name: string) => void;
}

export const InputsOrderContext = createContext<
InputsOrderContextValue | undefined
>(undefined);

export const useInputsOrderContext = () => {
const context = useContext(InputsOrderContext);
if (context === undefined) {
throw new Error(
'useInputsOrderContext must be used within the InputOrderContextProvider',
);
}
return context;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, {
type FC,
type PropsWithChildren,
useCallback,
useMemo,
useState,
} from 'react';

import { TITLE_FIELD_NAME } from '~v5/common/ActionSidebar/consts.ts';

import { InputsOrderContext } from './InputsOrderContext.ts';

const InputsOrderContextProvider: FC<PropsWithChildren> = ({ children }) => {
const [inputsOrder, setInputsOrder] = useState<string[]>([TITLE_FIELD_NAME]);

const registerInput = useCallback((name: string) => {
setInputsOrder((prevOrder) => {
if (!prevOrder.includes(name)) {
return [...prevOrder, name];
}
return prevOrder;
});
}, []);
const unregisterInputs = useCallback(() => {
setInputsOrder([TITLE_FIELD_NAME]);
}, []);

const value = useMemo(
() => ({
inputsOrder,
registerInput,
unregisterInputs,
}),
[inputsOrder, registerInput, unregisterInputs],
);

return (
<InputsOrderContext.Provider value={value}>
{children}
</InputsOrderContext.Provider>
);
};

export default InputsOrderContextProvider;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import useFlatFormErrors from '~hooks/useFlatFormErrors.ts';
import { uniqBy } from '~utils/lodash.ts';
import { REPUTATION_VALIDATION_FIELD_NAME } from '~v5/common/ActionSidebar/consts.ts';

import { useInputsOrderContext } from './InputsOrderContext/InputsOrderContext.ts';

export const useGetFormActionErrors = () => {
const {
formState: { errors },
Expand All @@ -15,7 +17,19 @@ export const useGetFormActionErrors = () => {

const flatFormErrors = uniqBy(allFlatFormErrors, 'message');

const { inputsOrder } = useInputsOrderContext();

const orderMap = new Map<string, number>(
inputsOrder.map((fieldName, index) => [fieldName, index]),
);

const sortedFlatFormErrors = flatFormErrors.sort((a, b) => {
const aIndex = orderMap.get(a.key.toString()) ?? Infinity;
const bIndex = orderMap.get(b.key.toString()) ?? Infinity;

return aIndex - bIndex;
});
return {
flatFormErrors,
flatFormErrors: sortedFlatFormErrors,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React, { useEffect, type FC, type PropsWithChildren } from 'react';

import { useInputsOrderContext } from '../InputsOrderContext/InputsOrderContext.ts';

const InputsOrderCellWrapper: FC<PropsWithChildren<{ fieldName: string }>> = ({
children,
fieldName,
}) => {
const { registerInput } = useInputsOrderContext();

useEffect(() => {
registerInput(fieldName);
}, [fieldName, registerInput]);
return <>{children}</>;
};

export default InputsOrderCellWrapper;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { array, type InferType, object, string } from 'yup';

import getLastIndexFromPath from '~utils/getLastIndexFromPath.ts';
import { formatText } from '~utils/intl.ts';
import { ACTION_BASE_VALIDATION_SCHEMA } from '~v5/common/ActionSidebar/consts.ts';

Expand All @@ -8,14 +9,57 @@ export const validationSchema = object()
title: string()
.trim()
.required(() => 'Please enter a title.'),
decisionMethod: string().defined(),
decisionMethod: string().required(
formatText({ id: 'errors.decisionMethod.required' }),
),
transactions: array()
.of(
object()
.shape({
contractAddress: string().defined(),
jsonAbi: string().defined(),
method: string().defined(),
contractAddress: string().required(({ path }) => {
const index = getLastIndexFromPath(path);
if (index === undefined) {
return formatText({
id: 'errors.arbitrary.contractAddressRequired',
});
}
return formatText(
{
id: 'errors.arbitrary.contractAddressRequiredIn',
},
{
arbitraryIndex: index + 1,
},
);
}),
jsonAbi: string().required(({ path }) => {
const index = getLastIndexFromPath(path);
if (index === undefined) {
return formatText({ id: 'errors.arbitrary.jsonABIRequired' });
}
return formatText(
{
id: 'errors.arbitrary.jsonABIRequiredIn',
},
{
arbitraryIndex: index + 1,
},
);
}),
method: string().required(({ path }) => {
const index = getLastIndexFromPath(path);
if (index === undefined) {
return formatText({ id: 'errors.arbitrary.methodRequired' });
}
return formatText(
{
id: 'errors.arbitrary.methodRequiredIn',
},
{
arbitraryIndex: index + 1,
},
);
}),
args: array().of(
object().shape({
value: string().required(
Expand All @@ -29,7 +73,7 @@ export const validationSchema = object()
})
.defined(),
)
.required(),
.required(formatText({ id: 'errors.transactions.required' })),
})
.defined()
.concat(ACTION_BASE_VALIDATION_SCHEMA);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type InferType, object, string, number } from 'yup';

import { MAX_ANNOTATION_LENGTH } from '~constants/index.ts';
import { formatText } from '~utils/intl.ts';
import { ACTION_BASE_VALIDATION_SCHEMA } from '~v5/common/ActionSidebar/consts.ts';

export const validationSchema = object()
Expand All @@ -12,15 +13,20 @@ export const validationSchema = object()
description: string()
.test(
'descriptionLength',
`Description must be at most ${MAX_ANNOTATION_LENGTH} characters`,
formatText(
{ id: 'errors.description.maxLength' },
{ maxLength: MAX_ANNOTATION_LENGTH },
),
(value) => {
if (!value) return true;
// This is added to remove HTML tags from the text to count the length
return value.replace(/<[^>]*>/g, '').length <= MAX_ANNOTATION_LENGTH;
},
)
.required(),
decisionMethod: string().defined(),
.required(formatText({ id: 'errors.description.required' })),
decisionMethod: string().required(
formatText({ id: 'errors.decisionMethod.required' }),
),
walletAddress: string().address().required(),
})
.defined()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ import {
MAX_COLONY_DISPLAY_NAME,
MAX_DOMAIN_PURPOSE_LENGTH,
} from '~constants/index.ts';
import { formatText } from '~utils/intl.ts';
import { ACTION_BASE_VALIDATION_SCHEMA } from '~v5/common/ActionSidebar/consts.ts';

export const validationSchema = object()
.shape({
teamName: string()
.trim()
.max(MAX_COLONY_DISPLAY_NAME)
.required(() => 'Team name required.'),
.required(formatText({ id: 'errors.teamName.required' })),
domainPurpose: string().trim().max(MAX_DOMAIN_PURPOSE_LENGTH).notRequired(),
domainColor: string().defined(),
domainColor: string().required(
formatText({ id: 'errors.domainColor.required' }),
),
createdIn: number().defined(),
decisionMethod: string().defined(),
decisionMethod: string().required(
formatText({ id: 'errors.decisionMethod.required' }),
),
})
.defined()
.concat(ACTION_BASE_VALIDATION_SCHEMA);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,34 @@ export const getEditColonyDetailsValidationSchema = (
image: string().nullable().defined(),
thumbnail: string().nullable().defined(),
}),
colonyName: string().trim().max(MAX_COLONY_DISPLAY_NAME).required(),
colonyName: string()
.trim()
.required(formatText({ id: 'errors.colonyName.required' }))
.max(
MAX_COLONY_DISPLAY_NAME,
formatText(
{ id: 'errors.colonyName.maxLength' },
{
maxLength: MAX_COLONY_DISPLAY_NAME,
},
),
),
createdIn: string().defined(),
decisionMethod: string().defined(),
decisionMethod: string().required(
formatText({ id: 'errors.decisionMethod.required' }),
),
description: string().max(MAX_ANNOTATION_LENGTH).notRequired(),
colonyDescription: string().required().max(MAX_COLONY_DESCRIPTION_LENGTH),
colonyDescription: string()
.required(formatText({ id: 'errors.colonyDescription.required' }))
.max(
MAX_COLONY_DESCRIPTION_LENGTH,
formatText(
{ id: 'errors.colonyDescription.maxLength' },
{
maxLength: MAX_COLONY_DESCRIPTION_LENGTH,
},
),
),
externalLinks: array()
.of(
object()
Expand Down
Loading