diff --git a/src/assets/locales/en/translation.json b/src/assets/locales/en/translation.json index 5b1171dc..e54aa7a7 100644 --- a/src/assets/locales/en/translation.json +++ b/src/assets/locales/en/translation.json @@ -66,6 +66,19 @@ "Delete3BoxPostConfirmation": "Delete this comment?", "Yes": "Yes", "No": "No", + "Required": "Required", + "Validate Non-Negative" : "Please enter a non-negative value", + "Add contract": "Add contract", + "Contract Add Success": "Contract added successfully!", + "Contract Exist": "Contract already exist!", + "Choose contract": "Choose contract", + "Choose method": "Choose method", + "Loading": "Loading...", + "Remove": "Remove", + "Cancel": "Cancel", + "Submit proposal": "Submit proposal", + "Whitelisted contracts": "Whitelisted contracts", + "User contracts": "User contracts", "No Proposals Found": "No proposals found whose title contains the given text. Note the filter is case-sensitive.", "Type and press Enter ": "Type and press Enter or Tab to filter proposals by title", "Unregistered Plugin Proposal": "This proposal is in unregistered plugin", diff --git a/src/components/Proposal/Create/CreateProposal.scss b/src/components/Proposal/Create/CreateProposal.scss index 0ac092c2..30eacaaa 100644 --- a/src/components/Proposal/Create/CreateProposal.scss +++ b/src/components/Proposal/Create/CreateProposal.scss @@ -8,7 +8,25 @@ } fieldset { - margin: 10px 0px; + margin: 30px 0px; +} + +.removeFieldSet { + float: right; + border: none; + color: $accent-2; + background: none; +} + +.addFieldSet { + border: none; + background: none; + color: $sky; +} + +.removeFieldSet:hover, +.addFieldSet:hover { + opacity: 0.7; } .createProposalWrapper { @@ -67,6 +85,7 @@ fieldset { } form { + select, input { width: 100%; margin-top: 3px; @@ -123,11 +142,16 @@ fieldset { } } + .addContract { + position: relative; + } + .encodedData { overflow-wrap: break-word; - margin: 5px 0 40px 0px; + margin: 5px 0 5px 0px; // margin: 5px 0 40px 0px; TEMP border: 1px solid; padding: 20px; + max-width: 480px; // TEMPORARY - NEED TO BE DYNAMIC } .proposerIsAdminCheckbox { diff --git a/src/components/Proposal/Create/PluginForms/ABIService.ts b/src/components/Proposal/Create/PluginForms/ABIService.ts index 38d4c602..0711f3fe 100644 --- a/src/components/Proposal/Create/PluginForms/ABIService.ts +++ b/src/components/Proposal/Create/PluginForms/ABIService.ts @@ -1,9 +1,9 @@ import { AbiItem } from "web3-utils"; -import { Interface, isHexString } from "ethers/utils"; +import { Interface } from "ethers/utils"; import { SortService } from "lib/sortService"; const Web3 = require("web3"); import axios from "axios"; -import { isAddress, targetedNetwork } from "lib/util"; +import { targetedNetwork } from "lib/util"; export interface IAllowedAbiItem extends AbiItem { name: string @@ -74,36 +74,6 @@ export const extractABIMethods = (abi: AbiItem[]): IAbiItemExtended[] => { .sort(({ name: a }, { name: b }) => SortService.evaluateString(a, b, 1)); }; -/** - * Given array of ABI parameters objects, returns true if all values are valid. - * Data example: - * [{ type: address, value: "0x25112235dDA2F775c81f0AA37a2BaeA21B470f65" }] - * @param {array} data - * @returns {boolean} - */ -export const validateABIInputs = (data: Array): boolean => { - for (const input of data) { - switch (input.type) { - case "address": - if (!isAddress(input.value)) { - return false; - } - break; - case "bytes": - if (!isHexString(input.value)) { - return false; - } - break; - case "uint256": - if (/^\d+$/.test(input.value) === false) { - return false; - } - break; - } - } - return true; -}; - /** * Given contract address returns it's ABI data. * @param {string} contractAddress @@ -124,22 +94,17 @@ export const getABIByContract = async (contractAddress: string): Promise, name: string, data: any[]): string => { - const interfaceABI = new Interface(abi); - - if (validateABIInputs(data)) { - const values = []; - for (const input of data) { - values.push(input.value); - } +export const encodeABI = (abi: Array, name: string, values: any[]): string => { + try { + const interfaceABI = new Interface(abi); return interfaceABI.functions[name].encode(values); + } catch (error) { + return error.reason; } - - return ""; }; diff --git a/src/components/Proposal/Create/PluginForms/CreateGenericMultiCallProposal.tsx b/src/components/Proposal/Create/PluginForms/CreateGenericMultiCallProposal.tsx new file mode 100644 index 00000000..3ec76b24 --- /dev/null +++ b/src/components/Proposal/Create/PluginForms/CreateGenericMultiCallProposal.tsx @@ -0,0 +1,534 @@ +import { IPluginState } from "@daostack/arc.js"; +import { createProposal } from "actions/arcActions"; +import { enableWalletProvider } from "arc"; +import { ErrorMessage, Field, Form, Formik, FormikProps, FieldArray } from "formik"; +import Analytics from "lib/analytics"; +import * as React from "react"; +import { connect } from "react-redux"; +import { showNotification } from "reducers/notifications"; +import { baseTokenName, isValidUrl, linkToEtherScan, isAddress } from "lib/util"; +import TagsSelector from "components/Proposal/Create/PluginForms/TagsSelector"; +import TrainingTooltip from "components/Shared/TrainingTooltip"; +import * as css from "../CreateProposal.scss"; +import MarkdownField from "./MarkdownField"; +import HelpButton from "components/Shared/HelpButton"; +import i18next from "i18next"; +import { IFormModalService, CreateFormModalService } from "components/Shared/FormModalService"; +import ResetFormButton from "components/Proposal/Create/PluginForms/ResetFormButton"; +import { getABIByContract, extractABIMethods, encodeABI } from "./ABIService"; +import Loading from "components/Shared/Loading"; +import { requireValue, validateParam } from "./Validators"; + +const whitelistedContracts = [ + "0x543Ff227F64Aa17eA132Bf9886cAb5DB55DCAddf", + "0x5C5cbaC45b18F990AbcC4b890Bf98d82e9ee58A0", + "0x24832a7A5408B2b18e71136547d308FCF60B6e71", + "0x4E073a7E4a2429eCdfEb1324a472dd8e82031F34", +]; + +interface IExternalProps { + daoAvatarAddress: string; + handleClose: () => any; + pluginState: IPluginState; +} + +interface IDispatchProps { + createProposal: typeof createProposal; + showNotification: typeof showNotification; +} + +type AddContractError = "NOT_VALID_ADDRESS" | "CONTRACT_EXIST" | "ABI_DATA_ERROR" | ""; + +interface IAddContractStatus { + error: AddContractError + message: string +} + +interface IStateProps { + loading: boolean + tags: Array + addContractStatus: IAddContractStatus + whitelistedContracts: Array + userContracts: Array +} + +// interface IABIField { +// id: string, +// name: string +// type: string +// placeholder: string, +// } + +type IProps = IExternalProps & IDispatchProps; + +const mapDispatchToProps = { + createProposal, + showNotification, +}; + +interface IContract { + address: string // Contract address + value: number // Token to send with the proposal + abi: any // Contract ABI data + methods: any // ABI write methods + method: string // Selected method + params: any // Method params + values: any // Params values + callData: string // The encoded data +} + +interface IFormValues { + description: string; + title: string; + url: string; + contracts: Array + [key: string]: any; +} + +const defaultValues: IFormValues = Object.freeze({ + description: "", + title: "", + url: "", + tags: [], + userContracts: [], + contracts: [ + { + address: "", + value: 0, + abi: [], + methods: [], + method: "", + params: [], + values: [], + callData: "", + }, + ], +}); + + +class CreateGenericMultiCallProposal extends React.Component { + + formModalService: IFormModalService; + currentFormValues: IFormValues; + + constructor(props: IProps) { + super(props); + this.state = { loading: false, addContractStatus: { error: "", message: "" }, whitelistedContracts: whitelistedContracts, userContracts: [], tags: [] }; + + this.handleSubmit = this.handleSubmit.bind(this); + this.formModalService = CreateFormModalService( + "CreateGenericMultiCallProposal", + defaultValues, + () => Object.assign(this.currentFormValues, this.state), // this.removeABIDataFromObject(this.currentFormValues) + (formValues: IFormValues, firstTime: boolean) => { + this.currentFormValues = formValues; + if (firstTime) { + Object.assign(this.state, { + tags: formValues.tags, + userContracts: formValues.userContracts, + }); + } + else { this.setState({ tags: formValues.tags, userContracts: formValues.userContracts }); } + }, + this.props.showNotification); + } + + // componentDidMount() { + // for (const contract of formValues.contracts) { + // contract.abi = await getABIByContract(contract.address); + // } + // } + + componentWillUnmount() { + this.formModalService.saveCurrentValues(); + } + + public async handleSubmit(formValues: IFormValues, { setSubmitting }: any): Promise { + if (!await enableWalletProvider({ showNotification: this.props.showNotification })) { return; } + + const contractsToCall = []; + const callsData = []; + const values = []; + + for (const contract of formValues.contracts) { + contractsToCall.push(contract.address); + callsData.push(contract.callData); + values.push(contract.value); + } + + const proposalValues = { + title: formValues.title, + description: formValues.description, + contractsToCall: contractsToCall, + callsData: callsData, + values: values, + url: formValues.url, + dao: this.props.daoAvatarAddress, + plugin: this.props.pluginState.address, + tags: this.state.tags, + }; + + setSubmitting(false); + await this.props.createProposal(proposalValues); + + Analytics.track("Submit Proposal", { + "DAO Address": this.props.daoAvatarAddress, + "Proposal Title": formValues.title, + "Plugin Address": this.props.pluginState.address, + "Plugin Name": this.props.pluginState.name, + }); + + this.props.handleClose(); + } + + // private removeABIDataFromObject = (obj: IFormValues) => { + // for (const contract of obj.contracts) { + // contract.abi = []; + // } + // return obj; + // } + + private onTagsChange = (tags: any[]): void => { + this.setState({ tags }); + } + + /** + * Given a contract address, checks whether it's valid, not exists in the current contract list and that the contract is verified with valid ABI data and write methods. + * If all checks are okay, pushes the contract address to the contract lists, otherwise returns an appropriate message. + * @param {string} contractToCall + */ + private verifyContract = async (contractToCall: string) => { + const addContractStatus: IAddContractStatus = { + error: "", + message: "", + }; + + if (!isAddress(contractToCall)) { + addContractStatus.error = "NOT_VALID_ADDRESS"; + addContractStatus.message = i18next.t("Validate Address"); + } else if (this.state.whitelistedContracts.includes(contractToCall) || this.state.userContracts.includes(contractToCall)) { + addContractStatus.error = "CONTRACT_EXIST"; + addContractStatus.message = i18next.t("Contract Exist"); + } else { + this.setState({ loading: true }); + const abiData = await getABIByContract(contractToCall); + const abiMethods = extractABIMethods(abiData); + if (abiMethods.length > 0) { + this.state.userContracts.push(contractToCall); + addContractStatus.error = ""; + addContractStatus.message = i18next.t("Contract Add Success"); + } else { + addContractStatus.error = "ABI_DATA_ERROR"; + addContractStatus.message = abiData.length === 0 ? i18next.t("No ABI") : i18next.t("No Write Methods"); + } + } + this.setState({ loading: false, addContractStatus: addContractStatus }); + } + + private getContractABI = async (contractToCall: string, setFieldValue: any, index: number) => { + setFieldValue(`contracts.${index}.method`, ""); // reset + setFieldValue(`contracts.${index}.callData`, ""); // reset + const abiData = await getABIByContract(contractToCall); + const abiMethods = extractABIMethods(abiData); + setFieldValue(`contracts.${index}.abi`, abiData); + setFieldValue(`contracts.${index}.methods`, abiMethods); + } + + private getMethodInputs = (abi: any, methods: any[], methodName: any, setFieldValue: any, index: number) => { + setFieldValue(`contracts.${index}.callData`, ""); // reset + const selectedMethod = methods.filter(method => method.methodSignature === methodName); + const abiParams = selectedMethod[0].inputs.map((input: any, index: number) => { + return { + id: index, + name: input.name, + type: input.type, + placeholder: `${input.name} (${input.type})`, + methodSignature: input.methodSignature, + }; + }); + setFieldValue(`contracts.${index}.params`, abiParams); + if (abiParams.length === 0) { // If no params, generate the encoded data + setFieldValue(`contracts.${index}.callData`, encodeABI(abi, methodName, [])); + } + } + + private abiInputChange = (abi: any, values: any, name: string, setFieldValue: any, index: number) => { // params: any, + const encodedData = encodeABI(abi, name, values); + setFieldValue(`contracts.${index}.callData`, encodedData); + } + + + public render(): RenderOutput { + const { handleClose } = this.props; + const { loading, addContractStatus, userContracts } = this.state; + + const whitelistedContractsOptions = whitelistedContracts.map((address, index) => { + return ; + }); + + const userContractsOptions = userContracts.map((address, index) => { + return ; + }); + + return ( +
+ + { + const errors: any = {}; + + this.currentFormValues = values; + + const require = (name: string) => { + if (!(values as any)[name]) { + errors[name] = "Required"; + } + }; + + if (!isValidUrl(values.url)) { + errors.url = "Invalid URL"; + } + + require("title"); + require("description"); + + return errors; + }} + onSubmit={this.handleSubmit} + // eslint-disable-next-line react/jsx-no-bind + render={({ + errors, + touched, + handleBlur, + isSubmitting, + resetForm, + values, + setFieldValue, + }: FormikProps) => +
+ + + + + + + + + { setFieldValue("description", value); }} + id="descriptionInput" + placeholder={i18next.t("Description Placeholder")} + name="description" + className={touched.description && errors.description ? css.error : null} + /> + + + + + +
+ +
+ + + + + + +
+ + { setFieldValue("addContract", e.target.value); this.verifyContract(e.target.value); }} + disabled={loading ? true : false} + /> + {loading ? : addContractStatus.error === "ABI_DATA_ERROR" ? +
+ {addContractStatus.message} + {i18next.t("contract")} +
: addContractStatus.message} +
+ + + {({ insert, remove, push }) => ( // eslint-disable-line @typescript-eslint/no-unused-vars +
+ { + values.contracts.length > 0 && values.contracts.map((contract: any, index: any) => ( +
+ {/* eslint-disable-next-line react/jsx-no-bind */} + {values.contracts.length > 1 && } + +
+ + +
+ +
+ + { setFieldValue(`contracts.${index}.address`, e.target.value); await this.getContractABI(e.target.value, setFieldValue, index); }} + component="select" + name={`contracts.${index}.address`} + type="text" + validate={requireValue} + > + + + {whitelistedContractsOptions} + + {userContractsOptions.length > 0 && + + {userContractsOptions} + + } + +
+ + { + values.contracts[index].address !== "" && +
+ + {values.contracts[index]?.methods?.length === 0 ? i18next.t("Loading") : + { setFieldValue(`contracts.${index}.method`, e.target.value); this.getMethodInputs(values.contracts[index].abi, values.contracts[index]?.methods, e.target.value, setFieldValue, index); }} + component="select" + name={`contracts.${index}.method`} + type="text" + validate={requireValue} + > + + {values.contracts[index]?.methods?.map((method: any, j: any) => ( + + ))} + } +
+ } + + { + values.contracts[index].method !== "" && +
+ {values.contracts[index].params.map((param: any, i: number) => ( + + + { handleBlur(e); this.abiInputChange(values.contracts[index].abi, values.contracts[index].values, values.contracts[index].method, setFieldValue, index); }} + // eslint-disable-next-line react/jsx-no-bind + validate={(e: any) => validateParam(param.type, e)} + /> + + ))} +
+ } + + +
{values.contracts[index].callData}
+
+ )) + } + +
+ ) + } +
+ +
+ + + + + + + + + + +
+ + } + /> +
+
+ ); + } +} + +export default connect(null, mapDispatchToProps)(CreateGenericMultiCallProposal); diff --git a/src/components/Proposal/Create/PluginForms/CreateUnknownGenericPluginProposal.tsx b/src/components/Proposal/Create/PluginForms/CreateUnknownGenericPluginProposal.tsx index d74be61c..bdc9c36e 100644 --- a/src/components/Proposal/Create/PluginForms/CreateUnknownGenericPluginProposal.tsx +++ b/src/components/Proposal/Create/PluginForms/CreateUnknownGenericPluginProposal.tsx @@ -6,11 +6,9 @@ import Analytics from "lib/analytics"; import * as React from "react"; import { connect } from "react-redux"; import { showNotification } from "reducers/notifications"; -import { baseTokenName, isValidUrl, isAddress } from "lib/util"; -import { isHexString } from "ethers/utils"; +import { baseTokenName, isValidUrl, linkToEtherScan } from "lib/util"; import TagsSelector from "components/Proposal/Create/PluginForms/TagsSelector"; import TrainingTooltip from "components/Shared/TrainingTooltip"; -import { SelectSearch } from "components/Shared/SelectSearch"; import * as css from "../CreateProposal.scss"; import MarkdownField from "./MarkdownField"; import HelpButton from "components/Shared/HelpButton"; @@ -19,7 +17,7 @@ import { IFormModalService, CreateFormModalService } from "components/Shared/For import ResetFormButton from "components/Proposal/Create/PluginForms/ResetFormButton"; import { getABIByContract, extractABIMethods, encodeABI } from "./ABIService"; import Loading from "components/Shared/Loading"; -import { linkToEtherScan } from "lib/util"; +import { requireValue, validateParam } from "./Validators"; interface IExternalProps { daoAvatarAddress: string; @@ -37,15 +35,12 @@ interface IStateProps { tags: Array abiData: Array abiMethods: Array - abiInputs: IABIField[] - callData: string } interface IABIField { id: string, name: string type: string - inputType: string, placeholder: string, } @@ -57,12 +52,17 @@ const mapDispatchToProps = { }; interface IFormValues { - description: string; - title: string; - url: string; - value: number; - method: string, - [key: string]: any; + description: string + title: string + url: string + value: number + abi: any + methods: Array + method: string + params: IABIField[] + values: any + callData: string + [key: string]: any } const defaultValues: IFormValues = Object.freeze({ @@ -71,7 +71,12 @@ const defaultValues: IFormValues = Object.freeze({ url: "", value: 0, tags: [], + abi: [], + methods: [], method: "", + params: [], + values: [], + callData: "", }); interface INoABIProps { @@ -95,7 +100,7 @@ class CreateGenericPlugin extends React.Component { constructor(props: IProps) { super(props); - this.state = { loading: true, tags: [], abiData: [], abiMethods: [], abiInputs: [], callData: "" }; + this.state = { loading: true, tags: [], abiData: [], abiMethods: [] }; this.handleSubmit = this.handleSubmit.bind(this); this.formModalService = CreateFormModalService( @@ -106,10 +111,10 @@ class CreateGenericPlugin extends React.Component { this.currentFormValues = formValues; if (firstTime) { Object.assign(this.state, { - tags: formValues.tags, abiInputs: this.state.abiInputs, callData: this.state.callData, + tags: formValues.tags, }); } - else { this.setState({ tags: formValues.tags, abiInputs: [], callData: "" }); } + else { this.setState({ tags: formValues.tags }); } }, this.props.showNotification); } @@ -129,11 +134,14 @@ class CreateGenericPlugin extends React.Component { if (!await enableWalletProvider({ showNotification: this.props.showNotification })) { return; } const proposalValues = { - ...values, + title: values.title, + description: values.description, + value: values.value, + callData: values.callData, + url: values.url, dao: this.props.daoAvatarAddress, plugin: this.props.pluginState.address, tags: this.state.tags, - callData: this.state.callData, }; setSubmitting(false); @@ -153,39 +161,37 @@ class CreateGenericPlugin extends React.Component { this.setState({ tags }); } - private getEncodedData = (abi: Array, name: string, values: any[]) => { - const encodedData = encodeABI(abi, name, values); - this.setState({ callData: encodedData }); - } - - private abiInputChange = (values: any) => { + private abiInputChange = (setFieldValue: any, values: any) => { const abiValues = []; - for (const abiInput of this.state.abiInputs) { - abiValues.push({ type: abiInput.type, value: values[abiInput.name] }); + for (const abiInput of values.params) { + abiValues.push(values[abiInput.name]); } - - this.getEncodedData(this.state.abiMethods, values.method, abiValues); + setFieldValue("callData", encodeABI(values.methods, values.method, abiValues)); } - private onSelectChange = (data: any): void => { - const abiInputs = data.inputs.map((input: any, index: number) => { + private onSelectChange = (values: any, setFieldValue: any, data: any): void => { + const abiInputs = data[0].inputs.map((input: any, index: number) => { return { id: index, name: input.name, type: input.type, - inputType: input.type === "bool" ? "number" : "text", placeholder: `${input.name} (${input.type})`, methodSignature: input.methodSignature, }; }); - this.setState({ abiInputs: abiInputs, callData: "" }); + setFieldValue("callData", ""); + setFieldValue("params", abiInputs); + if (abiInputs.length === 0) { - this.getEncodedData(this.state.abiMethods, data.methodSignature, []); + setFieldValue("callData", encodeABI(values.methods, data[0].methodSignature, [])); } } public render(): RenderOutput { + const { abiData, abiMethods } = this.state; + this.currentFormValues.abi = abiData; + this.currentFormValues.methods = abiMethods; const { handleClose } = this.props; const contractToCall = (this.props.pluginState as IGenericPluginState).pluginParams.contractToCall; @@ -217,45 +223,8 @@ class CreateGenericPlugin extends React.Component { errors.url = "Invalid URL"; } - const requireValue = (name: string) => { - if ((values as any)[name] === "") { - errors[name] = "Required"; - } - }; - - const nonNegative = (name: string) => { - if ((values as any)[name] < 0) { - errors[name] = "Please enter a non-negative value"; - } - }; - - if (this.state.abiInputs) { - for (const abiValue of this.state.abiInputs) { - require(abiValue.name); - switch (abiValue.type) { - case "address": - if (!isAddress(values[abiValue.name])) { - errors[abiValue.name] = i18next.t("Validate Address"); - } - break; - case "bytes": - if (!isHexString(values[abiValue.name])) { - errors[abiValue.name] = i18next.t("Validate HEX"); - } - break; - case "uint256": - if (/^\d+$/.test(values[abiValue.name]) === false) { - errors[abiValue.name] = i18next.t("Validate Digits"); - } - break; - } - } - } - require("title"); require("description"); - requireValue("value"); - nonNegative("value"); require("method"); return errors; @@ -349,26 +318,31 @@ class CreateGenericPlugin extends React.Component { name="value" type="number" className={touched.value && errors.value ? css.error : null} + validate={requireValue} /> - { setFieldValue("method", data.methodSignature); this.onSelectChange(data); }} - name="method" - placeholder="-- Select method --" - errors={errors} - cssForm={css} - touched={touched} - nameOnList="methodSignature" - /> +
+ + { setFieldValue("method", e.target.value); this.onSelectChange(values, setFieldValue, this.state.abiMethods.filter(method => method.methodSignature === e.target.value)); }} + component="select" + name="method"> + + {this.state.abiMethods.map((method, index) => { + return ; + })} + +
{ - this.state.abiInputs.map((abiInput, index) => { + values.params.map((abiInput, index) => { return (
@@ -393,7 +368,7 @@ class CreateGenericPlugin extends React.Component { } -
{this.state.callData}
+
{values.callData}
diff --git a/src/components/Proposal/Create/PluginForms/Validators.ts b/src/components/Proposal/Create/PluginForms/Validators.ts index 8e123d13..75dbc4e9 100644 --- a/src/components/Proposal/Create/PluginForms/Validators.ts +++ b/src/components/Proposal/Create/PluginForms/Validators.ts @@ -1,4 +1,6 @@ -import { isAddress } from "../../../../lib/util"; +import { isAddress } from "lib/util"; +import i18next from "i18next"; +import { isHexString } from "ethers/utils"; const constants = { REQUIRED: "Required", @@ -116,3 +118,51 @@ export const address = (value: string, allowNulls = false): string => { } return error; }; + + +/** + * Given a value returns error message in case value is less than 0 or no value provided + * @param {any} value + */ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const requireValue = (value: any): string => { + let error; + if (value === "") { + error = i18next.t("Required"); + } else if (value < 0) { + error = i18next.t("Validate Non-Negative"); + } + return error; +}; + +/** + * Given ABI method param type (address, byets32, unit256, ...) and it's value, returns error message in case validation fails or no value provided + * @param {string} type + * @param {string} value + */ +export const validateParam = (type: string, value: string): string => { + let error; + if (!value) { + error = i18next.t("Required"); + } + else { + switch (true) { + case type.includes("address"): + if (!isAddress(value)) { + error = i18next.t("Validate Address"); + } + break; + case type.includes("byte"): + if (!isHexString(value)) { + error = i18next.t("Validate HEX"); + } + break; + case type.includes("uint"): + if (/^\d+$/.test(value) === false) { + error = i18next.t("Validate Digits"); + } + break; + } + } + return error; +}; diff --git a/src/components/Shared/SelectSearch.scss b/src/components/Shared/SelectSearch.scss deleted file mode 100644 index 344a7edc..00000000 --- a/src/components/Shared/SelectSearch.scss +++ /dev/null @@ -1,57 +0,0 @@ -.selectSearchWrapper { - position: relative; - margin: 20px 0px; - cursor: pointer; - .dropdownSelection { - .arrow { - width: 15px; - height: 15px; - background: url("../../assets/images/Icon/sort-order-arrow.svg") no-repeat right; - transform: rotate(90deg); - position: absolute; - right: 20px; - top: 13px; - } - .arrow:hover { - opacity: 0.7; - } - } - - .dropdownOpen { - display: flex; - flex-direction: column; - top: 60px; - position: absolute; - width: 100%; - z-index: 2; - background-color: $white; - box-shadow: 0px 3px 8px $gray-3; - margin-bottom: 40px; - .search { - position: sticky; - width: 95%; - align-self: center; - margin-top: 15px; - } - .elementsWrapper { - display: flex; - flex-direction: column; - max-height: 100px; - overflow-y: auto; - background-color: $white; - padding: 5px; - overflow-y: scroll; - .element { - padding: 10px; - } - .element:hover { - background-color: $border-accent; - cursor: pointer; - } - .noResults { - align-self: center; - color: $gray-1; - } - } - } -} diff --git a/src/components/Shared/SelectSearch.tsx b/src/components/Shared/SelectSearch.tsx deleted file mode 100644 index d76bf09e..00000000 --- a/src/components/Shared/SelectSearch.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from "react"; -import i18next from "i18next"; -import { ErrorMessage, Field } from "formik"; -import * as css from "./SelectSearch.scss"; - -/** - * Generic Select with Search for formik forms. - * - * data - Objects array - * nameOnList - the property name which will be displayd on the select list - * label - select input label name - * name - 'name' attribute in the parent form - * required (true / false) - * value - parent form value of this filed - * onChange - function to trigger on select. Also returns the whole selected element. - * placeholder [optional] - * errors - errors attribute of the parent form - * cssForm - css styles of the parent form - * touched - touched attribute of the parent form - */ - -interface IProps { - data: Array - label: string - name: string - required: boolean - value: string - onChange: any - placeholder?: string - errors: any - cssForm: any - touched: any - nameOnList: string -} - -export const SelectSearch = (props: IProps): React.ReactElement => { - const [toggle, setToggle] = React.useState(false); - const [search, setSearch] = React.useState(""); - const { data, label, required, onChange, name, placeholder, errors, cssForm, touched, nameOnList, value } = props; - - const handleSelect = (element: any) => { - setToggle(false); - setSearch(""); - onChange(element); - }; - - const elements: any = []; - -data?.forEach((element: any, index: number) => { - if (element.name.toLowerCase().includes(search.toLowerCase())){ - // eslint-disable-next-line react/jsx-no-bind - elements.push(
handleSelect(element)}>{element[nameOnList]}
); - } -}); - -return ( -
- {/* eslint-disable-next-line react/jsx-no-bind */} - -); -};