Refactor form to allow dynamic children

This commit is contained in:
Caleb Okpara 2022-05-19 20:41:02 +00:00
parent 02abfb53fa
commit deed690345
10 changed files with 4232 additions and 1845 deletions

View File

@ -112,9 +112,7 @@
"formik": "^2.2.9", "formik": "^2.2.9",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^18.1.0", "react": "^18.1.0",
"react-dom": "^18.1.0", "react-scripts": "5.0.1",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"redux": "^4.2.0", "redux": "^4.2.0",
"universal-cookie": "^4.0.4", "universal-cookie": "^4.0.4",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",
@ -18439,9 +18437,7 @@
"formik": "^2.2.9", "formik": "^2.2.9",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^18.1.0", "react": "^18.1.0",
"react-dom": "^18.1.0", "react-scripts": "5.0.1",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"redux": "^4.2.0", "redux": "^4.2.0",
"universal-cookie": "^4.0.4", "universal-cookie": "^4.0.4",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",

View File

@ -61,17 +61,10 @@ body {
.brand { .brand {
position: absolute; position: absolute;
top: 5%; top: -5%;
left: 50%; left: 50%;
transform: translateX(-55%); transform: translateX(-50%);
margin-bottom: 20px;
@include responsive($xs) {
top: 6%;
}
@include responsive($lg) {
top: 5%;
}
img { img {
width: 200px; width: 200px;
@ -137,6 +130,19 @@ form {
font-weight: 400; font-weight: 400;
color: lighten($bgDark, 20%); color: lighten($bgDark, 20%);
margin-bottom: 5px; margin-bottom: 5px;
display: flex;
justify-content: space-between;
span:last-child {
color: $lightBlue;
cursor: pointer;
transition: $transition;
&:hover {
color: darken($lightBlue, 50%);
}
}
} }
input { input {
@ -248,6 +254,7 @@ form {
span { span {
color: lighten($bgDark, 20%); color: lighten($bgDark, 20%);
cursor: default;
} }
&:hover { &:hover {
@ -282,7 +289,7 @@ form {
cursor: pointer; cursor: pointer;
@include responsive($xs) { @include responsive($xs) {
padding-bottom: 15px; padding-bottom: 5px;
} }
a { a {

View File

@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
import BasicModelForm from 'CommonUI/src/Components/Forms/BasicModelForm'; import BasicModelForm from 'CommonUI/src/Components/Forms/BasicModelForm';
import User from 'Common/Models/User'; import User from 'Common/Models/User';
import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues'; import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues';
import Route from 'Common/Types/API/Route';
const LoginPage: FunctionComponent = () => { const LoginPage: FunctionComponent = () => {
const user: User = new User(); const user: User = new User();
@ -24,6 +25,11 @@ const LoginPage: FunctionComponent = () => {
password: true, password: true,
}, },
title: 'Password', title: 'Password',
sideLink: {
text: 'Forgot password?',
url: new Route('/forgot-password'),
openLinkInNewTab: true,
},
}, },
]} ]}
onSubmit={(values: FormValues<User>) => { onSubmit={(values: FormValues<User>) => {
@ -31,7 +37,22 @@ const LoginPage: FunctionComponent = () => {
}} }}
submitButtonText={'Login'} submitButtonText={'Login'}
title={'Sign in to your account'} title={'Sign in to your account'}
/> >
<div className="actions">
<p>
<Link to="/forgot-password">Forgot your password?</Link>
</p>
<p>
<Link to="/login/sso">
Use single sign-on (SSO) instead
</Link>
</p>
<p>
<span>Don&apos;t have an account? </span>{' '}
<Link to="/register">Sign up</Link>
</p>
</div>
</BasicModelForm>
<div className="footer"> <div className="footer">
<p> <p>

File diff suppressed because it is too large Load Diff

View File

@ -18,15 +18,12 @@
"Common": "file:../Common", "Common": "file:../Common",
"formik": "^2.2.9", "formik": "^2.2.9",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^18.1.0",
"react-scripts": "5.0.1",
"redux": "^4.2.0", "redux": "^4.2.0",
"universal-cookie": "^4.0.4", "universal-cookie": "^4.0.4",
"web-vitals": "^2.1.4", "web-vitals": "^2.1.4",
"yup": "^0.32.11", "yup": "^0.32.11"
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^17.0.23", "@types/node": "^17.0.23",

View File

@ -7,7 +7,6 @@ import Fields from './Types/Fields';
import DataField from './Types/Field'; import DataField from './Types/Field';
import ButtonTypes from '../Basic/Button/ButtonTypes'; import ButtonTypes from '../Basic/Button/ButtonTypes';
import BadDataException from 'Common/Types/Exception/BadDataException'; import BadDataException from 'Common/Types/Exception/BadDataException';
export interface ComponentProps<T extends Object> { export interface ComponentProps<T extends Object> {
id: string; id: string;
initialValues: FormValues<T>; initialValues: FormValues<T>;
@ -18,19 +17,36 @@ export interface ComponentProps<T extends Object> {
model: T; model: T;
submitButtonText?: string; submitButtonText?: string;
title?: string; title?: string;
children: ReactElement;
} }
const BasicForm = <T extends Object>( const BasicForm = <T extends Object>(
props: ComponentProps<T> props: ComponentProps<T>
): ReactElement => { ): ReactElement => {
const getFormField = (field: DataField<T>, index: number): ReactElement => { const getFormField = (field: DataField<T>, index: number): ReactElement => {
let fieldType = 'text'; const fieldType = 'text';
if (Object.keys(field.field).length === 0) { if (Object.keys(field.field).length === 0) {
throw new BadDataException('Object cannot be without Field'); throw new BadDataException('Object cannot be without Field');
} }
return ( return (
<div key={index}> <div key={index}>
<label>{field.title}</label> <label>
<span>{field.title}</span>
{
<span>
<a
href={field.sideLink?.url.toString()}
target={`${
field.sideLink?.openLinkInNewTab
? '_blank'
: '_self'
}`}
>
{field.sideLink?.text}
</a>
</span>
}
</label>
<p>{field.description}</p> <p>{field.description}</p>
<Field <Field
placeholder={field.placeholder} placeholder={field.placeholder}
@ -60,35 +76,24 @@ const BasicForm = <T extends Object>(
props.onSubmit(values); props.onSubmit(values);
}} }}
> >
{({ isSubmitting }) => ( {({ isSubmitting }) => {
<Form> return (
<h1>{props.title}</h1> <Form>
{props.fields && <h1>{props.title}</h1>
props.fields.map((field: DataField<T>, i) => { {props.fields &&
return getFormField(field, i); props.fields.map((field: DataField<T>, i) => {
})} return getFormField(field, i);
<div className="remember"> })}
<input type="checkbox" id="remember" /> <Button
<label htmlFor="remember"> title={props.submitButtonText || 'Submit'}
Stay signed in for a week disabled={isSubmitting}
</label> type={ButtonTypes.Submit}
</div> id={`${props.id}-submit-button`}
<Button />
title={props.submitButtonText || 'Submit'} {props.children}
disabled={isSubmitting} </Form>
type={ButtonTypes.Submit} );
id={`${props.id}-submit-button`} }}
/>
<div className="actions">
<p>Forgot your password?</p>
<p>Use single sign-on (SSO) instead</p>
<p>
<span>Don&apos;t have an account? </span> Sign
up
</p>
</div>
</Form>
)}
</Formik> </Formik>
</div> </div>
); );

View File

@ -13,6 +13,7 @@ export interface ComponentProps<T extends BaseModel> {
fields: Fields<T>; fields: Fields<T>;
submitButtonText?: string; submitButtonText?: string;
title?: string; title?: string;
children: ReactElement;
} }
const BasicModelForm = <TBaseModel extends BaseModel>( const BasicModelForm = <TBaseModel extends BaseModel>(
@ -58,7 +59,9 @@ const BasicModelForm = <TBaseModel extends BaseModel>(
model={props.model} model={props.model}
submitButtonText={props.submitButtonText || 'Save'} submitButtonText={props.submitButtonText || 'Save'}
title={props.title || ''} title={props.title || ''}
/> >
{props.children}
</BasicForm>
); );
}; };

View File

@ -1,3 +1,5 @@
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import SelectFormFields from './SelectFormField'; import SelectFormFields from './SelectFormField';
export default interface Field<TEntity> { export default interface Field<TEntity> {
@ -5,4 +7,9 @@ export default interface Field<TEntity> {
description?: string; description?: string;
field: SelectFormFields<TEntity>; field: SelectFormFields<TEntity>;
placeholder?: string; placeholder?: string;
sideLink?: {
text: string;
url: Route | URL;
openLinkInNewTab?: boolean;
};
} }

View File

@ -1,4 +1,6 @@
import Hostname from 'Common/Types/API/Hostname'; import Hostname from 'Common/Types/API/Hostname';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import Email from 'Common/Types/Email'; import Email from 'Common/Types/Email';
import Name from 'Common/Types/Name'; import Name from 'Common/Types/Name';
import ObjectID from 'Common/Types/ObjectID'; import ObjectID from 'Common/Types/ObjectID';
@ -10,6 +12,8 @@ type FormFieldType =
| ObjectID | ObjectID
| Hostname | Hostname
| Email | Email
| Name; | Name
| Route
| URL;
export default FormFieldType; export default FormFieldType;

View File

@ -3,6 +3,7 @@ enum FormType {
Name, Name,
Hostname, Hostname,
URL, URL,
Route,
String, String,
Number, Number,
Password, Password,