fix: allow form label and name to differ
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@ -12,7 +12,10 @@ import {
|
|||||||
const propTypes = {
|
const propTypes = {
|
||||||
buttonText: PropTypes.string,
|
buttonText: PropTypes.string,
|
||||||
enableSubmit: PropTypes.bool,
|
enableSubmit: PropTypes.bool,
|
||||||
formFields: PropTypes.arrayOf(PropTypes.string).isRequired,
|
formFields: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({ name: PropTypes.string, label: PropTypes.string })
|
||||||
|
.isRequired
|
||||||
|
).isRequired,
|
||||||
hideButton: PropTypes.bool,
|
hideButton: PropTypes.bool,
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
initialValues: PropTypes.object,
|
initialValues: PropTypes.object,
|
||||||
@ -47,7 +50,7 @@ function DynamicForm({
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
<FormFields fields={formFields} options={options} />
|
<FormFields formFields={formFields} options={options} />
|
||||||
<BlockSaveWrapper>
|
<BlockSaveWrapper>
|
||||||
{hideButton ? null : (
|
{hideButton ? null : (
|
||||||
<BlockSaveButton disabled={(pristine && !enableSubmit) || error}>
|
<BlockSaveButton disabled={(pristine && !enableSubmit) || error}>
|
||||||
|
@ -8,7 +8,10 @@ import Form from './Form';
|
|||||||
|
|
||||||
const defaultTestProps = {
|
const defaultTestProps = {
|
||||||
buttonText: 'Submit',
|
buttonText: 'Submit',
|
||||||
formFields: ['name', 'website'],
|
formFields: [
|
||||||
|
{ name: 'name', label: 'name Label' },
|
||||||
|
{ name: 'website', label: 'WebSite label' }
|
||||||
|
],
|
||||||
id: 'my-test-form',
|
id: 'my-test-form',
|
||||||
options: {
|
options: {
|
||||||
types: {
|
types: {
|
||||||
@ -23,11 +26,11 @@ const defaultTestProps = {
|
|||||||
test('should render', () => {
|
test('should render', () => {
|
||||||
const { getByLabelText, getByText } = render(<Form {...defaultTestProps} />);
|
const { getByLabelText, getByText } = render(<Form {...defaultTestProps} />);
|
||||||
|
|
||||||
const nameInput = getByLabelText(/name/i);
|
const nameInput = getByLabelText(/name Label/);
|
||||||
expect(nameInput).not.toBeRequired();
|
expect(nameInput).not.toBeRequired();
|
||||||
expect(nameInput).toHaveAttribute('type', 'text');
|
expect(nameInput).toHaveAttribute('type', 'text');
|
||||||
|
|
||||||
const websiteInput = getByLabelText(/website/i);
|
const websiteInput = getByLabelText(/WebSite label/);
|
||||||
expect(websiteInput).toBeRequired();
|
expect(websiteInput).toBeRequired();
|
||||||
expect(websiteInput).toHaveAttribute('type', 'url');
|
expect(websiteInput).toHaveAttribute('type', 'url');
|
||||||
|
|
||||||
@ -48,10 +51,10 @@ test('should render with default values', () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const nameInput = getByLabelText(/name/i);
|
const nameInput = getByLabelText(/name Label/);
|
||||||
expect(nameInput).toHaveValue(nameValue);
|
expect(nameInput).toHaveValue(nameValue);
|
||||||
|
|
||||||
const websiteInput = getByLabelText(/website/i);
|
const websiteInput = getByLabelText(/WebSite label/);
|
||||||
expect(websiteInput).toHaveValue(websiteValue);
|
expect(websiteInput).toHaveValue(websiteValue);
|
||||||
|
|
||||||
const button = getByText(/submit/i);
|
const button = getByText(/submit/i);
|
||||||
@ -68,7 +71,7 @@ test('should submit', () => {
|
|||||||
|
|
||||||
const { getByLabelText, getByText } = render(<Form {...props} />);
|
const { getByLabelText, getByText } = render(<Form {...props} />);
|
||||||
|
|
||||||
const websiteInput = getByLabelText(/website/i);
|
const websiteInput = getByLabelText(/WebSite label/);
|
||||||
fireEvent.change(websiteInput, { target: { value: websiteValue } });
|
fireEvent.change(websiteInput, { target: { value: websiteValue } });
|
||||||
expect(websiteInput).toHaveValue(websiteValue);
|
expect(websiteInput).toHaveValue(websiteValue);
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { kebabCase, startCase } from 'lodash';
|
import { kebabCase } from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
@ -12,7 +12,10 @@ import {
|
|||||||
import { Field } from 'react-final-form';
|
import { Field } from 'react-final-form';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
fields: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
|
formFields: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({ name: PropTypes.string, label: PropTypes.string })
|
||||||
|
.isRequired
|
||||||
|
).isRequired,
|
||||||
options: PropTypes.shape({
|
options: PropTypes.shape({
|
||||||
ignored: PropTypes.arrayOf(PropTypes.string),
|
ignored: PropTypes.arrayOf(PropTypes.string),
|
||||||
placeholders: PropTypes.objectOf(PropTypes.string),
|
placeholders: PropTypes.objectOf(PropTypes.string),
|
||||||
@ -22,19 +25,20 @@ const propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function FormFields(props) {
|
function FormFields(props) {
|
||||||
const { fields, options = {} } = props;
|
const { formFields, options = {} } = props;
|
||||||
const {
|
const {
|
||||||
ignored = [],
|
ignored = [],
|
||||||
placeholders = {},
|
placeholders = {},
|
||||||
required = [],
|
required = [],
|
||||||
types = {}
|
types = {}
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{fields
|
{formFields
|
||||||
.filter(field => !ignored.includes(field))
|
.filter(formField => !ignored.includes(formField.name))
|
||||||
.map(name => (
|
.map(({ name, label }) => (
|
||||||
<Field key={`${name}-field`} name={name}>
|
<Field key={`${kebabCase(name)}-field`} name={name}>
|
||||||
{({ input: { value, onChange }, meta: { pristine, error } }) => {
|
{({ input: { value, onChange }, meta: { pristine, error } }) => {
|
||||||
const key = kebabCase(name);
|
const key = kebabCase(name);
|
||||||
const type = name in types ? types[name] : 'text';
|
const type = name in types ? types[name] : 'text';
|
||||||
@ -44,9 +48,7 @@ function FormFields(props) {
|
|||||||
<Col key={key} xs={12}>
|
<Col key={key} xs={12}>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
{type === 'hidden' ? null : (
|
{type === 'hidden' ? null : (
|
||||||
<ControlLabel htmlFor={key}>
|
<ControlLabel htmlFor={key}>{label}</ControlLabel>
|
||||||
{startCase(name)}
|
|
||||||
</ControlLabel>
|
|
||||||
)}
|
)}
|
||||||
<FormControl
|
<FormControl
|
||||||
componentClass={type === 'textarea' ? type : 'input'}
|
componentClass={type === 'textarea' ? type : 'input'}
|
||||||
|
@ -9,7 +9,6 @@ export { default as FormFields } from './FormFields.js';
|
|||||||
const normalizeOptions = {
|
const normalizeOptions = {
|
||||||
stripWWW: false
|
stripWWW: false
|
||||||
};
|
};
|
||||||
|
|
||||||
// callIfDefined(fn: (Any) => Any) => (value: Any) => Any
|
// callIfDefined(fn: (Any) => Any) => (value: Any) => Any
|
||||||
export function callIfDefined(fn) {
|
export function callIfDefined(fn) {
|
||||||
return value => (value ? fn(value) : value);
|
return value => (value ? fn(value) : value);
|
||||||
@ -24,7 +23,6 @@ export function formatUrlValues(values, options) {
|
|||||||
return { ...result, [key]: value };
|
return { ...result, [key]: value };
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatUrl(url: String) => String
|
// formatUrl(url: String) => String
|
||||||
export function formatUrl(url) {
|
export function formatUrl(url) {
|
||||||
if (typeof url === 'string' && url.length > 4 && url.indexOf('.') !== -1) {
|
if (typeof url === 'string' && url.length > 4 && url.indexOf('.') !== -1) {
|
||||||
@ -41,21 +39,17 @@ export function formatUrl(url) {
|
|||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidURL(data) {
|
export function isValidURL(data) {
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
return isURL(data, { require_protocol: true });
|
return isURL(data, { require_protocol: true });
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeOptional(validator) {
|
export function makeOptional(validator) {
|
||||||
return val => (val ? validator(val) : true);
|
return val => (val ? validator(val) : true);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeRequired(validator) {
|
export function makeRequired(validator) {
|
||||||
return val => (val ? validator(val) : false);
|
return val => (val ? validator(val) : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createFormValidator(fieldValidators) {
|
export function createFormValidator(fieldValidators) {
|
||||||
const fieldKeys = Object.keys(fieldValidators);
|
const fieldKeys = Object.keys(fieldValidators);
|
||||||
return values =>
|
return values =>
|
||||||
@ -69,7 +63,6 @@ export function createFormValidator(fieldValidators) {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.reduce((errors, error) => ({ ...errors, ...error }), {});
|
.reduce((errors, error) => ({ ...errors, ...error }), {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getValidationState(field) {
|
export function getValidationState(field) {
|
||||||
if (field.pristine) {
|
if (field.pristine) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -415,6 +415,11 @@ export class CertificationSettings extends Component {
|
|||||||
{ types: {} }
|
{ types: {} }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const formFields = challengeTitles.map(title => ({
|
||||||
|
name: title,
|
||||||
|
label: title
|
||||||
|
}));
|
||||||
|
|
||||||
const fullForm = filledforms === challengeTitles.length;
|
const fullForm = filledforms === challengeTitles.length;
|
||||||
|
|
||||||
const createClickHandler = certLocation => e => {
|
const createClickHandler = certLocation => e => {
|
||||||
@ -436,7 +441,7 @@ export class CertificationSettings extends Component {
|
|||||||
<Form
|
<Form
|
||||||
buttonText={fullForm ? 'Claim Certification' : 'Save Progress'}
|
buttonText={fullForm ? 'Claim Certification' : 'Save Progress'}
|
||||||
enableSubmit={fullForm}
|
enableSubmit={fullForm}
|
||||||
formFields={challengeTitles}
|
formFields={formFields}
|
||||||
hideButton={isCertClaimed}
|
hideButton={isCertClaimed}
|
||||||
id={superBlock}
|
id={superBlock}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
|
@ -18,8 +18,11 @@ const propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// back end challenges and front end projects use a single form field
|
// back end challenges and front end projects use a single form field
|
||||||
const solutionField = ['solution'];
|
const solutionField = [{ name: 'solution', label: 'Solution Link' }];
|
||||||
const backEndProjectFields = ['solution', 'githubLink'];
|
const backEndProjectFields = [
|
||||||
|
{ name: 'solution', label: 'Solution Link' },
|
||||||
|
{ name: 'githubLink', label: 'GitHub Link' }
|
||||||
|
];
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
types: {
|
types: {
|
||||||
|
Reference in New Issue
Block a user