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

Made formchooser work (#88)

* made formchooser work

* made start new application work for company info page

* made start new application work for membershiplevel page

* fixed a bug when user has no existing forms

* made continue with existing work for company info

* made start new form and continue exisitng work for WG

* fixed a small issue on membership level autocomplete

* fixed the order issue of existing forms

* finished the logic for going back to a previous page and update

* cleaned up the code

* fixed loading forever issue in React Only Mode

* simplified the formFunctionHelpers

* fixed 2 minor issue, checkbox in company info and WG on review page
parent be656fd5
......@@ -39,10 +39,7 @@ const App = () => {
<AppHeader />
<MembershipContext.Provider value={membershipContextValue}>
<Router>
<Main
furthestPage={furthestPage}
setFurthestPage={setFurthestPage}
/>
<Main />
</Router>
</MembershipContext.Provider>
<AppFooter />
......
......@@ -29,12 +29,12 @@ export const FETCH_HEADER = {
};
export const membership_levels = [
{ label: 'Strategic Member', value: 'strategic' },
{ label: 'Strategic Member', value: 'Strategic Member' },
{
label: 'Contributing Member (formerly referred to as Solutions Members)',
value: 'contributing',
value: 'Contributing Member',
},
{ label: 'Associate Member', value: 'associate' },
{ label: 'Associate Member', value: 'Associate Member' },
];
export const PAGE_STEP = [
......
......@@ -4,7 +4,6 @@ import {
end_point,
api_prefix_form,
FETCH_HEADER,
newForm_tempId,
getCurrentMode,
MODE_REACT_ONLY,
MODE_REACT_API,
......@@ -78,11 +77,11 @@ export function matchCompanyFields(existingOrganizationData) {
street: existingOrganizationData?.address.street || '',
city: existingOrganizationData?.address.city || '',
provinceOrState: existingOrganizationData?.address.province_state || '',
country: existingOrganizationData?.address.country || '',
'country-label': {
label: existingOrganizationData?.address.country || '',
value: existingOrganizationData?.address.country || '',
},
country: existingOrganizationData?.address.country || '',
postalCode: existingOrganizationData?.address.postal_code || '',
},
twitterHandle: existingOrganizationData?.twitter_handle || '',
......@@ -206,14 +205,14 @@ export function matchCompanyFieldsToBackend(organizationData, formId) {
var org = {
address: {
city: organizationData.address.city,
country: organizationData.address.country.value,
country: organizationData.address.country,
postal_code: organizationData.address.postalCode,
province_state: organizationData.address.provinceOrState,
street: organizationData.address.street,
},
form_id: formId,
id: organizationData.id,
legal_name: organizationData.legalName.label,
legal_name: organizationData.legalName,
twitter_handle: organizationData.twitterHandle || '',
};
......@@ -237,7 +236,7 @@ export function matchMembershipLevelFieldsToBackend(
return {
id: formId,
user_id: userId,
membership_level: membershipLevel.value,
membership_level: membershipLevel,
signing_authority: true,
};
}
......@@ -281,13 +280,15 @@ export function matchWGFieldsToBackend(eachWorkingGroupData, formId) {
formId
);
const theDate = eachWorkingGroupData?.effectiveDate
? new Date(eachWorkingGroupData?.effectiveDate)
: new Date();
return {
id: eachWorkingGroupData?.id,
working_group_id: eachWorkingGroupData?.workingGroup.value,
participation_level: eachWorkingGroupData?.participationLevel.value,
effective_date: (eachWorkingGroupData?.effectiveDate)
.toISOString()
.replace(/.\d+Z$/g, 'Z'),
participation_level: eachWorkingGroupData?.participationLevel,
effective_date: theDate.toISOString().replace(/.\d+Z$/g, 'Z'),
contact: {
...wg_contact,
},
......@@ -304,13 +305,13 @@ export function matchWGFieldsToBackend(eachWorkingGroupData, formId) {
*/
export async function executeSendDataByStep(step, formData, formId, userId) {
switch (step) {
case 0:
sendData(
case 1:
callSendData(
formId,
end_point.organizations,
matchCompanyFieldsToBackend(formData.organization, formId)
);
sendData(
callSendData(
formId,
end_point.contacts,
matchContactFieldsToBackend(
......@@ -319,7 +320,7 @@ export async function executeSendDataByStep(step, formData, formId, userId) {
formId
)
);
sendData(
callSendData(
formId,
end_point.contacts,
matchContactFieldsToBackend(
......@@ -328,7 +329,7 @@ export async function executeSendDataByStep(step, formData, formId, userId) {
formId
)
);
sendData(
callSendData(
formId,
end_point.contacts,
matchContactFieldsToBackend(
......@@ -339,8 +340,8 @@ export async function executeSendDataByStep(step, formData, formId, userId) {
);
break;
case 1:
sendData(
case 2:
callSendData(
formId,
'',
matchMembershipLevelFieldsToBackend(
......@@ -351,9 +352,9 @@ export async function executeSendDataByStep(step, formData, formId, userId) {
);
break;
case 2:
case 3:
formData.workingGroups.forEach((item) => {
sendData(
callSendData(
formId,
end_point.working_groups,
matchWGFieldsToBackend(item, formId)
......@@ -361,7 +362,7 @@ export async function executeSendDataByStep(step, formData, formId, userId) {
});
break;
case 3:
case 4:
return;
default:
......@@ -373,13 +374,14 @@ export async function executeSendDataByStep(step, formData, formId, userId) {
* @param formId - Form Id fetched from the server, sotored in membership context, used for calling APIs
* @param endpoint - To which endpoint the fetch is calling to backend:
* /form/{id}, /form/{id}/organizations/{id}, /form/{id}/contacts/{id}, /form/{id}/working_groups/{id}
* @param method - Fetch methods: POST, GET, PUT, DELETE
* @param dataBody - The data body passed to server, normally is the filled form data to be saved
* @param entityId - The Id of organizations, or contacts, or working groups entry;
* If empty, is creating a new entity, use POST method;
* If has value, is fetched from server, use PUT or DELETE;
*/
function callSendData(formId, endpoint = '', method, dataBody, entityId = '') {
function callSendData(formId, endpoint = '', dataBody) {
const entityId = dataBody.id ? dataBody.id : '';
const method = dataBody.id ? FETCH_METHOD.PUT : FETCH_METHOD.POST;
let url = api_prefix_form + `/${formId}`;
if (endpoint) {
......@@ -408,40 +410,6 @@ function callSendData(formId, endpoint = '', method, dataBody, entityId = '') {
}
}
/**
* PUT or POST function
*
* @param formId -
* Form Id fetched from the server, sotored in membership context, used for calling APIs
* @param endpoint -
* To which endpoint the fetch is calling to backend:
* /form/{id}, /form/{id}/organizations/{id}, /form/{id}/contacts/{id}, /form/{id}/working_groups/{id}
* @param dataBody -
* The data body passed to server, normally is the filled form data to be saved
*
* If no data.id, means it's a new data entry, we should use POST. otherwise, use PUT
*/
export function sendData(formId, endpoint, dataBody) {
switch (endpoint) {
case end_point.organizations:
if (!dataBody.id || formId === newForm_tempId) {
delete dataBody.id;
callSendData(formId, endpoint, FETCH_METHOD.POST, dataBody);
} else {
callSendData(formId, endpoint, FETCH_METHOD.PUT, dataBody, dataBody.id);
}
break;
default:
if (!dataBody.id) {
delete dataBody.id;
callSendData(formId, endpoint, FETCH_METHOD.POST, dataBody);
} else {
callSendData(formId, endpoint, FETCH_METHOD.PUT, dataBody, dataBody.id);
}
}
}
/**
* DELETE
*
......@@ -500,12 +468,7 @@ export function deleteData(formId, endpoint, entityId, callback, index) {
* - Store the returned new form Id in my FormId Context
* - Send the API calls to organizations and contacts
* **/
export async function handleNewForm(
setCurrentFormId,
formData,
userId,
defaultBehaviour
) {
export async function handleNewForm(setCurrentFormId, defaultBehaviour) {
if (getCurrentMode() === MODE_REACT_ONLY) {
defaultBehaviour();
}
......@@ -523,10 +486,11 @@ export async function handleNewForm(
})
.then((res) => res.json())
.then((data) => {
console.log('Start with a new form:', data);
setCurrentFormId(data[0]?.id);
executeSendDataByStep(0, formData, data[0]?.id, userId);
defaultBehaviour();
});
})
.catch((err) => console.log(err));
}
// Probably Also need to delete the old form Id, or keep in the db for 30 days
......
......@@ -11,13 +11,11 @@ import {
end_point,
api_prefix_form,
FETCH_HEADER,
newForm_tempId,
getCurrentMode,
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
......@@ -31,98 +29,113 @@ import { initialValues } from '../../UIComponents/FormComponents/formFieldModel'
* library (such as "formik.values", "formik.setFieldValue");
* - formField: the form field in formModels/formFieldModel.js
*/
const CompanyInformation = ({ formik }) => {
const { currentFormId } = useContext(MembershipContext); // current chosen form id
const CompanyInformation = ({ formik, isStartNewForm }) => {
const { currentFormId, furthestPage } = useContext(MembershipContext); // current chosen form id
const [loading, setLoading] = useState(true);
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.
// 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';
// just for React only testing.
// let currentFormId = 'form_1';
let url_prefix_local;
let url_suffix_local = '';
// If running on localhost:3000
if (getCurrentMode() === MODE_REACT_ONLY) {
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,
// need to get company data, and contacts data
let pool = [
fetch(
url_prefix_local +
`/${currentFormId}/` +
end_point.organizations +
url_suffix_local,
{ headers: FETCH_HEADER }
),
fetch(
url_prefix_local +
`/${currentFormId}/` +
end_point.contacts +
url_suffix_local,
{ headers: FETCH_HEADER }
),
];
Promise.all(pool)
.then((res) => Promise.all(res.map((r) => r.json())))
.then(([organizations, contacts]) => {
// Matching the field data
if (organizations[0]) {
// the organization data returned is always an
// array of one object, that is why using [0]
// Call the the function to map the retrived
// organization backend data to fit frontend
let tempOrg = matchCompanyFields(organizations[0]);
// Call the setFieldValue of Formik, to set
// organization field with the mapped data,
// if nested, it will automatically map the
// properties and values
console.log(tempOrg);
formik.setFieldValue('organization', tempOrg);
}
if (contacts.length) {
// Call the the function to map the retrived contacts
// (company representative, marketing rep, accounting rep)
// backend data to fit frontend
let tempContacts = matchContactFields(contacts);
// 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
formik.setFieldValue('representative', tempContacts);
}
setLoading(false);
});
} else if (currentFormId === newForm_tempId) {
formik.setFieldValue('representative', initialValues.representative);
formik.setFieldValue('organization', initialValues.organization);
setLoading(false);
let url_prefix_local;
let url_suffix_local = '';
// If running on localhost:3000
if (getCurrentMode() === MODE_REACT_ONLY) {
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) {
// Using promise pool, because in first step,
// need to get company data, and contacts data
let pool = [
fetch(
url_prefix_local +
`/${currentFormId}/` +
end_point.organizations +
url_suffix_local,
{ headers: FETCH_HEADER }
),
fetch(
url_prefix_local +
`/${currentFormId}/` +
end_point.contacts +
url_suffix_local,
{ headers: FETCH_HEADER }
),
];
Promise.all(pool)
.then((res) => Promise.all(res.map((r) => r.json())))
.then(([organizations, contacts]) => {
// Matching the field data
if (organizations[0]) {
// the organization data returned is always an
// array of one object, that is why using [0]
// Call the the function to map the retrived
// organization backend data to fit frontend
let tempOrg = matchCompanyFields(organizations[0]);
// Call the setFieldValue of Formik, to set
// organization field with the mapped data,
// if nested, it will automatically map the
// properties and values
formik.setFieldValue('organization', tempOrg);
}
if (contacts.length) {
// Call the the function to map the retrived contacts
// (company representative, marketing rep, accounting rep)
// backend data to fit frontend
let tempContacts = matchContactFields(contacts);
// 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
formik.setFieldValue('representative', tempContacts);
}
setLoading(false);
});
} else {
setLoading(false);
}
};
if (isStartNewForm) {
if (furthestPage.index > 1 && !formik.values.organization?.id) {
// This means user already submitted/finished this page, and comes back from a further page/step
// so, we need to GET the info user submitted and if user changes anything,
// we will use the organization_id from the GET to do the PUT to update the info.
detectModeAndFetch();
setLoading(false);
} else {
// This means this is the 1st time the user see this page,
// or the user already got the organizations.id
// no need to do any API call
setLoading(false);
}
} else if (!formik.values.organization?.id) {
// continue with an existing one, if there is no id saved locally
// then it means this is the 1st time the user see this page
// need to GET the data
detectModeAndFetch();
} else {
// user already has the data, no need to do any API call
setLoading(false);
}
};
// 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
......
......@@ -48,7 +48,7 @@ const Contacts = ({ formik }) => {
formik.setFieldValue('representative.marketing', newValues);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMarketingSameAsCompany]);
}, [isMarketingSameAsCompany, formik.values.representative.company]);
// update representative.accounting values based on related checkbox
useEffect(() => {
......@@ -61,7 +61,7 @@ const Contacts = ({ formik }) => {
formik.setFieldValue('representative.accounting', newValues);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isAccountingSameAsCompany]);
}, [isAccountingSameAsCompany, formik.values.representative.company]);
const generateContacts = (
representativeFields,
......
import { useState } from 'react';
import { useContext, useState } from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import { useFormik } from 'formik';
import SignIn from './SignIn/SignIn';
import {
COMPANY_INFORMATION,
MEMBERSHIP_LEVEL,
SIGNING_AUTHORITY,
WORKING_GROUPS,
PAGE_STEP,
} from '../../Constants/Constants';
import {
......@@ -23,10 +20,15 @@ import SignInIntroduction from './SignIn/SignInIntroduction';
import SubmitSuccess from './SubmitSuccess/SubmitSuccess';
import { validationSchema } from '../UIComponents/FormComponents/ValidationSchema';
import { useHistory } from 'react-router-dom';
import { executeSendDataByStep } from '../../Utils/formFunctionHelpers';
import MembershipContext from '../../Context/MembershipContext';
export default function Main({ furthestPage, setFurthestPage }) {
export default function Main() {
const history = useHistory();
const { currentFormId, furthestPage, setFurthestPage, currentUser } =
useContext(MembershipContext);
const [updatedFormValues, setUpdatedFormValues] = useState(initialValues);
const [isStartNewForm, setIsStartNewForm] = useState(true);
const goToNextStep = (pageIndex, nextPage) => {
if (furthestPage.index <= pageIndex)
......@@ -53,6 +55,10 @@ export default function Main({ furthestPage, setFurthestPage }) {
organization,
representative,
});
console.log('updated company info: ', values);
executeSendDataByStep(1, values, currentFormId, currentUser.name);
goToNextStep(1, '/membership-level');
},
});
......@@ -64,6 +70,10 @@ export default function Main({ furthestPage, setFurthestPage }) {
// update the membershipLevel values
const membershipLevel = values.membershipLevel;
setUpdatedFormValues({ ...updatedFormValues, membershipLevel });
console.log('updated membership level: ', values);
executeSendDataByStep(2, values, currentFormId, currentUser.name);
goToNextStep(2, '/working-groups');
},
});
......@@ -75,6 +85,10 @@ export default function Main({ furthestPage, setFurthestPage }) {
// update the workingGroups values
const workingGroups = values.workingGroups;
setUpdatedFormValues({ ...updatedFormValues, workingGroups });
console.log('updated working groups: ', values);
executeSendDataByStep(3, values, currentFormId, currentUser.name);
goToNextStep(3, '/signing-authority');
},
});
......@@ -133,6 +147,8 @@ export default function Main({ furthestPage, setFurthestPage }) {
formField={formField}
label={COMPANY_INFORMATION}
setFurthestPage={setFurthestPage}
history={history}
setIsStartNewForm={setIsStartNewForm}
/>
</Route>
......@@ -141,8 +157,8 @@ export default function Main({ furthestPage, setFurthestPage }) {
// stop users visiting steps/pages that are not able to edit yet
furthestPage.index >= 1 ? (
<CompanyInformation
label={COMPANY_INFORMATION}
formik={formikCompanyInfo}
isStartNewForm={isStartNewForm}
/>
) : (
// if uses are not allowed to visit this page,
......@@ -156,7 +172,8 @@ export default function Main({ furthestPage, setFurthestPage }) {
{furthestPage.index >= 2 ? (
<MembershipLevel
formik={formikMembershipLevel}
label={MEMBERSHIP_LEVEL}
isStartNewForm={isStartNewForm}
furthestPage={furthestPage}
/>
) : (
<Redirect to={furthestPage.pathName} />
......@@ -166,9 +183,9 @@ export default function Main({ furthestPage, setFurthestPage }) {
<Route path="/working-groups">
{furthestPage.index >= 3 ? (
<WorkingGroupsWrapper
formField={formField}
label={WORKING_GROUPS}
formik={formikWorkingGroups}
isStartNewForm={isStartNewForm}
furthestPage={furthestPage}
/>
) : (
<Redirect to={furthestPage.pathName} />
......@@ -177,11 +194,7 @@ export default function Main({ furthestPage, setFurthestPage }) {
<Route path="/signing-authority">
{furthestPage.index >= 4 ? (
<SigningAuthority
formField={formField}
label={SIGNING_AUTHORITY}
formik={formikSigningAuthority}
/>
<SigningAuthority formik={formikSigningAuthority} />
) : (
<Redirect to={furthestPage.pathName} />
)}
......
......@@ -33,7 +33,7 @@ const useStyles = makeStyles(() => ({
},
}));