diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f4d54fd..bbc9ccd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,5 +4,5 @@ We welcome contributions to the FireFly Project in many forms, and there's always plenty to do! Please visit the -[contributors guide](https://hyperledger.github.io/firefly/contributors/contributors.html) in the +[contributors guide](https://hyperledger.github.io/firefly/contributors/) in the docs to learn how to make contributions to this exciting project. diff --git a/README.md b/README.md index 6af58d4..e3afe28 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ npm install npm start ``` +Note: If your Firefly instance is not exposed on port 5000 locally or running remotely, set the env FF_ENDPOINT to the location of your instance. + ## Git repositories There are multiple Git repos making up the Hyperledger FireFly project. Some others @@ -54,4 +56,4 @@ Please adhere to this project's [Code of Conduct](CODE_OF_CONDUCT.md). ## License -Hyperledger Project source code files are made available under the Apache License, Version 2.0 (Apache-2.0), located in the [LICENSE](LICENSE) file. +Hyperledger Project source code files are made available under the Apache License, Version 2.0 (Apache-2.0), located in the [LICENSE](LICENSE) file. \ No newline at end of file diff --git a/ui/src/components/Forms/Contracts/RegisterContractApiForm.tsx b/ui/src/components/Forms/Contracts/RegisterContractApiForm.tsx index 952fb83..6670030 100644 --- a/ui/src/components/Forms/Contracts/RegisterContractApiForm.tsx +++ b/ui/src/components/Forms/Contracts/RegisterContractApiForm.tsx @@ -19,6 +19,7 @@ import { BLOCKCHAIN_TYPE } from '../../../enums/enums'; import { IContractInterface } from '../../../interfaces/api'; import { DEFAULT_SPACING } from '../../../theme'; import { fetchCatcher } from '../../../utils/fetches'; +import { isValidFFName, isValidAddress } from '../../../utils/regex'; export const RegisterContractApiForm: React.FC = () => { const { blockchainPlugin, setJsonPayload, setPayloadMissingFields } = @@ -32,9 +33,11 @@ export const RegisterContractApiForm: React.FC = () => { >([]); const [contractInterfaceIdx, setContractInterfaceIdx] = useState(0); const [name, setName] = useState(''); + const [nameError, setNameError] = useState(''); const [chaincode, setChaincode] = useState(''); const [channel, setChannel] = useState(''); const [contractAddress, setContractAddress] = useState(''); + const [contractAddressError, setContractAddressError] = useState(''); const [isMounted, setIsMounted] = useState(false); useEffect(() => { @@ -133,7 +136,23 @@ export const RegisterContractApiForm: React.FC = () => { fullWidth required label={t('name')} - onChange={(e) => setName(e.target.value)} + onChange={(event: React.ChangeEvent) => { + const newValue = event.target.value; + if (newValue === '') { + setNameError(t('')); + } else if (!isValidFFName(newValue)) { + setNameError( + t('form.validation.invalidFFField', { + field: t('form.contracts.apiEndpointName'), + }) + ); + } else { + setNameError(''); + } + setName(newValue); + }} + error={!!nameError} + helperText={nameError} /> @@ -144,7 +163,21 @@ export const RegisterContractApiForm: React.FC = () => { fullWidth required label={t('address')} - onChange={(e) => setContractAddress(e.target.value)} + onChange={(event: React.ChangeEvent) => { + const newValue = event.target.value; + if (newValue === '') { + setContractAddressError(t('')); + } else if (!isValidAddress(newValue)) { + setContractAddressError( + t('form.validation.invalidAddress') + ); + } else { + setContractAddressError(''); + } + setContractAddress(newValue); + }} + error={!!contractAddressError} + helperText={contractAddressError} /> {t('contractAddressHelperText')} diff --git a/ui/src/translations/en.json b/ui/src/translations/en.json index 48334ef..8726050 100644 --- a/ui/src/translations/en.json +++ b/ui/src/translations/en.json @@ -92,6 +92,15 @@ "firstEventDescription": "The first event for this listener to index. Valid options are 'newest', 'oldest', or a specific block number.", "followStepsInInstructions": "Follow steps outlined in the instructions", "format": "format", + "form": { + "validation": { + "invalidFFField": "{{field}} must consist of alphanumeric characters, '-', '_', or '.', must start and end with an alphanumeric character, and be no more than 64 characters.", + "invalidAddress": "Not a valid ethereum address" + }, + "contracts": { + "apiEndpointName": "API endpoint name" + } + }, "fromAddress": "From Address", "fungible": "Fungible", "github": "Github", diff --git a/ui/src/utils/regex.ts b/ui/src/utils/regex.ts new file mode 100644 index 0000000..4698d4b --- /dev/null +++ b/ui/src/utils/regex.ts @@ -0,0 +1,15 @@ +const ffNameRegex = /^[0-9a-zA-Z]([0-9a-zA-Z._-]{0,62}[0-9a-zA-Z])?$/; +const addressRegex = /^0x[a-fA-F0-9]{40}$/; + +export const isValidFFName = (name: string) => { + // have to use a variable to store the return value before return it + // otherwise you'll see "jumpy" validation result when you use the function + // directly as the value of a react component + const result = ffNameRegex.test(name); + return result; +}; + +export const isValidAddress = (address: string) => { + const result = addressRegex.test(address); + return result; +};