Unverified Commit d4800f96 authored by Zhou (Link)  Fang's avatar Zhou (Link) Fang Committed by GitHub
Browse files

Replaced input components (#70)

parent 73c5f451
......@@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@material-ui/core": "^4.11.4",
"@material-ui/lab": "^4.0.0-alpha.58",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
......@@ -18,6 +19,7 @@
"react": "^17.0.0",
"react-app-polyfill": "^2.0.0",
"react-dom": "^17.0.0",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"swagger-repo": "^2.0.0-rc.15",
"typescript": "^4.3.2",
......
......@@ -10,6 +10,9 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
html {
font-size: 14px;
}
.eclipseFdn-membership-webform {
/* Stepper */
}
......@@ -102,6 +105,20 @@
position: relative;
padding-top: 30px;
}
.eclipseFdn-membership-webform .step a {
text-decoration: none;
color: inherit;
}
.eclipseFdn-membership-webform .step a:visited {
color: inherit;
}
.eclipseFdn-membership-webform .btn a {
text-decoration: none;
color: inherit;
}
.eclipseFdn-membership-webform .btn a:visited {
color: inherit;
}
.eclipseFdn-membership-webform .step-span {
width: 50px;
height: 50px;
......@@ -133,6 +150,13 @@
background: #404040 50% no-repeat;
color: #fff;
}
.eclipseFdn-membership-webform .button-container a {
text-decoration: none;
}
.eclipseFdn-membership-webform .button-container button {
width: 80px;
margin: 0 14px;
}
@media (min-width: 992px) {
.eclipseFdn-membership-webform .step {
width: calc(100% / 6);
......
import React, { useState } from 'react';
import { useState } from 'react';
import './App.css';
import AppFooter from './components/UIComponents/layout/AppFooter';
import AppHeader from './components/UIComponents/layout/AppHeader';
import FormWrapper from './components/UIComponents/FormPreprocess/FormWrapper';
import MembershipContext from './Context/MembershipContext';
import { createMuiTheme, ThemeProvider } from '@material-ui/core';
import { BrowserRouter as Router } from 'react-router-dom';
import Main from './components/Pages/Main';
const theme = createMuiTheme({
palette: {
primary: {
main: '#f7941e',
contrastText: '#fff', // for button text color
},
},
});
......@@ -17,12 +19,18 @@ const theme = createMuiTheme({
const App = () => {
const [currentUser, setCurrentUser] = useState(null);
const [currentFormId, setCurrentFormId] = useState('');
const [furthestPage, setFurthestPage] = useState({
index: 0,
pathName: '/signIn',
});
const membershipContextValue = {
currentUser,
setCurrentUser: (val) => setCurrentUser(val),
currentFormId,
setCurrentFormId: (val) => setCurrentFormId(val),
furthestPage,
setFurthestPage,
};
return (
......@@ -30,7 +38,12 @@ const App = () => {
<ThemeProvider theme={theme}>
<AppHeader />
<MembershipContext.Provider value={membershipContextValue}>
<FormWrapper />
<Router>
<Main
furthestPage={furthestPage}
setFurthestPage={setFurthestPage}
/>
</Router>
</MembershipContext.Provider>
<AppFooter />
</ThemeProvider>
......
......@@ -29,7 +29,6 @@ export const FETCH_HEADER = {
};
export const membership_levels = [
{ label: 'Select a level', value: '' },
{ label: 'Strategic Member', value: 'strategic' },
{
label: 'Contributing Member (formerly referred to as Solutions Members)',
......@@ -38,12 +37,12 @@ export const membership_levels = [
{ label: 'Associate Member', value: 'associate' },
];
export const fakeChildrenArray = [
{ props: { label: COMPANY_INFORMATION } },
{ props: { label: MEMBERSHIP_LEVEL } },
{ props: { label: WORKING_GROUPS } },
{ props: { label: SIGNING_AUTHORITY } },
{ props: { label: REVIEW } },
export const PAGE_STEP = [
{ props: { label: COMPANY_INFORMATION, pathName: '/company-info' } },
{ props: { label: MEMBERSHIP_LEVEL, pathName: '/membership-level' } },
{ props: { label: WORKING_GROUPS, pathName: '/working-groups' } },
{ props: { label: SIGNING_AUTHORITY, pathName: '/signing-authority' } },
{ props: { label: REVIEW, pathName: '/review' } },
];
export const contact_type = {
......
......@@ -8,11 +8,14 @@ import React from 'react';
* It is simliar to state, but you can export and import anywhere,
* no need to pass all the way down to the child component
*/
const MembershipContext = React.createContext({
currentUser: {},
setCurrentUser: () => {},
currentFormId: '',
setCurrentFormId: () => {},
furthestPage: '',
setFurthestPage: '',
});
export default MembershipContext;
......@@ -72,23 +72,17 @@ export function matchCompanyFields(existingOrganizationData) {
return {
// Step1: company Info
id: existingOrganizationData?.id || '',
legalName:
{
value: existingOrganizationData?.legal_name || '',
label: existingOrganizationData?.legal_name || '',
address: existingOrganizationData?.address || '',
twitterHandle: existingOrganizationData?.twitter_handle || '',
} || '',
legalName: existingOrganizationData?.legal_name || '',
address: {
id: existingOrganizationData?.address.id || '',
street: existingOrganizationData?.address.street || '',
city: existingOrganizationData?.address.city || '',
provinceOrState: existingOrganizationData?.address.province_state || '',
country:
{
label: existingOrganizationData?.address.country,
value: existingOrganizationData?.address.country,
} || '',
country: {
label: existingOrganizationData?.address.country || '',
value: existingOrganizationData?.address.country || '',
},
'country-label': existingOrganizationData?.address.country || '',
postalCode: existingOrganizationData?.address.postal_code || '',
},
twitterHandle: existingOrganizationData?.twitter_handle || '',
......
......@@ -4,8 +4,8 @@ import {
matchCompanyFields,
matchContactFields,
} from '../../../Utils/formFunctionHelpers';
import Company from './CompanyInformationCompany';
import Contacts from './CompanyInformationContacts';
import CompanyInformationCompany from './CompanyInformationCompany';
import CompanyInformationContacts from './CompanyInformationContacts';
import Loading from '../../UIComponents/Loading/Loading';
import {
end_point,
......@@ -16,6 +16,8 @@ import {
MODE_REACT_ONLY,
MODE_REACT_API,
} from '../../../Constants/Constants';
import CustomStepButton from '../../UIComponents/Button/CustomStepButton';
import { initialValues } from '../../UIComponents/FormComponents/formFieldModel';
/**
* Wrapper for Contacts and Company components
......@@ -24,25 +26,24 @@ import {
* with fetch and prefill data operation.
*
* Props:
* - otherProps: any other props passing down from MultiStepForm and
* - otherProps: any other props passing down from
* FormikStepper components, including formik props of formik
* library (such as "formik.values", "formik.setFieldValue");
* - formField: the form field in formModels/formFieldModel.js
*/
const CompanyInformation = ({ formField, ...otherProps }) => {
const CompanyInformation = ({ formik }) => {
const { currentFormId } = useContext(MembershipContext); // current chosen form id
const formValues = otherProps.parentState.formik.values; // current form values
const { setFieldValue } = otherProps.parentState.formik;
const [loading, setLoading] = useState(true);
// Fetch data only once and prefill data,
// as long as currentFormId and setFieldValue
// Function does not change, will not cause re-render again
useEffect(() => {
const detectModeAndFetch = () => {
// Once we have API set up ready, we don't need the
// fake data anymore, and can remove these pre-process.
// it is mainly for if running the application
// only react without server.
// just for React only testing.
// let currentFormId = 'form_1';
let url_prefix_local;
let url_suffix_local = '';
// If running on localhost:3000
......@@ -50,14 +51,12 @@ const CompanyInformation = ({ formField, ...otherProps }) => {
url_prefix_local = 'membership_data'; // --> public/membership_data/
url_suffix_local = '.json'; // --> it is the fake json file
}
// If running on localhost:8090 or any other not on localhost:3000
// Once we have the API ready running on production,
// will use the correct domain name rather than localhost:8090
if (getCurrentMode() === MODE_REACT_API) {
url_prefix_local = api_prefix_form;
}
// If the current form exsits, and it is not creating a new form
if (currentFormId && currentFormId !== newForm_tempId) {
// Using promise pool, because in first step,
......@@ -78,7 +77,6 @@ const CompanyInformation = ({ formField, ...otherProps }) => {
{ headers: FETCH_HEADER }
),
];
Promise.all(pool)
.then((res) => Promise.all(res.map((r) => r.json())))
.then(([organizations, contacts]) => {
......@@ -94,7 +92,8 @@ const CompanyInformation = ({ formField, ...otherProps }) => {
// organization field with the mapped data,
// if nested, it will automatically map the
// properties and values
setFieldValue('organization', tempOrg);
console.log(tempOrg);
formik.setFieldValue('organization', tempOrg);
}
if (contacts.length) {
// Call the the function to map the retrived contacts
......@@ -104,14 +103,27 @@ const CompanyInformation = ({ formField, ...otherProps }) => {
// Prefill Data --> Call the setFieldValue of Formik,
// to set representative field with the mapped data,
// if nested, it will automatically map the properties and values
setFieldValue('representative', tempContacts);
formik.setFieldValue('representative', tempContacts);
}
setLoading(false);
});
} else if (currentFormId === newForm_tempId) {
formik.setFieldValue('representative', initialValues.representative);
formik.setFieldValue('organization', initialValues.organization);
setLoading(false);
} else {
setLoading(false);
}
}, [currentFormId, setFieldValue]);
};
// Fetch data only once and prefill data,
// as long as currentFormId and setFieldValue
// Function does not change, will not cause re-render again
useEffect(() => {
setLoading(true);
detectModeAndFetch();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentFormId]);
// If it is in loading status,
// only return a loading spinning
......@@ -120,17 +132,23 @@ const CompanyInformation = ({ formField, ...otherProps }) => {
}
return (
<>
<form onSubmit={formik.handleSubmit}>
<h1 className="fw-600 h2">Company Information</h1>
<p>
Please complete your company information below. This should be the legal
name and address of your organization.
</p>
<div className="align-center">
<Company />
<Contacts formValues={formValues} formField={formField} />
<CompanyInformationCompany formik={formik} />
<CompanyInformationContacts formik={formik} />
</div>
</>
<CustomStepButton
previousPage=""
nextPage="/membership-level"
pageIndex={1}
/>
</form>
);
};
......
import React from 'react';
import CustomSelectWrapper from '../../UIComponents/Inputs/CustomSelect/CustomSelectWrapper';
import DefaultSelect from '../../UIComponents/Inputs/CustomSelect/DefaultSelect';
import CustomAsyncSelect from '../../UIComponents/Inputs/CustomSelect/CustomAsyncSelect';
import Input from '../../UIComponents/Inputs/Input';
import { formField } from '../../UIComponents/FormComponents/formModels/formFieldModel';
import { companies } from '../../../Constants/Constants';
import { formField } from '../../UIComponents/FormComponents/formFieldModel';
import Autocomplete from '@material-ui/lab/Autocomplete';
import { makeStyles, TextField } from '@material-ui/core';
/**
* Render Oraganization selector (used React-Select)
......@@ -14,12 +12,20 @@ import { companies } from '../../../Constants/Constants';
* and country-list library of updated
* correct country list names)
*/
const Company = () => {
const {
organizationName,
organizationTwitter,
organizationAddress,
} = formField;
const useStyles = makeStyles(() => ({
textField: {
marginBottom: 14,
marginTop: 6,
backgroundColor: 'white',
},
}));
const CompanyInformationCompany = ({ formik }) => {
const classes = useStyles();
const { organizationName, organizationTwitter, organizationAddress } =
formField;
// get country list library and map as option pass to the React-Select
const countryList = require('country-list')
.getNames()
......@@ -28,14 +34,17 @@ const Company = () => {
return (
<>
<h2 className="fw-600 h4" id={organizationName.name}>
{' '}
Organization <span className="orange-star">*</span>{' '}
Organization <span className="orange-star">*</span>
</h2>
<CustomSelectWrapper
<Input
name={organizationName.name}
labelName={organizationName.label}
placeholder={organizationName.placeholder}
ariaLabel={organizationName.name}
srcData={companies}
renderComponent={CustomAsyncSelect}
requiredMark={true}
value={formik.values.organization.legalName}
onChange={formik.handleChange}
/>
<div className="row">
<div className="col-md-8">
......@@ -43,11 +52,17 @@ const Company = () => {
name={organizationTwitter.name}
labelName={organizationTwitter.label}
placeholder={organizationTwitter.placeholder}
ariaLabel={organizationName.name}
requiredMark={true}
value={formik.values.organization.twitterHandle}
onChange={formik.handleChange}
/>
</div>
</div>
<h4 className="fw-600">Address</h4>
<h4 className="fw-600" id={`${organizationName.name}-address`}>
Address
</h4>
<div className="row">
<div className="col-md-16">
<Input
......@@ -55,6 +70,9 @@ const Company = () => {
labelName={organizationAddress.street.label}
placeholder={organizationAddress.street.placeholder}
requiredMark={true}
value={formik.values.organization.address.street}
onChange={formik.handleChange}
ariaLabel={`${organizationName.name}-address`}
/>
</div>
<div className="col-md-8">
......@@ -63,35 +81,80 @@ const Company = () => {
labelName={organizationAddress.city.label}
placeholder={organizationAddress.city.placeholder}
requiredMark={true}
value={formik.values.organization.address.city}
onChange={formik.handleChange}
ariaLabel={`${organizationName.name}-address`}
/>
</div>
</div>
<div className="row margin-bottom-40">
<div className="col-md-8">
<label id={organizationAddress.country.name}>Country</label>
<span className="orange-star margin-left-5">*</span>
<CustomSelectWrapper
name={organizationAddress.country.name}
ariaLabel={organizationAddress.country.name}
renderComponent={DefaultSelect}
<Autocomplete
id={organizationAddress.country.name}
options={countryList}
getOptionLabel={(option) => (option?.label ? option.label : '')}
getOptionSelected={(option, value) => option.value === value.value}
fullWidth={true}
onChange={(ev, value) => {
// this is only for display
formik.setFieldValue(
`${organizationAddress.country.name}-label`,
value ? value : null
);
// this is the data will be actually used
formik.setFieldValue(
organizationAddress.country.name,
value ? value.value : null
);
}}
value={
formik.values.organization.address['country-label']
? formik.values.organization.address['country-label']
: null
}
renderInput={(params) => {
params.inputProps = {
...params.inputProps,
'aria-labelledby': `${organizationName.name}-address`,
};
return (
<TextField
{...params}
label="Country"
placeholder="Country"
variant="outlined"
size="small"
required={true}
className={classes.textField}
/>
);
}}
/>
</div>
<div className="col-md-8">
<Input
name={organizationAddress.provinceOrState.name}
labelName={organizationAddress.provinceOrState.label}
placeholder={organizationAddress.provinceOrState.placeholder}
requiredMark={true}
value={formik.values.organization.address.provinceOrState}
onChange={formik.handleChange}
ariaLabel={`${organizationName.name}-address`}
/>
</div>
<div className="col-md-8">
<Input
name={organizationAddress.postalCode.name}
labelName={organizationAddress.postalCode.label}
placeholder={organizationAddress.postalCode.placeholder}
requiredMark={true}
value={formik.values.organization.address.postalCode}
onChange={formik.handleChange}
ariaLabel={`${organizationName.name}-address`}
/>
</div>
</div>
......@@ -99,4 +162,4 @@ const Company = () => {
);
};
export default Company;
export default CompanyInformationCompany;
import React from 'react';
import React, { useEffect } from 'react';
import Input from '../../UIComponents/Inputs/Input';
import CustomCheckbox from '../../UIComponents/Inputs/CustomCheckbox';
import { formField } from '../../UIComponents/FormComponents/formFieldModel';
import { Checkbox, FormControlLabel } from '@material-ui/core';
/**
* -
......@@ -17,12 +18,15 @@ import CustomCheckbox from '../../UIComponents/Inputs/CustomCheckbox';
*
* @returns
*/
const Contacts = ({ formValues, formField }) => {
const Contacts = ({ formik }) => {
// the boolean form value of "is marketing Rep. the same as company Rep.?"
const mktSame = formValues.representative.marketing.sameAsCompany;
const isMarketingSameAsCompany =
formik.values.representative.marketing.sameAsCompany;
// the boolean form value of "is accounting Rep. the same as company Rep.?"
const accSame = formValues.representative.accounting.sameAsCompany;
const { company, marketing, accounting } = formField;
const isAccountingSameAsCompany =
formik.values.representative.accounting.sameAsCompany;
const { companyRep } = formField;
/**
* Generate Representatives Inputs components
......@@ -32,27 +36,68 @@ const Contacts = ({ formValues, formField }) => {
* @param prefix - simply to add it in the key prop, so that each component has a unique key
* @param disableInput - if marketing / accounting is the same as company Rep., mark the input disabled and just used the same values from company Rep.
*/
const generateContacts = (representativeFields, prefix, disableInput) => {
return (
// update representative.marketing values based on related checkbox
useEffect(() => {
if (isMarketingSameAsCompany) {
const newValues = {
...formik.values.representative.company,
id: formik.values.representative.marketing.id || '',
sameAsCompany: formik.values.representative.marketing.sameAsCompany,
};
formik.setFieldValue('representative.marketing', newValues);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMarketingSameAsCompany]);
// update representative.accounting values based on related checkbox
useEffect(() => {
if (isAccountingSameAsCompany) {
const newValues = {
...formik.values.representative.company,
id: formik.values.representative.accounting.id || '',
sameAsCompany: formik.values.representative.accounting.sameAsCompany,
};
formik.setFieldValue('representative.accounting', newValues);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isAccountingSameAsCompany]);
const generateContacts = (
representativeFields,
prefix,
type,
disableInput