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

Added validation logic for using steppers (#309)

* Added validation logic for using steppers

* Added a function to check if a form is empty

* Added logic to handle go back using stepper

* Fixed missing id when roll back form values in some occasions

* Added customized dialog component

* Applied new logic to back button

* Fixed reset wg data issue in some occasions

* Updated code format based on feedback

* Updated the logic based on feedback

* Made current step index into context

* Updated the wording in modal window

* Fixed reset/roll back issue on company info step
parent efc3c447
...@@ -26,6 +26,7 @@ const App = () => { ...@@ -26,6 +26,7 @@ const App = () => {
pathName: '/sign-in', pathName: '/sign-in',
}); });
const [needLoadingSignIn, setNeedLoadingSignIn] = useState(true); const [needLoadingSignIn, setNeedLoadingSignIn] = useState(true);
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const membershipContextValue = { const membershipContextValue = {
currentUser, currentUser,
...@@ -36,6 +37,8 @@ const App = () => { ...@@ -36,6 +37,8 @@ const App = () => {
setFurthestPage, setFurthestPage,
needLoadingSignIn, needLoadingSignIn,
setNeedLoadingSignIn, setNeedLoadingSignIn,
currentStepIndex,
setCurrentStepIndex
}; };
return ( return (
......
...@@ -23,6 +23,7 @@ export const api_prefix = () => { ...@@ -23,6 +23,7 @@ export const api_prefix = () => {
export const API_PREFIX_FORM = api_prefix() + '/form'; export const API_PREFIX_FORM = api_prefix() + '/form';
export const API_FORM_PARAM = '?sort=dateCreated&order=desc'; export const API_FORM_PARAM = '?sort=dateCreated&order=desc';
export const SIGN_IN = 'Sign In';
export const COMPANY_INFORMATION = 'Company Information'; export const COMPANY_INFORMATION = 'Company Information';
export const MEMBERSHIP_LEVEL = 'Membership Level'; export const MEMBERSHIP_LEVEL = 'Membership Level';
export const WORKING_GROUPS = 'Working Groups'; export const WORKING_GROUPS = 'Working Groups';
...@@ -31,7 +32,7 @@ export const REVIEW = 'Review'; ...@@ -31,7 +32,7 @@ export const REVIEW = 'Review';
export const HAS_TOKEN_EXPIRED = 'HAS_TOKEN_EXPIRED'; export const HAS_TOKEN_EXPIRED = 'HAS_TOKEN_EXPIRED';
export const LOGIN_EXPIRED_MSG = 'Your session has expired, please sign in again.'; export const LOGIN_EXPIRED_MSG = 'Your session has expired, please sign in again.';
export const MAX_LENGTH_HELPER_TEXT = 'The value exceeds max length 255 characters' export const MAX_LENGTH_HELPER_TEXT = 'The value exceeds max length 255 characters';
export const PATH_NAME_ARRAY = [ export const PATH_NAME_ARRAY = [
'/company-info', '/company-info',
...@@ -65,6 +66,7 @@ export const MEMBERSHIP_LEVELS = [ ...@@ -65,6 +66,7 @@ export const MEMBERSHIP_LEVELS = [
]; ];
export const PAGE_STEP = [ export const PAGE_STEP = [
{ label: SIGN_IN, pathName: '/sign-in' },
{ label: COMPANY_INFORMATION, pathName: '/company-info' }, { label: COMPANY_INFORMATION, pathName: '/company-info' },
{ label: MEMBERSHIP_LEVEL, pathName: '/membership-level' }, { label: MEMBERSHIP_LEVEL, pathName: '/membership-level' },
{ label: WORKING_GROUPS, pathName: '/working-groups' }, { label: WORKING_GROUPS, pathName: '/working-groups' },
......
...@@ -18,6 +18,8 @@ const MembershipContext = React.createContext({ ...@@ -18,6 +18,8 @@ const MembershipContext = React.createContext({
setFurthestPage: () => {}, setFurthestPage: () => {},
needLoadingSignIn: '', needLoadingSignIn: '',
setNeedLoadingSignIn: () => {}, setNeedLoadingSignIn: () => {},
currentStepIndex: 0,
setCurrentStepIndex: (stepIndex) => {},
}); });
export default MembershipContext; export default MembershipContext;
...@@ -102,9 +102,9 @@ export function mapPurchasingAndVAT(existingPurchasingAndVATData) { ...@@ -102,9 +102,9 @@ export function mapPurchasingAndVAT(existingPurchasingAndVATData) {
// Step1: purchasing process and VAT Info // Step1: purchasing process and VAT Info
id: existingPurchasingAndVATData?.id || '', id: existingPurchasingAndVATData?.id || '',
isRegistered: !!existingPurchasingAndVATData?.registration_country, isRegistered: !!existingPurchasingAndVATData?.registration_country,
purchasingProcess: existingPurchasingAndVATData?.purchase_order_required, purchasingProcess: existingPurchasingAndVATData?.purchase_order_required || '',
vatNumber: existingPurchasingAndVATData?.vat_number, vatNumber: existingPurchasingAndVATData?.vat_number || '',
countryOfRegistration: existingPurchasingAndVATData?.registration_country, countryOfRegistration: existingPurchasingAndVATData?.registration_country || '',
}; };
} }
...@@ -339,7 +339,7 @@ export function matchWGFieldsToBackend(eachWorkingGroupData, formId) { ...@@ -339,7 +339,7 @@ export function matchWGFieldsToBackend(eachWorkingGroupData, formId) {
* @param formId - Form Id fetched from the server, sotored in membership context, used for calling APIs * @param formId - Form Id fetched from the server, sotored in membership context, used for calling APIs
* @param userId - User Id fetched from the server when sign in, sotored in membership context, used for calling APIs * @param userId - User Id fetched from the server when sign in, sotored in membership context, used for calling APIs
*/ */
export async function executeSendDataByStep(step, formData, formId, userId, setFieldValueObj) { export async function executeSendDataByStep(step, formData, formId, userId, setFieldValueObj, updateFormValuesObj) {
switch (step) { switch (step) {
case 1: case 1:
callSendData( callSendData(
...@@ -350,7 +350,8 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF ...@@ -350,7 +350,8 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF
{ {
fieldName: setFieldValueObj.fieldName.organization, fieldName: setFieldValueObj.fieldName.organization,
method: setFieldValueObj.method, method: setFieldValueObj.method,
} },
updateFormValuesObj
); );
callSendData( callSendData(
formId, formId,
...@@ -360,7 +361,8 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF ...@@ -360,7 +361,8 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF
{ {
fieldName: setFieldValueObj.fieldName.member, fieldName: setFieldValueObj.fieldName.member,
method: setFieldValueObj.method, method: setFieldValueObj.method,
} },
updateFormValuesObj
); );
callSendData( callSendData(
formId, formId,
...@@ -370,7 +372,8 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF ...@@ -370,7 +372,8 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF
{ {
fieldName: setFieldValueObj.fieldName.marketing, fieldName: setFieldValueObj.fieldName.marketing,
method: setFieldValueObj.method, method: setFieldValueObj.method,
} },
updateFormValuesObj
); );
callSendData( callSendData(
formId, formId,
...@@ -380,14 +383,10 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF ...@@ -380,14 +383,10 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF
{ {
fieldName: setFieldValueObj.fieldName.accounting, fieldName: setFieldValueObj.fieldName.accounting,
method: setFieldValueObj.method, method: setFieldValueObj.method,
} },
); updateFormValuesObj
callSendData(
formId,
'',
matchMembershipLevelFieldsToBackend(formData, formId, userId),
''
); );
callSendData(formId, '', matchMembershipLevelFieldsToBackend(formData, formId, userId), '');
let isWGRepSameAsCompany = false; let isWGRepSameAsCompany = false;
formData.workingGroups.map( formData.workingGroups.map(
(wg) => (isWGRepSameAsCompany = wg.workingGroupRepresentative?.sameAsCompany || isWGRepSameAsCompany) (wg) => (isWGRepSameAsCompany = wg.workingGroupRepresentative?.sameAsCompany || isWGRepSameAsCompany)
...@@ -401,6 +400,7 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF ...@@ -401,6 +400,7 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF
matchWGFieldsToBackend(item, formId), matchWGFieldsToBackend(item, formId),
'', '',
setFieldValueObj, setFieldValueObj,
updateFormValuesObj,
index index
); );
}); });
...@@ -419,6 +419,7 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF ...@@ -419,6 +419,7 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF
matchWGFieldsToBackend(item, formId), matchWGFieldsToBackend(item, formId),
step, step,
setFieldValueObj, setFieldValueObj,
updateFormValuesObj,
index index
); );
}); });
...@@ -430,7 +431,8 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF ...@@ -430,7 +431,8 @@ export async function executeSendDataByStep(step, formData, formId, userId, setF
END_POINT.contacts, END_POINT.contacts,
matchContactFieldsToBackend(formData.signingAuthorityRepresentative, CONTACT_TYPE.SIGNING, formId), matchContactFieldsToBackend(formData.signingAuthorityRepresentative, CONTACT_TYPE.SIGNING, formId),
step, step,
setFieldValueObj setFieldValueObj,
updateFormValuesObj
); );
break; break;
...@@ -457,7 +459,8 @@ function callSendData( ...@@ -457,7 +459,8 @@ function callSendData(
dataBody, dataBody,
stepNum, stepNum,
setFieldValueObj, setFieldValueObj,
index updateFormValuesObj,
index,
) { ) {
const entityId = dataBody.id ? dataBody.id : ''; const entityId = dataBody.id ? dataBody.id : '';
const method = dataBody.id ? FETCH_METHOD.PUT : FETCH_METHOD.POST; const method = dataBody.id ? FETCH_METHOD.PUT : FETCH_METHOD.POST;
...@@ -508,6 +511,9 @@ function callSendData( ...@@ -508,6 +511,9 @@ function callSendData(
'organization.address.id', 'organization.address.id',
data[0]?.address?.id data[0]?.address?.id
); );
updateFormValuesObj.theNewValue.organization.id = data[0]?.id;
updateFormValuesObj.theNewValue.organization.address.id = data[0]?.address?.id;
updateFormValuesObj.setUpdatedFormValues(updateFormValuesObj.theNewValue);
break; break;
case 'representative.member': case 'representative.member':
...@@ -515,6 +521,8 @@ function callSendData( ...@@ -515,6 +521,8 @@ function callSendData(
`${setFieldValueObj.fieldName}.id`, `${setFieldValueObj.fieldName}.id`,
data[0]?.id data[0]?.id
); );
updateFormValuesObj.theNewValue.representative.member.id = data[0]?.id;
updateFormValuesObj.setUpdatedFormValues(updateFormValuesObj.theNewValue);
break; break;
case 'representative.marketing': case 'representative.marketing':
...@@ -522,6 +530,8 @@ function callSendData( ...@@ -522,6 +530,8 @@ function callSendData(
`${setFieldValueObj.fieldName}.id`, `${setFieldValueObj.fieldName}.id`,
data[0]?.id data[0]?.id
); );
updateFormValuesObj.theNewValue.representative.marketing.id = data[0]?.id;
updateFormValuesObj.setUpdatedFormValues(updateFormValuesObj.theNewValue);
break; break;
case 'representative.accounting': case 'representative.accounting':
...@@ -529,6 +539,8 @@ function callSendData( ...@@ -529,6 +539,8 @@ function callSendData(
`${setFieldValueObj.fieldName}.id`, `${setFieldValueObj.fieldName}.id`,
data[0]?.id data[0]?.id
); );
updateFormValuesObj.theNewValue.representative.accounting.id = data[0]?.id;
updateFormValuesObj.setUpdatedFormValues(updateFormValuesObj.theNewValue);
break; break;
case 'workingGroups': case 'workingGroups':
...@@ -540,6 +552,11 @@ function callSendData( ...@@ -540,6 +552,11 @@ function callSendData(
`workingGroups[${index}].workingGroupRepresentative.id`, `workingGroups[${index}].workingGroupRepresentative.id`,
data[0]?.contact?.id data[0]?.contact?.id
); );
if (updateFormValuesObj?.theNewValue) {
updateFormValuesObj.theNewValue.workingGroups[index].id = data[0]?.id;
updateFormValuesObj.theNewValue.workingGroups[index].workingGroupRepresentative.id = data[0]?.contact?.id;
updateFormValuesObj.setUpdatedFormValues(updateFormValuesObj.theNewValue);
}
break; break;
case 'signingAuthorityRepresentative': case 'signingAuthorityRepresentative':
...@@ -551,6 +568,8 @@ function callSendData( ...@@ -551,6 +568,8 @@ function callSendData(
`${setFieldValueObj.fieldName}.id`, `${setFieldValueObj.fieldName}.id`,
data[0]?.id data[0]?.id
); );
updateFormValuesObj.theNewValue.signingAuthorityRepresentative.id = data[0]?.id;
updateFormValuesObj.setUpdatedFormValues(updateFormValuesObj.theNewValue);
break; break;
default: default:
...@@ -693,6 +712,51 @@ export function scrollToTop() { ...@@ -693,6 +712,51 @@ export function scrollToTop() {
window.scrollTo({ top: 0, behavior: 'smooth' }); window.scrollTo({ top: 0, behavior: 'smooth' });
} }
export function isObjectEmpty(obj) {
for (const key in obj) {
// Do not need to check the value of id or allWorkingGroups, as they are not provided by users
if (key === 'id' || key === 'allWorkingGroups') {
continue;
}
const element = obj[key];
if (typeof element === 'object') {
if (!isObjectEmpty(element)) {
return false;
}
} else if (element !== '' && element !== false) {
return false;
}
}
return true;
}
export function validateGoBack(isEmpty, result, formik, setShouldOpen, navigate, isNotFurthestPage) {
// Save values on current step if it's NOT empty and passes validation
if (!isEmpty && Object.keys(result).length <= 0) {
formik.submitForm();
}
// Open modal window if it's NOT empty and fails to pass validation
// OR open it if it's emtpy and NOT the furthest page
if ((!isEmpty && Object.keys(result).length > 0) || (isEmpty && isNotFurthestPage)) {
formik.setTouched(result);
setShouldOpen(true);
return;
}
navigate();
}
export function checkIsNotFurthestPage(currentIndex, furthestIndex) {
if (currentIndex === 3) {
// For wg/3rd step, it can be empty, and clear and remove operation will update the database when user does so
// So, no need to roll back the data
return false;
}
return currentIndex < furthestIndex;
}
export const logout = () => { export const logout = () => {
fetch(`${api_prefix()}/logout`) fetch(`${api_prefix()}/logout`)
.then(() => { .then(() => {
......
...@@ -55,7 +55,7 @@ export default function Application() { ...@@ -55,7 +55,7 @@ export default function Application() {
goToNextStep(5, '/submitted'); goToNextStep(5, '/submitted');
}; };
const submitCompanyInfo = (isUsingStepper) => { const submitCompanyInfo = () => {
const values = formikCompanyInfo.values; const values = formikCompanyInfo.values;
// update the organization values // update the organization values
const organization = values.organization; const organization = values.organization;
...@@ -107,27 +107,28 @@ export default function Application() { ...@@ -107,27 +107,28 @@ export default function Application() {
method: formikCompanyInfo.setFieldValue, method: formikCompanyInfo.setFieldValue,
}; };
executeSendDataByStep(1, theNewValue, currentFormId, currentUser.name, setFieldValueObj); const updateFormValuesObj = {
theNewValue,
setUpdatedFormValues,
};
executeSendDataByStep(1, theNewValue, currentFormId, currentUser.name, setFieldValueObj, updateFormValuesObj);
// Only make the API call when signingAuthorityRepresentative has an id // Only make the API call when signingAuthorityRepresentative has an id
// If not, it means there is nothing in the db, so no need to update. // If not, it means there is nothing in the db, so no need to update.
values.signingAuthorityRepresentative.id && values.signingAuthorityRepresentative.id &&
executeSendDataByStep( executeSendDataByStep(4, values, currentFormId, currentUser.name, setFieldValueObj, updateFormValuesObj);
4,
values,
currentFormId,
currentUser.name,
setFieldValueObj
);
// Only need to call goToNextStep when is not using stepper // Only need to call goToNextStep when is not using stepper
!isUsingStepper && goToNextStep(1, '/membership-level');
}; };
const formikCompanyInfo = useFormik({ const formikCompanyInfo = useFormik({
initialValues: initialValues, initialValues: initialValues,
validationSchema: validationSchema[0], validationSchema: validationSchema[0],
onSubmit: () => submitCompanyInfo(), onSubmit: () => {
submitCompanyInfo();
goToNextStep(1, '/membership-level');
},
}); });
const submitMembershipLevel = (isUsingStepper) => { const submitMembershipLevel = () => {
const values = formikMembershipLevel.values; const values = formikMembershipLevel.values;
// update the membershipLevel values // update the membershipLevel values
const membershipLevel = values.membershipLevel; const membershipLevel = values.membershipLevel;
...@@ -145,21 +146,26 @@ export default function Application() { ...@@ -145,21 +146,26 @@ export default function Application() {
]; ];
// set valueToUpdateFormik to CompanyInfo formik to make sure the value is up to date // set valueToUpdateFormik to CompanyInfo formik to make sure the value is up to date
updateCompanyInfoForm(valueToUpdateFormik); updateCompanyInfoForm(valueToUpdateFormik);
executeSendDataByStep(2, values, currentFormId, currentUser.name); executeSendDataByStep(2, values, currentFormId, currentUser.name);
!isUsingStepper && goToNextStep(2, '/working-groups');
}; };
const formikMembershipLevel = useFormik({ const formikMembershipLevel = useFormik({
initialValues: initialValues, initialValues: initialValues,
validationSchema: validationSchema[1], validationSchema: validationSchema[1],
onSubmit: () => submitMembershipLevel(), onSubmit: () => {
submitMembershipLevel();
goToNextStep(2, '/working-groups');
},
}); });
const submitWorkingGroups = (isUsingStepper) => { const submitWorkingGroups = () => {
const values = formikWorkingGroups.values; const values = formikWorkingGroups.values;
// update the workingGroups values // update the workingGroups values
const workingGroups = values.workingGroups; const theNewValue = {
setUpdatedFormValues({ ...updatedFormValues, workingGroups }); ...updatedFormValues,
workingGroups: values.workingGroups,
skipJoiningWG: values.skipJoiningWG,
};
setUpdatedFormValues(theNewValue);
console.log('updated working groups: ', values); console.log('updated working groups: ', values);
if (!values.skipJoiningWG) { if (!values.skipJoiningWG) {
...@@ -169,27 +175,30 @@ export default function Application() { ...@@ -169,27 +175,30 @@ export default function Application() {
method: formikWorkingGroups.setFieldValue, method: formikWorkingGroups.setFieldValue,
}; };
executeSendDataByStep(3, values, currentFormId, currentUser.name, setFieldValueObj); executeSendDataByStep(3, values, currentFormId, currentUser.name, setFieldValueObj, {
!isUsingStepper && goToNextStep(3, '/signing-authority'); theNewValue,
} else if (!isUsingStepper) { setUpdatedFormValues,
// If the user is NOT using stepper and NOT joining any wg, then go to next page directly });
goToNextStep(3, '/signing-authority');
} }
}; };
const formikWorkingGroups = useFormik({ const formikWorkingGroups = useFormik({
initialValues: initialValues, initialValues: initialValues,
validationSchema: validationSchema[2], validationSchema: validationSchema[2],
onSubmit: () => submitWorkingGroups(), onSubmit: () => {
submitWorkingGroups();
goToNextStep(3, '/signing-authority');
},
}); });
const submitSigningAuthority = (isUsingStepper) => { const submitSigningAuthority = () => {
const values = formikSigningAuthority.values; const values = formikSigningAuthority.values;
// update the signingAuthorityRepresentative values // update the signingAuthorityRepresentative values
const signingAuthorityRepresentative = values.signingAuthorityRepresentative; const signingAuthorityRepresentative = values.signingAuthorityRepresentative;
setUpdatedFormValues({ const theNewValue = {
...updatedFormValues, ...updatedFormValues,
signingAuthorityRepresentative, signingAuthorityRepresentative,
}); };
setUpdatedFormValues(theNewValue);
console.log('updated SigningAuthority: ', values); console.log('updated SigningAuthority: ', values);
const valueToUpdateFormik = [ const valueToUpdateFormik = [
...@@ -207,19 +216,24 @@ export default function Application() { ...@@ -207,19 +216,24 @@ export default function Application() {
companyInfo: formikCompanyInfo.setFieldValue, companyInfo: formikCompanyInfo.setFieldValue,
}, },
}; };
executeSendDataByStep(4, values, currentFormId, currentUser.name, setFieldValueObj);
!isUsingStepper && goToNextStep(4, '/review'); executeSendDataByStep(4, values, currentFormId, currentUser.name, setFieldValueObj, {
theNewValue,
setUpdatedFormValues,
});
}; };
const formikSigningAuthority = useFormik({ const formikSigningAuthority = useFormik({
initialValues: initialValues, initialValues: initialValues,
validationSchema: validationSchema[3], validationSchema: validationSchema[3],
onSubmit: () => submitSigningAuthority(), onSubmit: () => {
submitSigningAuthority();
goToNextStep(4, '/review');
},
}); });
const handleLoginExpired = useCallback(() => { const handleLoginExpired = useCallback(() => {
if (sessionStorage.getItem(HAS_TOKEN_EXPIRED)) { if (sessionStorage.getItem(HAS_TOKEN_EXPIRED)) {
sessionStorage.setItem(HAS_TOKEN_EXPIRED, ''); sessionStorage.setItem(HAS_TOKEN_EXPIRED, '');
// using setTimeout here is to make the pop up message more noticeable // using setTimeout here is to make the pop up message more noticeable
setTimeout(() => { setTimeout(() => {
setIsLoginExpired(true); setIsLoginExpired(true);
...@@ -237,8 +251,6 @@ export default function Application() { ...@@ -237,8 +251,6 @@ export default function Application() {
// generate the step options above the form // generate the step options above the form
const renderStepper = () => ( const renderStepper = () => (
<div className="stepper"> <div className="stepper">
<Step title="Sign In" index={-1} pathName="/sign-in" />
{PAGE_STEP.map((pageStep, index) => { {PAGE_STEP.map((pageStep, index) => {
return ( return (
<Step <Step
...@@ -246,10 +258,23 @@ export default function Application() { ...@@ -246,10 +258,23 @@ export default function Application() {
title={pageStep.label} title={pageStep.label}
index={index} index={index}
pathName={pageStep.pathName} pathName={pageStep.pathName}
submitCompanyInfo={submitCompanyInfo}