diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..1133129421c4ad97a3e4f9ebcf53801d01011b22 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/NOTICE.md b/NOTICE.md index e683cdc5f22dcdaac2c165383aa2556ffd5a4ebd..a59203e8b94d64826b2774df6fbe6352c3b7b514 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -31,6 +31,13 @@ The project maintains the following source code repositories: ## Third-party Content +### OPEN HTML TO PDF (1.09) + +https://github.com/danfickle/openhtmltopdf/ +License: GNU Lesser General Public License (LGPL) +SPDX-License-Identifier: LGPL-2.1-only +No modifications or additions to the library have been made. License text provided through library distribution. + ### Drupal-Bootstrap (7.x-3.20) * License: The GPL License (GPL) diff --git a/config/nginx/nginx.conf b/config/nginx/nginx.conf index 6d99167c0d543701decd26dfd7ff3c5a28442c75..fcde2335bec47592a6c6f4f6a4c30f4e8c36c36f 100644 --- a/config/nginx/nginx.conf +++ b/config/nginx/nginx.conf @@ -5,6 +5,12 @@ server { client_max_body_size 16m; client_body_buffer_size 128k; + + # proxy buffering configuration + # https://stackoverflow.com/a/27551259/8538422 + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; location /api { # don't cache it @@ -31,4 +37,4 @@ server { index index.html index.htm; try_files $uri $uri/ /index.html =404; } -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index 93b5a7b38c39e65b3d6bce2ca0b339f3dceedebc..22816a92821309846549abed11337d2ee744c976 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ 1.13.7.Final 3.8.1 io.quarkus + 1.0.9 @@ -97,6 +98,28 @@ io.quarkus quarkus-hibernate-validator + + + + com.openhtmltopdf + openhtmltopdf-core + ${openhtml.version} + + + + + com.openhtmltopdf + openhtmltopdf-pdfbox + ${openhtml.version} + + + + + com.openhtmltopdf + openhtmltopdf-java2d + ${openhtml.version} + + io.quarkus quarkus-junit5 diff --git a/src/main/java/org/eclipsefoundation/react/service/impl/DefaultMailerService.java b/src/main/java/org/eclipsefoundation/react/service/impl/DefaultMailerService.java index c58c4cf47aabb58a40c49e674ed2fa05f1f8310d..d9e2aef751125541b69705946c3272f811a5c432 100644 --- a/src/main/java/org/eclipsefoundation/react/service/impl/DefaultMailerService.java +++ b/src/main/java/org/eclipsefoundation/react/service/impl/DefaultMailerService.java @@ -1,8 +1,13 @@ package org.eclipsefoundation.react.service.impl; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; +import java.util.UUID; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; @@ -13,6 +18,9 @@ import org.eclipsefoundation.react.service.MailerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.PageSizeUnits; +import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; + import io.quarkus.mailer.Mail; import io.quarkus.mailer.Mailer; import io.quarkus.qute.Location; @@ -31,12 +39,19 @@ import io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipal; public class DefaultMailerService implements MailerService { public static final Logger LOGGER = LoggerFactory.getLogger(DefaultMailerService.class); + private static final String EMAIL_INCLUDE_PREAMBLE_VAR = "includePreamble"; + private static final String EMAIL_DATA_VAR = "data"; + private static final String EMAIL_NOW_VAR = "now"; + private static final String EMAIL_NAME_VAR = "name"; + @ConfigProperty(name = "eclipse.mailer.membership.inbox") String membershipMailbox; @ConfigProperty(name = "eclipse.mailer.membership.author-message.bcc") Optional> authorMessageMailboxBcc; @ConfigProperty(name = "eclipse.mailer.membership.membership-message.bcc") Optional> membershipMessageMailboxBcc; + @ConfigProperty(name = "eclipse.mailer.membership.doc-storage-root", defaultValue = "/tmp") + String temporaryDocumentStorage; @Inject SecurityIdentity ident; @@ -79,12 +94,21 @@ public class DefaultMailerService implements MailerService { throw new IllegalStateException( "Could not find a fully complete form for form with ID '" + data.form.getId() + "'"); } - Mail m = getMail(membershipMailbox, "New Request to join working group(s) - " - + generateName((DefaultJWTCallerPrincipal) ident.getPrincipal()), data, false); + DefaultJWTCallerPrincipal defaultPrin = (DefaultJWTCallerPrincipal) ident.getPrincipal(); + Mail m = getMail(membershipMailbox, "New Request to join working group(s) - " + generateName(defaultPrin), data, + false); // add BCC if set if (!membershipMessageMailboxBcc.isEmpty()) { m.setBcc(membershipMessageMailboxBcc.get()); } + // add the PDF attachment + m.addAttachment("membership-enrollment-" + generateName(defaultPrin).toLowerCase().replace(" ", "-") + ".pdf", + renderHTMLPDF( + membershipTemplateWeb + .data(EMAIL_DATA_VAR, data, EMAIL_NAME_VAR, generateName(defaultPrin), EMAIL_NOW_VAR, + LocalDateTime.now(), EMAIL_INCLUDE_PREAMBLE_VAR, false, "isPDF", true) + .render()), + "application/pdf"); mailer.send(m); } @@ -101,15 +125,36 @@ public class DefaultMailerService implements MailerService { private Mail getMail(String recipient, String subject, MailerData data, boolean includePreamble) { String name = generateName((DefaultJWTCallerPrincipal) ident.getPrincipal()); // generate the mail message, sending the messsage to the membershipMailbox - Mail m = Mail.withHtml(recipient, subject, membershipTemplateWeb - .data("data", data, "name", name, "now", LocalDateTime.now(), "includePreamble", includePreamble) - .render()); - m.setText(membershipTemplate - .data("data", data, "name", name, "now", LocalDateTime.now(), "includePreamble", includePreamble) - .render()); + Mail m = Mail.withHtml(recipient, subject, membershipTemplateWeb.data(EMAIL_DATA_VAR, data, EMAIL_NAME_VAR, + name, EMAIL_NOW_VAR, LocalDateTime.now(), EMAIL_INCLUDE_PREAMBLE_VAR, includePreamble).render()); + m.setText(membershipTemplate.data(EMAIL_DATA_VAR, data, EMAIL_NAME_VAR, name, EMAIL_NOW_VAR, + LocalDateTime.now(), EMAIL_INCLUDE_PREAMBLE_VAR, includePreamble).render()); return m; } + /** + * Render the PDF document using HTML generated by the Qute template engine. + * + * @param html the HTML for the PDF document. + * @return the file that represents the document. This should be in a temporary directory so that it gets cleaned on + * occasion. + */ + private File renderHTMLPDF(String html) { + // create a unique file name for temporary storage + File f = new File(temporaryDocumentStorage + '/' + UUID.randomUUID().toString() + ".pdf"); + try (OutputStream os = new FileOutputStream(f)) { + PdfRendererBuilder builder = new PdfRendererBuilder(); + builder.withHtmlContent(html, temporaryDocumentStorage); + // using a4 measurements + builder.useDefaultPageSize(210, 297, PageSizeUnits.MM); + builder.toStream(os); + builder.run(); + } catch (IOException e) { + throw new RuntimeException("Could not build PDF document", e); + } + return f; + } + private String generateName(DefaultJWTCallerPrincipal defaultPrin) { return new StringBuilder().append((String) defaultPrin.getClaim("given_name")).append(" ") .append((String) defaultPrin.getClaim("family_name")).toString(); diff --git a/src/main/resources/templates/emails/form_membership_email_template.txt b/src/main/resources/templates/emails/form_membership_email_template.txt index 688d9c1b522c633e675678cf69080d7806638029..14bee32016c2eb05b32f073612263715254fa9f3 100644 --- a/src/main/resources/templates/emails/form_membership_email_template.txt +++ b/src/main/resources/templates/emails/form_membership_email_template.txt @@ -2,7 +2,7 @@ Eclipse Foundation AISBL Membership Application date submitted: {now} {#if includePreamble} Dear {name}, -Thank you for submitting the member enrollment form on behalf of your organization. Our team will review your application and follow up within 2 business days. +Thank you for submitting the Membership Application Form on behalf of your organization. Our team will review your application and follow up within 2 business days. Meanwhile, if you have any questions, please reach out to us at membership.coordination@eclipse-foundation.org. Best regards, diff --git a/src/main/resources/templates/emails/form_membership_email_web_template.html b/src/main/resources/templates/emails/form_membership_email_web_template.html index 59f44740c921503bfe1837cc7a92e4c38ff445ac..2f98f7259762453c2c57b5e6883f490ea58102b9 100644 --- a/src/main/resources/templates/emails/form_membership_email_web_template.html +++ b/src/main/resources/templates/emails/form_membership_email_web_template.html @@ -1,24 +1,26 @@ -
-
+
+
-

Eclipse Foundation AISBL Membership Application date submitted: {now}

+

Eclipse Foundation AISBL Membership Application

+

Date submitted: {time:format(now,'d MMM uuuu, HH:mm')}

{#if includePreamble}

Dear {name},

-

Thank you for submitting the member enrollment form on behalf of your organization. Our team will review your application and follow up within 2 business days.

+

Thank you for submitting the Membership Application Form on behalf of your organization. Our team will review your application and follow up within 2 business days.

Meanwhile, if you have any questions, please reach out to us at membership.coordination@eclipse-foundation.org.

Best regards,

Eclipse Foundation Membership Coordination

{/if} -

New form submission by: {name} ({data.form.userID})

+

Form submission by: {name} ({data.form.userID})

Organization

-

Name: {data.org.legalName}

-

Aggregate Revenue: {data.org.aggregateRevenue}

-

Employee count: {data.org.employeeCount}

+

Name: {data.org.legalName}

+

Aggregate Revenue: {data.org.aggregateRevenue}

+

Employee Count: {data.org.employeeCount}

Organization Type: {data.org.organizationType}

-

Twitter: {data.org.twitterHandle}

-

Requires purchase order: {data.form.purchaseOrderRequired}

-

VAT number: {data.form.vatNumber}

-

VAT Registration country: {data.form.registrationCountry}

+

Twitter: {data.org.twitterHandle}

+

Requires Purchase Order: {data.form.purchaseOrderRequired}

+

VAT Number: {data.form.vatNumber}

+

VAT Registration Country: {data.form.registrationCountry}

+

Membership Level: {data.form.membershipLevel}

Address

@@ -70,7 +72,18 @@ {/for} -


+ {#if isPDF} +

As a new Member:

+
    +
  1. You agree to publicly support the Eclipse Foundation and its Purpose.
  2. +
  3. You acknowledge your commitment in principle to comply with the Bylaws, the Internal Rules, the Eclipse Foundation Antitrust Policy, IP Policy, and any and all additional policies, procedures and other governing rules of the Eclipse Foundation.
  4. +
  5. You agree to provide Eclipse Foundation with your logo (or instructions to obtain your logo) in accordance with Section 2.3 of the Eclipse Foundation Membership Agreement. When providing your logo, be sure to include a reference or link to any logo and trademark usage guidelines you have.
    + Our Membership Coordination team will work with you to complete this after your Membership Application is processed.
  6. +
+

Thank you for completing the Membership Application Process. This PDF captures the information you have provided to us. Please review this information as it becomes a part of your formal application to the Eclipse Foundation.

+

Should you wish to update any of the information, please reach out to our Membership team at membership.coordination@eclipse-foundation.org

+ {/if} +

This is an automatic message from https://membership.eclipse.org

Twitter: @EclipseFdn

diff --git a/src/main/www/src/App.css b/src/main/www/src/App.css index ab73c8ba86071ead8adfe79cbe7679eb1bce329f..0b0ac52483423e5c11ef3227ebc92cf55d62f0c7 100644 --- a/src/main/www/src/App.css +++ b/src/main/www/src/App.css @@ -41,6 +41,8 @@ html { display: flex; align-items: center; justify-content: center; +} +.eclipsefdn-membership-webform .loadingIcon { margin-top: 5rem; margin-bottom: 3rem; } @@ -56,7 +58,7 @@ html { border-color: #ff0000; } .eclipsefdn-membership-webform .btn { - margin: 0 10px; + margin: 10px; border-radius: 5px; } .eclipsefdn-membership-webform .button-container { @@ -116,6 +118,7 @@ html { justify-content: center; position: relative; padding-top: 30px; + cursor: pointer; } .eclipsefdn-membership-webform .step a { text-decoration: none; diff --git a/src/main/www/src/Constants/Constants.js b/src/main/www/src/Constants/Constants.js index 532315930afd5053b91d20919c790f35510818cf..205cafd8d4bc5e3817654dc3cf57397bd9c9075d 100644 --- a/src/main/www/src/Constants/Constants.js +++ b/src/main/www/src/Constants/Constants.js @@ -31,6 +31,7 @@ export const REVIEW = 'Review'; export const HAS_TOKEN_EXPIRED = 'HAS_TOKEN_EXPIRED'; 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 PATH_NAME_ARRAY = [ '/company-info', @@ -57,18 +58,18 @@ export const FETCH_HEADER = { export const MEMBERSHIP_LEVELS = [ { label: 'Strategic Member', value: 'Strategic Member' }, { - label: 'Contributing Member (formerly referred to as Solutions Members)', + label: 'Contributing Member', value: 'Contributing Member', }, { label: 'Associate Member', value: 'Associate Member' }, ]; 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' } }, + { label: COMPANY_INFORMATION, pathName: '/company-info' }, + { label: MEMBERSHIP_LEVEL, pathName: '/membership-level' }, + { label: WORKING_GROUPS, pathName: '/working-groups' }, + { label: SIGNING_AUTHORITY, pathName: '/signing-authority' }, + { label: REVIEW, pathName: '/review' }, ]; export const CONTACT_TYPE = { diff --git a/src/main/www/src/Utils/formFunctionHelpers.js b/src/main/www/src/Utils/formFunctionHelpers.js index 86d78e288464e752ac0764da56d10fd31808af6d..50d201897aa601f1e6792d8c531bd8a07086c386 100644 --- a/src/main/www/src/Utils/formFunctionHelpers.js +++ b/src/main/www/src/Utils/formFunctionHelpers.js @@ -7,7 +7,6 @@ import { getCurrentMode, MODE_REACT_ONLY, MODE_REACT_API, - PATH_NAME_ARRAY, HAS_TOKEN_EXPIRED, } from '../Constants/Constants'; @@ -179,7 +178,8 @@ export function matchContactFields(existingContactData) { */ export function matchWorkingGroupFields( existingworkingGroupData, - workingGroupsOptions + workingGroupsOptions, + existingCompanyContact, ) { var res = []; // Array @@ -187,6 +187,12 @@ export function matchWorkingGroupFields( let wg = workingGroupsOptions?.find( (el) => el.label === item?.working_group_id ); + const basicRepInfo = { + firstName: item?.contact?.first_name || '', + lastName: item?.contact?.last_name || '', + jobtitle: item?.contact?.job_title || '', + email: item?.contact?.email || '', + }; res.push({ id: item?.id || '', workingGroup: @@ -198,11 +204,12 @@ export function matchWorkingGroupFields( participationLevel: item?.participation_level || '', effectiveDate: item?.effective_date?.substring(0, 10) || '', workingGroupRepresentative: { - firstName: item?.contact?.first_name || '', - lastName: item?.contact?.last_name || '', - jobtitle: item?.contact?.job_title || '', - email: item?.contact?.email || '', + ...basicRepInfo, id: item?.contact?.id || '', + sameAsCompany: checkSameContact( + existingCompanyContact, + basicRepInfo + ), }, }); }); @@ -306,9 +313,7 @@ export function matchWGFieldsToBackend(eachWorkingGroupData, formId) { formId ); - const theDate = eachWorkingGroupData?.effectiveDate - ? new Date(eachWorkingGroupData?.effectiveDate) - : new Date(); + const theDate = new Date(); return { id: eachWorkingGroupData?.id, @@ -329,27 +334,14 @@ export function matchWGFieldsToBackend(eachWorkingGroupData, formId) { * @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 */ -export async function executeSendDataByStep( - step, - formData, - formId, - userId, - goToNextStep, - setFieldValueObj -) { - const goToNextStepObj = { - method: goToNextStep, - stepNum: step, - pathName: PATH_NAME_ARRAY[step], - }; +export async function executeSendDataByStep(step, formData, formId, userId, setFieldValueObj) { switch (step) { case 1: - // only need 1 goToNextStepObj in "case 1", or it would execute it 5 times. callSendData( formId, END_POINT.organizations, matchCompanyFieldsToBackend(formData.organization, formId), - goToNextStepObj, + step, { fieldName: setFieldValueObj.fieldName.organization, method: setFieldValueObj.method, @@ -358,12 +350,8 @@ export async function executeSendDataByStep( callSendData( formId, END_POINT.contacts, - matchContactFieldsToBackend( - formData.representative.member, - CONTACT_TYPE.COMPANY, - formId - ), - '', + matchContactFieldsToBackend(formData.representative.member, CONTACT_TYPE.COMPANY, formId), + step, { fieldName: setFieldValueObj.fieldName.member, method: setFieldValueObj.method, @@ -372,12 +360,8 @@ export async function executeSendDataByStep( callSendData( formId, END_POINT.contacts, - matchContactFieldsToBackend( - formData.representative.marketing, - CONTACT_TYPE.MARKETING, - formId - ), - '', + matchContactFieldsToBackend(formData.representative.marketing, CONTACT_TYPE.MARKETING, formId), + step, { fieldName: setFieldValueObj.fieldName.marketing, method: setFieldValueObj.method, @@ -386,12 +370,8 @@ export async function executeSendDataByStep( callSendData( formId, END_POINT.contacts, - matchContactFieldsToBackend( - formData.representative.accounting, - CONTACT_TYPE.ACCOUNTING, - formId - ), - '', + matchContactFieldsToBackend(formData.representative.accounting, CONTACT_TYPE.ACCOUNTING, formId), + step, { fieldName: setFieldValueObj.fieldName.accounting, method: setFieldValueObj.method, @@ -403,15 +383,27 @@ export async function executeSendDataByStep( matchMembershipLevelFieldsToBackend(formData, formId, userId), '' ); + let isWGRepSameAsCompany = false; + formData.workingGroups.map( + (wg) => (isWGRepSameAsCompany = wg.workingGroupRepresentative?.sameAsCompany || isWGRepSameAsCompany) + ); + // only do this API call when there is at least 1 WG rep is same as company rep + if (isWGRepSameAsCompany) { + formData.workingGroups.forEach((item, index) => { + callSendData( + formId, + END_POINT.working_groups, + matchWGFieldsToBackend(item, formId), + '', + setFieldValueObj, + index + ); + }); + } break; case 2: - callSendData( - formId, - '', - matchMembershipLevelFieldsToBackend(formData, formId, userId), - goToNextStepObj - ); + callSendData(formId, '', matchMembershipLevelFieldsToBackend(formData, formId, userId), step); break; case 3: @@ -420,7 +412,7 @@ export async function executeSendDataByStep( formId, END_POINT.working_groups, matchWGFieldsToBackend(item, formId), - goToNextStepObj, + step, setFieldValueObj, index ); @@ -431,24 +423,14 @@ export async function executeSendDataByStep( callSendData( formId, END_POINT.contacts, - matchContactFieldsToBackend( - formData.signingAuthorityRepresentative, - CONTACT_TYPE.SIGNING, - formId - ), - goToNextStepObj, + matchContactFieldsToBackend(formData.signingAuthorityRepresentative, CONTACT_TYPE.SIGNING, formId), + step, setFieldValueObj ); break; case 5: - callSendData( - formId, - END_POINT.complete, - false, - goToNextStepObj, - setFieldValueObj - ); + callSendData(formId, END_POINT.complete, false, step, setFieldValueObj); break; default: @@ -468,7 +450,7 @@ function callSendData( formId, endpoint = '', dataBody, - goToNextStepObj, + stepNum, setFieldValueObj, index ) { @@ -490,9 +472,6 @@ function callSendData( if (getCurrentMode() === MODE_REACT_ONLY) { console.log(`You called ${url} with Method ${method} and data body is:`); console.log(JSON.stringify(dataBody)); - if (goToNextStepObj) { - goToNextStepObj.method(goToNextStepObj.stepNum, goToNextStepObj.pathName); - } } if (getCurrentMode() === MODE_REACT_API) { @@ -502,7 +481,7 @@ function callSendData( body: JSON.stringify(dataBody), }) .then((res) => { - if (goToNextStepObj.stepNum === 5) { + if (stepNum === 5) { if (res.ok) return res; } else { if (res.ok) return res.json(); @@ -573,13 +552,6 @@ function callSendData( break; } } - - if (goToNextStepObj) { - goToNextStepObj.method( - goToNextStepObj.stepNum, - goToNextStepObj.pathName - ); - } }) .catch((err) => { console.log(err); diff --git a/src/main/www/src/components/Application/Application.js b/src/main/www/src/components/Application/Application.js index d2bf601e8a7a9d1cf1f9274fed1b4f1abb4ada50..ac0f1270299048b62ed2dcf325ac67c7ad90306e 100644 --- a/src/main/www/src/components/Application/Application.js +++ b/src/main/www/src/components/Application/Application.js @@ -25,6 +25,8 @@ export default function Application() { const [isStartNewForm, setIsStartNewForm] = useState(true); const [isLoginExpired, setIsLoginExpired] = useState(false); const [isTermChecked, setIsTermChecked] = useState(false); + const [fullWorkingGroupList, setFullWorkingGroupList] = useState([]); + const [workingGroupsUserJoined, setWorkingGroupsUserJoined] = useState([]); const goToNextStep = (pageIndex, nextPage) => { if (furthestPage.index <= pageIndex) @@ -56,175 +58,160 @@ export default function Application() { '', currentFormId, currentUser.name, - goToNextStep, '' ); + goToNextStep(5, '/submitted'); }; + const submitCompanyInfo = (isUsingStepper) => { + const values = formikCompanyInfo.values; + // update the organization values + const organization = values.organization; + const representative = values.representative; + const purchasingAndVAT = values.purchasingAndVAT; + const membershipLevel = values.membershipLevel; + const membershipLevelLabel = values['membershipLevel-label']; + const signingAuthorityRepresentative = values.signingAuthorityRepresentative; + + const theNewValue = { + ...updatedFormValues, + organization, + representative, + purchasingAndVAT, + membershipLevel, + 'membershipLevel-label': membershipLevelLabel, + workingGroups: formikWorkingGroups.values.workingGroups, + signingAuthorityRepresentative: signingAuthorityRepresentative, + }; + setUpdatedFormValues(theNewValue); + console.log('updated company info: ', values); + + const valueForMembershipLevelFormik = [ + { field: 'purchasingAndVAT', value: purchasingAndVAT }, + { field: 'membershipLevel', value: membershipLevel }, + { + field: 'membershipLevel-label', + value: membershipLevelLabel?.label ? membershipLevelLabel : null, + }, + ]; + // set valueToUpdateFormik to membershipLevel formik to make sure the value is up to date + updateMembershipLevelForm(valueForMembershipLevelFormik); + + const valueForSigningAuthorityFormik = [ + { + field: 'signingAuthorityRepresentative', + value: signingAuthorityRepresentative, + }, + ]; + updateSigningAuthorityForm(valueForSigningAuthorityFormik); + + const setFieldValueObj = { + fieldName: { + organization: 'organization', + member: 'representative.member', + accounting: 'representative.accounting', + marketing: 'representative.marketing', + }, + method: formikCompanyInfo.setFieldValue, + }; + + executeSendDataByStep(1, theNewValue, currentFormId, currentUser.name, setFieldValueObj); + // Only need to call goToNextStep when is not using stepper + !isUsingStepper && goToNextStep(1, '/membership-level'); + }; const formikCompanyInfo = useFormik({ initialValues: initialValues, validationSchema: validationSchema[0], - onSubmit: (values) => { - // update the organization values - const organization = values.organization; - const representative = values.representative; - const purchasingAndVAT = values.purchasingAndVAT; - const membershipLevel = values.membershipLevel; - const membershipLevelLabel = values['membershipLevel-label']; - const signingAuthorityRepresentative = - values.signingAuthorityRepresentative; - - const theNewValue = { - ...updatedFormValues, - organization, - representative, - purchasingAndVAT, - membershipLevel, - 'membershipLevel-label': membershipLevelLabel, - signingAuthorityRepresentative: signingAuthorityRepresentative, - }; - setUpdatedFormValues(theNewValue); - console.log('updated company info: ', values); - - const valueForMembershipLevelFormik = [ - { field: 'purchasingAndVAT', value: purchasingAndVAT }, - { field: 'membershipLevel', value: membershipLevel }, - { - field: 'membershipLevel-label', - value: membershipLevelLabel?.label ? membershipLevelLabel : null, - }, - ]; - // set valueToUpdateFormik to membershipLevel formik to make sure the value is up to date - updateMembershipLevelForm(valueForMembershipLevelFormik); - - const valueForSigningAuthorityFormik = [ - { - field: 'signingAuthorityRepresentative', - value: signingAuthorityRepresentative, - }, - ]; - updateSigningAuthorityForm(valueForSigningAuthorityFormik); - - const setFieldValueObj = { - fieldName: { - organization: 'organization', - member: 'representative.member', - accounting: 'representative.accounting', - marketing: 'representative.marketing', - }, - method: formikCompanyInfo.setFieldValue, - }; - executeSendDataByStep( - 1, - theNewValue, - currentFormId, - currentUser.name, - goToNextStep, - setFieldValueObj - ); - }, + onSubmit: () => submitCompanyInfo(), }); + const submitMembershipLevel = (isUsingStepper) => { + const values = formikMembershipLevel.values; + // update the membershipLevel values + const membershipLevel = values.membershipLevel; + const membershipLevelLabel = values['membershipLevel-label']; + setUpdatedFormValues({ + ...updatedFormValues, + membershipLevel, + 'membershipLevel-label': membershipLevelLabel, + }); + console.log('updated membership level: ', values); + + const valueToUpdateFormik = [ + { field: 'membershipLevel', value: membershipLevel }, + { field: 'membershipLevel-label', value: membershipLevelLabel }, + ]; + // set valueToUpdateFormik to CompanyInfo formik to make sure the value is up to date + updateCompanyInfoForm(valueToUpdateFormik); + + executeSendDataByStep(2, values, currentFormId, currentUser.name); + !isUsingStepper && goToNextStep(2, '/working-groups'); + }; const formikMembershipLevel = useFormik({ initialValues: initialValues, validationSchema: validationSchema[1], - onSubmit: (values) => { - // update the membershipLevel values - const membershipLevel = values.membershipLevel; - const membershipLevelLabel = values['membershipLevel-label']; - setUpdatedFormValues({ - ...updatedFormValues, - membershipLevel, - 'membershipLevel-label': membershipLevelLabel, - }); - console.log('updated membership level: ', values); - - const valueToUpdateFormik = [ - { field: 'membershipLevel', value: membershipLevel }, - { field: 'membershipLevel-label', value: membershipLevelLabel }, - ]; - // set valueToUpdateFormik to CompanyInfo formik to make sure the value is up to date - updateCompanyInfoForm(valueToUpdateFormik); - - executeSendDataByStep( - 2, - values, - currentFormId, - currentUser.name, - goToNextStep - ); - }, + onSubmit: () => submitMembershipLevel(), }); + const submitWorkingGroups = (isUsingStepper) => { + const values = formikWorkingGroups.values; + // update the workingGroups values + const workingGroups = values.workingGroups; + setUpdatedFormValues({ ...updatedFormValues, workingGroups }); + console.log('updated working groups: ', values); + + if (!values.skipJoiningWG) { + // If the user is joining at least 1 wg, then make related API call + const setFieldValueObj = { + fieldName: 'workingGroups', + method: formikWorkingGroups.setFieldValue, + }; + + executeSendDataByStep(3, values, currentFormId, currentUser.name, setFieldValueObj); + !isUsingStepper && goToNextStep(3, '/signing-authority'); + } else if (!isUsingStepper) { + // 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({ initialValues: initialValues, validationSchema: validationSchema[2], - onSubmit: (values) => { - // update the workingGroups values - const workingGroups = values.workingGroups; - setUpdatedFormValues({ ...updatedFormValues, workingGroups }); - console.log('updated working groups: ', values); - - if (values.isJoiningWG){ - // If the user is joining at least 1 wg, then make related API call - const setFieldValueObj = { - fieldName: 'workingGroups', - method: formikWorkingGroups.setFieldValue, - }; - - executeSendDataByStep( - 3, - values, - currentFormId, - currentUser.name, - goToNextStep, - setFieldValueObj - ); - } else { - // If the user is NOT joining any wg, then go to next page directly - goToNextStep(3, '/signing-authority') - } - - }, + onSubmit: () => submitWorkingGroups(), }); + const submitSigningAuthority = (isUsingStepper) => { + const values = formikSigningAuthority.values; + // update the signingAuthorityRepresentative values + const signingAuthorityRepresentative = values.signingAuthorityRepresentative; + setUpdatedFormValues({ + ...updatedFormValues, + signingAuthorityRepresentative, + }); + console.log('updated SigningAuthority: ', values); + + const valueToUpdateFormik = [ + { + field: 'signingAuthorityRepresentative', + value: signingAuthorityRepresentative, + }, + ]; + // set valueToUpdateFormik to CompanyInfo formik to make sure the value is up to date + updateCompanyInfoForm(valueToUpdateFormik); + const setFieldValueObj = { + fieldName: 'signingAuthorityRepresentative', + method: { + signingAuthority: formikSigningAuthority.setFieldValue, + companyInfo: formikCompanyInfo.setFieldValue, + }, + }; + executeSendDataByStep(4, values, currentFormId, currentUser.name, setFieldValueObj); + !isUsingStepper && goToNextStep(4, '/review'); + }; const formikSigningAuthority = useFormik({ initialValues: initialValues, validationSchema: validationSchema[3], - onSubmit: (values) => { - // update the signingAuthorityRepresentative values - const signingAuthorityRepresentative = - values.signingAuthorityRepresentative; - setUpdatedFormValues({ - ...updatedFormValues, - signingAuthorityRepresentative, - }); - console.log('updated SigningAuthority: ', values); - - const valueToUpdateFormik = [ - { - field: 'signingAuthorityRepresentative', - value: signingAuthorityRepresentative, - }, - ]; - // set valueToUpdateFormik to CompanyInfo formik to make sure the value is up to date - updateCompanyInfoForm(valueToUpdateFormik); - const setFieldValueObj = { - fieldName: 'signingAuthorityRepresentative', - method: { - signingAuthority: formikSigningAuthority.setFieldValue, - companyInfo: formikCompanyInfo.setFieldValue, - }, - }; - executeSendDataByStep( - 4, - values, - currentFormId, - currentUser.name, - goToNextStep, - setFieldValueObj - ); - }, + onSubmit: () => submitSigningAuthority(), }); const handleLoginExpired = useCallback(() => { @@ -248,16 +235,19 @@ export default function Application() { // generate the step options above the form const renderStepper = () => (
- + {PAGE_STEP.map((pageStep, index) => { return ( ); })} @@ -293,6 +283,10 @@ export default function Application() { ) : ( // if uses are not allowed to visit this page, @@ -316,7 +310,10 @@ export default function Application() { {furthestPage.index >= 3 ? ( ) : ( diff --git a/src/main/www/src/components/Application/CompanyInformation/CompanyInformation.js b/src/main/www/src/components/Application/CompanyInformation/CompanyInformation.js index 4f5691a15e2ac58c645f1eb46f56eb570740acb5..938cbece6e96758e10059c52516f60de1e6952c3 100644 --- a/src/main/www/src/components/Application/CompanyInformation/CompanyInformation.js +++ b/src/main/www/src/components/Application/CompanyInformation/CompanyInformation.js @@ -21,6 +21,10 @@ import { import CustomStepButton from '../../UIComponents/Button/CustomStepButton'; import CompanyInformationVAT from './CompanyInformationVAT'; import { makeStyles } from '@material-ui/core'; +import { + fetchAvailableFullWorkingGroupList, + fetchWorkingGroupsUserJoined, +} from '../WorkingGroups/WorkingGroupsWrapper'; /** * Wrapper for Contacts and Company components @@ -35,6 +39,8 @@ import { makeStyles } from '@material-ui/core'; * - formField: the form field in formModels/formFieldModel.js */ +let hasWGData = false; + const useStyles = makeStyles(() => ({ textField: { marginBottom: 14, @@ -48,10 +54,19 @@ const useStyles = makeStyles(() => ({ let hasOrgData = false; let hasMembershipLevelData = false; -const CompanyInformation = ({ formik, isStartNewForm }) => { +const CompanyInformation = ({ + formik, + isStartNewForm, + formikWG, + fullWorkingGroupList, + setFullWorkingGroupList, + setWorkingGroupsUserJoined, +}) => { const { currentFormId } = useContext(MembershipContext); // current chosen form id const [loading, setLoading] = useState(true); const { setFieldValue } = formik; + const setWGFieldValue = formikWG.setFieldValue; + const companyRep = formik.values.representative.member; useEffect(() => { scrollToTop(); @@ -84,24 +99,12 @@ const CompanyInformation = ({ formik, isStartNewForm }) => { // 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, - } - ), + 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) => @@ -140,10 +143,7 @@ const CompanyInformation = ({ formik, isStartNewForm }) => { // if nested, it will automatically map the properties and values setFieldValue('representative', tempContacts.organizationContacts); - setFieldValue( - 'signingAuthorityRepresentative', - tempContacts.signingAuthorityRepresentative - ); + setFieldValue('signingAuthorityRepresentative', tempContacts.signingAuthorityRepresentative); hasOrgData = true; } setLoading(false); @@ -202,6 +202,35 @@ const CompanyInformation = ({ formik, isStartNewForm }) => { } }, [isStartNewForm, setFieldValue, currentFormId]); + useEffect(() => { + fetchAvailableFullWorkingGroupList(setFullWorkingGroupList); + }, [setFullWorkingGroupList]); + + useEffect(() => { + if (!isStartNewForm && !hasWGData && fullWorkingGroupList.length > 0 && companyRep.firstName) { + // continue with an existing one and there is no working group data + fetchWorkingGroupsUserJoined( + currentFormId, + fullWorkingGroupList, + setWorkingGroupsUserJoined, + setWGFieldValue, + companyRep, + setLoading + ); + hasWGData = true; + } else { + setLoading(false); + } + }, [ + isStartNewForm, + currentFormId, + fullWorkingGroupList, + setFieldValue, + companyRep, + setWGFieldValue, + setWorkingGroupsUserJoined, + ]); + // If it is in loading status or hasn't gotten the form id, // only return a loading spinning if (loading || !currentFormId) { @@ -213,19 +242,23 @@ const CompanyInformation = ({ formik, isStartNewForm }) => {

Company Information

- Please complete your company information below. This should be the - legal name and address of your organization. + Please complete your company information below. This should be the legal name and address of your + organization. +

+

+ **** NOTE: Committers wishing to complete the Eclipse Foundation membership process should not use this form, + but instead should visit{' '} + + here + + .

- +
- + ); }; diff --git a/src/main/www/src/components/Application/CompanyInformation/CompanyInformationCompany.js b/src/main/www/src/components/Application/CompanyInformation/CompanyInformationCompany.js index 1d30502887d087193ccd18e0d7e99f7aba67f215..8c4dc2ea638aa18386d9c460a591cce21f2fb54e 100644 --- a/src/main/www/src/components/Application/CompanyInformation/CompanyInformationCompany.js +++ b/src/main/www/src/components/Application/CompanyInformation/CompanyInformationCompany.js @@ -3,7 +3,12 @@ import { formField } from '../../UIComponents/FormComponents/formFieldModel'; import Autocomplete from '@material-ui/lab/Autocomplete'; import { TextField } from '@material-ui/core'; import DropdownMenu from '../../UIComponents/Inputs/DropdownMenu'; -import { OPTIONS_FOR_ORG_TYPE, OPTIONS_FOR_REVENUE, OPTIONS_FOR_EMPLOYEE_COUNT, HELPERTEXT_FOR_REVENUE } from '../../../Constants/Constants'; +import { + OPTIONS_FOR_ORG_TYPE, + OPTIONS_FOR_REVENUE, + OPTIONS_FOR_EMPLOYEE_COUNT, + HELPERTEXT_FOR_REVENUE, +} from '../../../Constants/Constants'; /** * Render Oraganization selector (used React-Select) @@ -43,6 +48,8 @@ const CompanyInformationCompany = ({ formik, useStyles }) => { requiredMark={true} value={formik.values.organization.legalName} onChange={formik.handleChange} + error={formik.touched.organization?.legalName && Boolean(formik.errors.organization?.legalName)} + helperText={formik.errors.organization?.legalName} />
@@ -54,6 +61,8 @@ const CompanyInformationCompany = ({ formik, useStyles }) => { inputValue={formik.values.organization.type} optionsArray={OPTIONS_FOR_ORG_TYPE} handleChange={formik.handleChange} + error={formik.touched.organization?.type && Boolean(formik.errors.organization?.type)} + helperText={formik.errors.organization?.type} />
@@ -89,8 +98,10 @@ const CompanyInformationCompany = ({ formik, useStyles }) => { inputName={organizationRevenue.revenue.name} inputValue={formik.values.organization.revenue} optionsArray={OPTIONS_FOR_REVENUE} - helperText={HELPERTEXT_FOR_REVENUE} + explanationHelperText={HELPERTEXT_FOR_REVENUE} handleChange={(ev) => handleFieldChange(ev.target.value, 'organization.revenue')} + error={formik.touched.organization?.revenue && Boolean(formik.errors.organization?.revenue)} + helperText={formik.errors.organization?.revenue} />
@@ -100,6 +111,8 @@ const CompanyInformationCompany = ({ formik, useStyles }) => { inputValue={formik.values.organization.employeeCount} optionsArray={OPTIONS_FOR_EMPLOYEE_COUNT} handleChange={(ev) => handleFieldChange(ev.target.value, 'organization.employeeCount')} + error={formik.touched.organization?.employeeCount && Boolean(formik.errors.organization?.employeeCount)} + helperText={formik.errors.organization?.employeeCount} />
@@ -117,6 +130,8 @@ const CompanyInformationCompany = ({ formik, useStyles }) => { value={formik.values.organization.address.street} onChange={formik.handleChange} ariaLabel={`${organizationName.name}-address`} + error={formik.touched.organization?.address?.street && Boolean(formik.errors.organization?.address?.street)} + helperText={formik.errors.organization?.address?.street} />
@@ -128,6 +143,8 @@ const CompanyInformationCompany = ({ formik, useStyles }) => { value={formik.values.organization.address.city} onChange={formik.handleChange} ariaLabel={`${organizationName.name}-address`} + error={formik.touched.organization?.address?.city && Boolean(formik.errors.organization?.address?.city)} + helperText={formik.errors.organization?.address?.city} />
@@ -167,8 +184,8 @@ const CompanyInformationCompany = ({ formik, useStyles }) => { size="small" required={true} className={classes.textField} - error={Boolean(formik.errors.organization?.address?.country)} - helperText={formik.errors.organization?.address?.country} + error={formik.touched.organization?.address?.city && Boolean(formik.errors.organization?.address?.country)} + helperText={formik.touched.organization?.address?.city && formik.errors.organization?.address?.country} /> ); }} @@ -184,6 +201,8 @@ const CompanyInformationCompany = ({ formik, useStyles }) => { value={formik.values.organization.address.provinceOrState} onChange={formik.handleChange} ariaLabel={`${organizationName.name}-address`} + error={Boolean(formik.errors.organization?.address?.provinceOrState)} + helperText={formik.errors.organization?.address?.provinceOrState} /> @@ -196,6 +215,8 @@ const CompanyInformationCompany = ({ formik, useStyles }) => { value={formik.values.organization.address.postalCode} onChange={formik.handleChange} ariaLabel={`${organizationName.name}-address`} + error={Boolean(formik.errors.organization?.address?.postalCode)} + helperText={formik.errors.organization?.address?.postalCode} /> diff --git a/src/main/www/src/components/Application/CompanyInformation/CompanyInformationContacts.js b/src/main/www/src/components/Application/CompanyInformation/CompanyInformationContacts.js index a5915e35b1dfce3cfbd2198a5e1d0d69102606a9..79c2040721a4e944214a23259789e16102765266 100644 --- a/src/main/www/src/components/Application/CompanyInformation/CompanyInformationContacts.js +++ b/src/main/www/src/components/Application/CompanyInformation/CompanyInformationContacts.js @@ -17,7 +17,7 @@ import { Checkbox, FormControlLabel } from '@material-ui/core'; * * @returns */ -const Contacts = ({ formik }) => { +const Contacts = ({ formik, formikWG }) => { // the boolean form value of "is marketing Rep. the same as company Rep.?" const isMarketingSameAsCompany = formik.values.representative.marketing.sameAsCompany; @@ -88,14 +88,26 @@ const Contacts = ({ formik }) => { } formik.setFieldValue('representative', newRepresentativeValue); + + let isWGRepSameAsCompany = false; + const newWG = formikWG.values.workingGroups.map((wg) => { + isWGRepSameAsCompany = wg.workingGroupRepresentative?.sameAsCompany || isWGRepSameAsCompany; + return wg.workingGroupRepresentative?.sameAsCompany + ? { + ...wg, + workingGroupRepresentative: { + ...memberRepInfo, + id: wg.workingGroupRepresentative.id || '', + sameAsCompany: wg.workingGroupRepresentative.sameAsCompany, + }, + } + : wg; + }); + // only call setFieldValue when there is at least 1 wg rep has sameAsCompany: true + isWGRepSameAsCompany && formikWG.setFieldValue('workingGroups', newWG); }; - const generateContacts = ( - representativeFields, - prefix, - type, - disableInput - ) => ( + const generateContacts = (representativeFields, prefix, type, disableInput) => ( <> {representativeFields.map((el, index) => (
@@ -107,12 +119,13 @@ const Contacts = ({ formik }) => { requiredMark={true} disableInput={disableInput} onChange={ - type === 'member' - ? (ev) => handleMemberInputChange(ev.target.value, el.name) - : formik.handleChange + type === 'member' ? (ev) => handleMemberInputChange(ev.target.value, el.name) : formik.handleChange } value={formik.values.representative?.[type]?.[el.name]} - error={Boolean(formik.errors.representative?.[type]?.[el.name])} + error={ + formik.touched.representative?.[type]?.[el.name] && + Boolean(formik.errors.representative?.[type]?.[el.name]) + } helperText={formik.errors.representative?.[type]?.[el.name]} />
diff --git a/src/main/www/src/components/Application/CompanyInformation/CompanyInformationVAT.js b/src/main/www/src/components/Application/CompanyInformation/CompanyInformationVAT.js index 90df1818fbb86b69f8d55d105a751c4596e1cd77..08a7aac93eadd5fb2edd9f5f317c00fa2f55ef93 100644 --- a/src/main/www/src/components/Application/CompanyInformation/CompanyInformationVAT.js +++ b/src/main/www/src/components/Application/CompanyInformation/CompanyInformationVAT.js @@ -23,17 +23,11 @@ export default function CompanyInformationVAT({ formik }) { return ( <> -

+

Purchasing Process *

-

- Does your organization require a Purchase Order to facilitate payment of - your membership dues? -

+

Does your organization require a Purchase Order to facilitate payment of your membership dues?

@@ -72,6 +71,11 @@ export default function CompanyInformationVAT({ formik }) { value={formik.values.purchasingAndVAT.vatNumber} onChange={formik.handleChange} ariaLabel={`vatRegistration`} + error={ + formik.touched.purchasingAndVAT?.vatNumber && + Boolean(formik.errors.purchasingAndVAT?.vatNumber) + } + helperText={formik.errors.purchasingAndVAT?.vatNumber} /> @@ -84,6 +88,11 @@ export default function CompanyInformationVAT({ formik }) { value={formik.values.purchasingAndVAT.countryOfRegistration} onChange={formik.handleChange} ariaLabel={`vatRegistration`} + error={ + formik.touched.purchasingAndVAT?.countryOfRegistration && + Boolean(formik.errors.purchasingAndVAT?.countryOfRegistration) + } + helperText={formik.errors.purchasingAndVAT?.countryOfRegistration} /> diff --git a/src/main/www/src/components/Application/MembershipLevel/MembershipLevel.js b/src/main/www/src/components/Application/MembershipLevel/MembershipLevel.js index 3627a34b8a2e8f844b8cf7bfa7e7d298d1ae35a4..9188e5765dae787632adc9a24522a8e4bce8385b 100644 --- a/src/main/www/src/components/Application/MembershipLevel/MembershipLevel.js +++ b/src/main/www/src/components/Application/MembershipLevel/MembershipLevel.js @@ -41,6 +41,8 @@ const MembershipLevel = ({ formik }) => { inputValue={formik.values.membershipLevel} optionsArray={MEMBERSHIP_LEVELS} handleChange={formik.handleChange} + error={formik.touched.membershipLevel && Boolean(formik.errors.membershipLevel)} + helperText={formik.errors.membershipLevel} /> diff --git a/src/main/www/src/components/Application/Review/Review.tsx b/src/main/www/src/components/Application/Review/Review.tsx index 09e8a20f006920c15a4966fb26798d35b25c1a6e..baa62310ad2640e38a2bb24fe6562a0b90876065 100644 --- a/src/main/www/src/components/Application/Review/Review.tsx +++ b/src/main/www/src/components/Application/Review/Review.tsx @@ -3,6 +3,7 @@ import CustomStepButton from '../../UIComponents/Button/CustomStepButton'; import { FormValue } from '../../../Interfaces/form_interface'; import { scrollToTop } from '../../../Utils/formFunctionHelpers'; import { FormControlLabel, Checkbox } from '@material-ui/core'; +import { OPTIONS_FOR_ORG_TYPE } from '../../../Constants/Constants'; interface ReviewProps { values: FormValue; @@ -39,24 +40,27 @@ const Review: React.FC = ({ values, submitForm, isTermChecked, setI

Company Information

-
{values.organization.legalName}
+ +
{values.organization.legalName}
-
+
-
{values.organization.type}
-
-
- -
{values.organization.twitterHandle}
+
+ {OPTIONS_FOR_ORG_TYPE.find((item) => item.value === values.organization.type)?.label} +
-
+
+ +
{values.organization.twitterHandle}
+
+
{values.organization.revenue}
-
+
{values.organization.employeeCount}
@@ -188,7 +192,7 @@ const Review: React.FC = ({ values, submitForm, isTermChecked, setI
-
{new Date(el.effectiveDate).toLocaleDateString()}
+
{new Date().toLocaleDateString()}
diff --git a/src/main/www/src/components/Application/SignIn/SignIn.js b/src/main/www/src/components/Application/SignIn/SignIn.js index baeb42638d0c08e48950079728e51e06c9dece00..defb3da3eda721a3bf4775181098234dd2b9ea44 100644 --- a/src/main/www/src/components/Application/SignIn/SignIn.js +++ b/src/main/www/src/components/Application/SignIn/SignIn.js @@ -60,26 +60,21 @@ class SignIn extends React.Component { this.context.needLoadingSignIn ? ( ) : ( -
+
+

+ Get started by logging in with your Eclipse Foundation account: +

{getCurrentMode() === MODE_REACT_ONLY && ( - )} {getCurrentMode() === MODE_REACT_API && ( - - Sign In + + Log in )} diff --git a/src/main/www/src/components/Application/SignIn/SignInIntroduction.js b/src/main/www/src/components/Application/SignIn/SignInIntroduction.js index b4f4d5fe982359c771a0588eb96147d6a3f416f6..2ebd1369a1aa407c3d065a7112f06d1fbad9cfb9 100644 --- a/src/main/www/src/components/Application/SignIn/SignInIntroduction.js +++ b/src/main/www/src/components/Application/SignIn/SignInIntroduction.js @@ -1,21 +1,27 @@ const SignInIntroduction = () => { return ( -
-
+
+
+ arrows-icons +
+
-

- Please complete the Member Application Form as part of the overall - membership application and enrolment process. Note that completion of - this Form is a required formal step in the Membership Application - Process. + Once you complete and submit this Application Form, we will forward the various legal agreements for + electronic execution, so please be sure to review these legal documents in advance. For more information, + please see{' '} + + here + + .

diff --git a/src/main/www/src/components/Application/SubmitSuccess/SubmitSuccess.js b/src/main/www/src/components/Application/SubmitSuccess/SubmitSuccess.js index 3d25d2ef3263ecec1435d56e166a0f31abdae1e7..26c7a4c2e9b0ad3d1d16fa880f3bfc6f42408ead 100644 --- a/src/main/www/src/components/Application/SubmitSuccess/SubmitSuccess.js +++ b/src/main/www/src/components/Application/SubmitSuccess/SubmitSuccess.js @@ -13,14 +13,49 @@ const SubmitSuccess = () => { return ( <>

Confirmation message on submission:

-

We thank you for completing the membership application.

+

We thank you for completing the application process.

+

As next steps,

+
    +
  1. +

    + You will receive an automated email capturing the information you have provided. Please review this + information, and let us know of any errors or changes required by emailing{' '} + + membership.coordination@eclipse-foundation.org + + . +

    +
  2. +
  3. +

    + Within the next two business days, our membership coordination team will send via HelloSign’s document + signature system the membership documents to be executed. +

      +
    1. + If you are the signing authority indicated on the application, then the documents will be sent directly + to you. +
    2. +
    3. + If you indicated someone else in your organization is the signing authority, our membership coordination + team will let you know of the status of the signing process - but the documents will be sent directly to + the signing authority. +
    4. +
    +

    +
  4. +
  5. + Once the membership document(s) are executed by your organization, the Eclipse Foundation’s Executive Director + will countersign and the completed documents will be returned to you by our membership coordination team. They + will also begin the ongboarding walk you and your other team members through our onboarding process. +
  6. +

- Our membership coordination team will send a ready to sign membership - documents to the signing authority. If you have not received the - ready-to-sign agreement within 2 business days, please contact{' '} + If you have any questions, or have not received notification of the signing process being underway after 2 + business days, please contact{' '} membership.coordination@eclipse-foundation.org + .

); diff --git a/src/main/www/src/components/Application/WorkingGroups/WorkingGroup.js b/src/main/www/src/components/Application/WorkingGroups/WorkingGroup.js index edf604da323e91b8f9df98d89fa3988e0a79bdae..9c4fe16049d9725050fb85e303e4fa5add8a15c9 100644 --- a/src/main/www/src/components/Application/WorkingGroups/WorkingGroup.js +++ b/src/main/www/src/components/Application/WorkingGroups/WorkingGroup.js @@ -1,7 +1,6 @@ import { useContext } from 'react'; import MembershipContext from '../../../Context/MembershipContext'; import WorkingGroupParticipationLevel from './WorkingGroupParticipationLevel'; -import WorkingGroupEffectiveDate from './WorkingGroupEffectiveDate'; import WorkingGroupsRepresentative from './WorkingGroupRepresentative'; import { deleteData } from '../../../Utils/formFunctionHelpers'; import { @@ -12,7 +11,7 @@ import { import Autocomplete from '@material-ui/lab/Autocomplete'; import { makeStyles, TextField } from '@material-ui/core'; import { FieldArray } from 'formik'; -import Loading from '../../UIComponents/Loading/Loading'; +import { initialValues } from '../../UIComponents/FormComponents/formFieldModel'; /** * Wrapper for Working Group Selector, @@ -27,19 +26,7 @@ import Loading from '../../UIComponents/Loading/Loading'; * - formField: the form field in formModels/formFieldModel.js */ -const each_workingGroupField = { - id: '', - workingGroup: '', - participationLevel: '', - effectiveDate: '', - workingGroupRepresentative: { - firstName: '', - lastName: '', - jobtitle: '', - email: '', - id: '', - }, -}; +const each_workingGroupField = initialValues.workingGroups[0]; const useStyles = makeStyles(() => ({ textField: { @@ -51,7 +38,7 @@ const useStyles = makeStyles(() => ({ }, })); -const WorkingGroup = ({ formik, fullWorkingGroupList, isLoading }) => { +const WorkingGroup = ({ formik, fullWorkingGroupList, formikOrgValue }) => { const classes = useStyles(); const { currentFormId } = useContext(MembershipContext); @@ -87,9 +74,7 @@ const WorkingGroup = ({ formik, fullWorkingGroupList, isLoading }) => { } }; - return isLoading ? ( - - ) : ( + return ( ( @@ -158,30 +143,17 @@ const WorkingGroup = ({ formik, fullWorkingGroupList, isLoading }) => { const wgLabelPropery = `${workingGroupsLabel}.${index}.workingGroup-label`; // if array.find returns a wg obejct, then it means it's already selected - const selectedWGValue = - formik.values.workingGroups.find((item) => { - if ( - item.workingGroup?.label === inputValue && - item['workingGroup-label'] === inputValue - ) { - return true; - } else { - return false; - } - }); + const selectedWGValue = formik.values.workingGroups.find( + (item) => + item.workingGroup?.label === inputValue && item['workingGroup-label'] === inputValue + ); // if the wg user types is already selected somewhere else, // then make the validation fail and show error message if (selectedWGValue) { - formik.setFieldValue( - wgLabelPropery, - `${inputValue} already selected` - ); + formik.setFieldValue(wgLabelPropery, `${inputValue} already selected`); } else { - formik.setFieldValue( - wgLabelPropery, - inputValue || null - ); + formik.setFieldValue(wgLabelPropery, inputValue || null); } }} label={WORKING_GROUPS} @@ -191,9 +163,11 @@ const WorkingGroup = ({ formik, fullWorkingGroupList, isLoading }) => { required={true} className={classes.textField} error={Boolean( - formik.errors.workingGroups?.[index]?.['workingGroup'] + formik.touched.workingGroups?.[index]?.['workingGroup'] && + formik.errors.workingGroups?.[index]?.['workingGroup'] )} helperText={ + formik.touched.workingGroups?.[index]?.['workingGroup'] && formik.errors.workingGroups?.[index]?.['workingGroup'] } /> @@ -211,13 +185,8 @@ const WorkingGroup = ({ formik, fullWorkingGroupList, isLoading }) => { fullWorkingGroupList={fullWorkingGroupList} formik={formik} /> - { - const theIndex = index; - return ( - <> -

- What is the effective date for your Membership Agreement/ Working Group - Participation Agreement? - * -

-
-
- -
-
- - ); -}; - -export default EffectiveDate; diff --git a/src/main/www/src/components/Application/WorkingGroups/WorkingGroupParticipationLevel.js b/src/main/www/src/components/Application/WorkingGroups/WorkingGroupParticipationLevel.js index abdcdf4a4d49f10877e813660078fca21a3e3730..99583052d19058e532c274583d68688054626a4f 100644 --- a/src/main/www/src/components/Application/WorkingGroups/WorkingGroupParticipationLevel.js +++ b/src/main/www/src/components/Application/WorkingGroups/WorkingGroupParticipationLevel.js @@ -13,8 +13,8 @@ import DropdownMenu from '../../UIComponents/Inputs/DropdownMenu'; const ParticipationLevel = ({ name, workingGroupUserJoined, fullWorkingGroupList, formik, index }) => { const [participationLevelOptions, setParticipationLevelOptions] = useState([]); const [selectedWG, setSelectedWG] = useState(); - const theIndex = index; + useEffect(() => { // If have selected working group, find this working group's // participation levels, and pass to the react-select option @@ -51,16 +51,26 @@ const ParticipationLevel = ({ name, workingGroupUserJoined, fullWorkingGroupList inputValue={formik.values.workingGroups[theIndex]['participationLevel']} optionsArray={participationLevelOptions} handleChange={formik.handleChange} + error={ + formik.touched.workingGroups?.[theIndex]?.['participationLevel'] && + Boolean(formik.errors.workingGroups?.[theIndex]?.['participationLevel']) + } + helperText={formik.errors.workingGroups?.[theIndex]?.['participationLevel']} /> )}

- You can find additional information about {selectedWG?.label} in the{' '} + Each Working Group has different participation levels and restrictions on who can join at those levels (e.g., + Guest Member level is typically restricted to non-profit organizations). See the{' '} - {selectedWG?.label} charter - - . + charter + {' '} + for full details on which choice is best for you, and to see the working group fees associated with joining this + working group. Note: working group fees are in addition to the membership fees associated with joining the + Eclipse Foundation. Please{' '} + contact our membership team with any + questions.

); diff --git a/src/main/www/src/components/Application/WorkingGroups/WorkingGroupRepresentative.js b/src/main/www/src/components/Application/WorkingGroups/WorkingGroupRepresentative.js index 91721787d5861443c6be3a9bf846bc71c396292d..51ac5d51c26c8a9d2788ccebcdc7eb15a38e629a 100644 --- a/src/main/www/src/components/Application/WorkingGroups/WorkingGroupRepresentative.js +++ b/src/main/www/src/components/Application/WorkingGroups/WorkingGroupRepresentative.js @@ -1,5 +1,6 @@ import Input from '../../UIComponents/Inputs/Input'; import { formField } from '../../UIComponents/FormComponents/formFieldModel'; +import { Checkbox, FormControlLabel } from '@material-ui/core'; /** * Render Working Group Representative input component @@ -9,15 +10,40 @@ import { formField } from '../../UIComponents/FormComponents/formFieldModel'; * * - formField: the form field in formModels/formFieldModel.js */ -const WorkingGroupRepresentative = ({ name, index, formik }) => { +const WorkingGroupRepresentative = ({ name, index, formik, formikOrgValue }) => { const { workingGroupRepresentative } = formField; const theIndex = index; + + const handleCheckboxChange = (isChecked) => { + const repInfo = isChecked + ? formikOrgValue.representative.member + : formik.values.workingGroups[theIndex].workingGroupRepresentative; + + const newValues = { + ...repInfo, + sameAsCompany: isChecked, + id: formik.values.workingGroups[theIndex].workingGroupRepresentative.id, + }; + formik.setFieldValue(`workingGroups[${theIndex}].workingGroupRepresentative`, newValues); + }; + return ( <>

- Who is the working group representative? + Who is to serve as your organization’s Member Representative with the Working Group? *

+ handleCheckboxChange(ev.target.checked, 'marketing')} + /> + } + label="Same as Member Representative" + />
{workingGroupRepresentative.map((el) => (
@@ -28,23 +54,15 @@ const WorkingGroupRepresentative = ({ name, index, formik }) => { ariaLabel={`${name} ${name}.${el.name}`} onChange={formik.handleChange} requiredMark={true} - value={ - formik.values.workingGroups[theIndex] - .workingGroupRepresentative[`${el.name}`] - } + disableInput={formik.values.workingGroups[theIndex].workingGroupRepresentative.sameAsCompany} + value={formik.values.workingGroups[theIndex].workingGroupRepresentative[`${el.name}`]} error={ - formik.touched.workingGroups?.[theIndex] - ?.workingGroupRepresentative?.[`${el.name}`] && - Boolean( - formik.errors.workingGroups?.[theIndex] - ?.workingGroupRepresentative?.[`${el.name}`] - ) + formik.touched.workingGroups?.[theIndex]?.workingGroupRepresentative?.[`${el.name}`] && + Boolean(formik.errors.workingGroups?.[theIndex]?.workingGroupRepresentative?.[`${el.name}`]) } helperText={ - formik.touched.workingGroups?.[theIndex] - ?.workingGroupRepresentative?.[`${el.name}`] && - formik.errors.workingGroups?.[theIndex] - ?.workingGroupRepresentative?.[`${el.name}`] + formik.touched.workingGroups?.[theIndex]?.workingGroupRepresentative?.[`${el.name}`] && + formik.errors.workingGroups?.[theIndex]?.workingGroupRepresentative?.[`${el.name}`] } />
diff --git a/src/main/www/src/components/Application/WorkingGroups/WorkingGroupsWrapper.js b/src/main/www/src/components/Application/WorkingGroups/WorkingGroupsWrapper.js index d5ba5a6e4b753b37c151b01c21e92882883241d5..4ad4042e51a14243ae6a25e5c75f23943e7de42a 100644 --- a/src/main/www/src/components/Application/WorkingGroups/WorkingGroupsWrapper.js +++ b/src/main/www/src/components/Application/WorkingGroups/WorkingGroupsWrapper.js @@ -7,7 +7,6 @@ import { requestErrorHandler, scrollToTop, } from '../../../Utils/formFunctionHelpers'; -import Loading from '../../UIComponents/Loading/Loading'; import { END_POINT, API_PREFIX_FORM, @@ -48,24 +47,16 @@ import { initialValues } from '../../UIComponents/FormComponents/formFieldModel' * - formField: the form field in formModels/formFieldModel.js */ -let hasWGData = false; - -const WorkingGroupsWrapper = ({ formik, isStartNewForm }) => { +const WorkingGroupsWrapper = ({ formik, formikOrgValue, fullWorkingGroupList, workingGroupsUserJoined }) => { const { currentFormId } = useContext(MembershipContext); - const { setFieldValue } = formik; - const [isLoading, setIsLoading] = useState(true); - const [workingGroupsUserJoined, setWorkingGroupsUserJoined] = useState([]); - const [fullWorkingGroupList, setFullWorkingGroupList] = useState([]); const [shouldOpen, setShouldOpen] = useState(false); - const handleIsJoiningWG = () => { - const isJoiningWG = formik.values.isJoiningWG; - - if (isJoiningWG) { - setShouldOpen(true); + const handleSkipJoiningWG = () => { + const skipJoiningWG = formik.values.skipJoiningWG; + if (skipJoiningWG) { + formik.setFieldValue('skipJoiningWG', !skipJoiningWG); } else { - formik.setFieldValue('isJoiningWG', !isJoiningWG); - formik.setFieldValue('workingGroups', initialValues.workingGroups); + setShouldOpen(true); } }; @@ -74,17 +65,13 @@ const WorkingGroupsWrapper = ({ formik, isStartNewForm }) => { }; const handleClearData = () => { - // if user uncheck it, then we need to reset WG form + // if user check it, we need to delete all wgs in formik and db formik.values.workingGroups.map((item) => { - deleteData( - currentFormId, - END_POINT.working_groups, - item.id, - formik.resetForm, - '' - ); + deleteData(currentFormId, END_POINT.working_groups, item.id, console.log, `Deleted ${item?.workingGroup?.label}`); return null; }); + formik.setFieldValue('skipJoiningWG', true); + formik.setFieldValue('workingGroups', initialValues.workingGroups); closeModal(); }; @@ -92,137 +79,20 @@ const WorkingGroupsWrapper = ({ formik, isStartNewForm }) => { scrollToTop(); }, []); - // Fetch data only once and prefill data, as long as - // fetchWorkingGroupsData Function does not change, - // will not cause re-render again - useEffect(() => { - // Fetch the full availabe working group list that user can join - const fetchAvailableFullWorkingGroupList = () => { - let url_prefix_local; - if (getCurrentMode() === MODE_REACT_ONLY) { - url_prefix_local = 'membership_data'; - setFullWorkingGroupList(FULL_WORKING_GROUP_LIST_FOR_REACT_ONLY); - return; - } - - if (getCurrentMode() === MODE_REACT_API) { - url_prefix_local = api_prefix() + '/'; - } - - fetch(url_prefix_local + END_POINT.working_groups, { - headers: FETCH_HEADER, - }) - .then((res) => { - if (res.ok) return res.json(); - - requestErrorHandler(res.status); - throw res.status; - }) - .then((data) => { - let options = data.map((item) => ({ - label: item.title, - value: item.title, - participation_levels: item.levels, - charter: item.resources.charter - })); - setFullWorkingGroupList(options); - }) - .catch((err) => { - requestErrorHandler(err); - console.log(err); - }); - }; - - fetchAvailableFullWorkingGroupList(); - }, []); - - useEffect(() => { - // Fetch the working groups user has joined - const fetchWorkingGroupsUserJoined = () => { - // All pre-process: if running without server, - // use fake json data; if running with API, use API - - let url_prefix_local; - let url_suffix_local = ''; - if (getCurrentMode() === MODE_REACT_ONLY) { - url_prefix_local = 'membership_data'; - url_suffix_local = '.json'; - } - - if (getCurrentMode() === MODE_REACT_API) { - url_prefix_local = API_PREFIX_FORM; - } - - fetch( - url_prefix_local + - `/${currentFormId}/` + - END_POINT.working_groups + - url_suffix_local, - { - headers: FETCH_HEADER, - } - ) - .then((res) => { - if (res.ok) return res.json(); - - requestErrorHandler(res.status); - throw res.status; - }) - .then((data) => { - if (data.length) { - // matchWorkingGroupFields(): Call the the function to map - // the retrived working groups backend data to fit frontend, and - // setFieldValue(): Prefill Data --> Call the setFieldValue - // of Formik, to set workingGroups field with the mapped data - const theGroupsUserJoined = matchWorkingGroupFields( - data, - fullWorkingGroupList - ); - setWorkingGroupsUserJoined(theGroupsUserJoined); - setFieldValue('isJoiningWG', true); - setFieldValue('workingGroups', theGroupsUserJoined); - hasWGData = true; - } - setIsLoading(false); - }) - .catch((err) => { - requestErrorHandler(err); - console.log(err); - }); - }; - - if (!isStartNewForm && !hasWGData && fullWorkingGroupList.length > 0) { - // continue with an existing one and there is no working group data - fetchWorkingGroupsUserJoined(); - } else { - setIsLoading(false); - } - }, [isStartNewForm, currentFormId, fullWorkingGroupList, setFieldValue]); - - if (isLoading) { - return ; - } - return (
-
+
- - {'Uncheck Joining a Working Group'} - + {'Skip Joining a Working Group'} - This will clear all saved data in this step. Proceed to - uncheck? + This will clear all saved data in this step. Proceed to uncheck? @@ -234,41 +104,129 @@ const WorkingGroupsWrapper = ({ formik, isStartNewForm }) => {

Working Group

+

+ Eclipse Foundation hosts a number of industry collaboration initiatives called Working Groups. While not + required, most Member organizations participate in one or more working groups. See a full list of Eclipse + Foundation working groups. +

+

+ Please complete the following details for joining a Working Group or you can skip joining a Working Group. +

handleIsJoiningWG()} + checked={formik.values.skipJoiningWG} + onChange={() => handleSkipJoiningWG()} /> } - label="Joining a Working Group" + label="Skip joining a Working Group" /> - {formik.values.isJoiningWG && ( + {!formik.values.skipJoiningWG && ( <> -

- Please complete the following details for joining a Working - Group -

+

Please complete the following details for joining a Working Group

)}
- + ); }; export default WorkingGroupsWrapper; + +// Fetch the working groups user has joined +export const fetchWorkingGroupsUserJoined = ( + currentFormId, + fullWorkingGroupList, + setWorkingGroupsUserJoined, + setWGFieldValue, + companyRep, + setLoading +) => { + // All pre-process: if running without server, + // use fake json data; if running with API, use API + + let url_prefix_local; + let url_suffix_local = ''; + if (getCurrentMode() === MODE_REACT_ONLY) { + url_prefix_local = 'membership_data'; + url_suffix_local = '.json'; + } + + if (getCurrentMode() === MODE_REACT_API) { + url_prefix_local = API_PREFIX_FORM; + } + + fetch(url_prefix_local + `/${currentFormId}/` + END_POINT.working_groups + url_suffix_local, { + headers: FETCH_HEADER, + }) + .then((res) => { + if (res.ok) return res.json(); + + requestErrorHandler(res.status); + throw res.status; + }) + .then((data) => { + if (data.length) { + // matchWorkingGroupFields(): Call the the function to map + // the retrived working groups backend data to fit frontend, and + // setFieldValue(): Prefill Data --> Call the setFieldValue + // of Formik, to set workingGroups field with the mapped data + const theGroupsUserJoined = matchWorkingGroupFields(data, fullWorkingGroupList, companyRep); + setWorkingGroupsUserJoined(theGroupsUserJoined); + setWGFieldValue('workingGroups', theGroupsUserJoined); + } + setLoading(false); + }) + .catch((err) => { + requestErrorHandler(err); + console.log(err); + }); +}; + +// Fetch the full availabe working group list that user can join +export const fetchAvailableFullWorkingGroupList = (setFullWorkingGroupList) => { + let url_prefix_local; + if (getCurrentMode() === MODE_REACT_ONLY) { + url_prefix_local = 'membership_data'; + setFullWorkingGroupList(FULL_WORKING_GROUP_LIST_FOR_REACT_ONLY); + return; + } + + if (getCurrentMode() === MODE_REACT_API) { + url_prefix_local = api_prefix() + '/'; + } + + fetch(url_prefix_local + END_POINT.working_groups, { + headers: FETCH_HEADER, + }) + .then((res) => { + if (res.ok) return res.json(); + + requestErrorHandler(res.status); + throw res.status; + }) + .then((data) => { + let options = data.map((item) => ({ + label: item.title, + value: item.title, + participation_levels: item.levels, + charter: item.resources.charter, + })); + setFullWorkingGroupList(options); + }) + .catch((err) => { + requestErrorHandler(err); + console.log(err); + }); +}; diff --git a/src/main/www/src/components/ErrorPages/InternalError50x.js b/src/main/www/src/components/ErrorPages/InternalError50x.js index 3fd22e68f88cc37211a93506a3912989b3b8456b..ff9f42f0b9e5bbb838d3cd6e7510ff8273e6f689 100644 --- a/src/main/www/src/components/ErrorPages/InternalError50x.js +++ b/src/main/www/src/components/ErrorPages/InternalError50x.js @@ -1,9 +1,14 @@ +import { Button } from "@material-ui/core"; + export default function InternalError50x() { return ( - <> +

An unexpected error occurred

Sorry, the page you are looking for is currently unavailable.

Please try again later.

- + +
); } diff --git a/src/main/www/src/components/UIComponents/FormComponents/ValidationSchema.js b/src/main/www/src/components/UIComponents/FormComponents/ValidationSchema.js index 3a8406c9c39d1b2f975e27c728606b4a1cf7a867..3a29ddc10e1c02f4f3447ab874ac2f9c8f8041b6 100644 --- a/src/main/www/src/components/UIComponents/FormComponents/ValidationSchema.js +++ b/src/main/www/src/components/UIComponents/FormComponents/ValidationSchema.js @@ -1,4 +1,5 @@ import * as yup from 'yup'; +import { MAX_LENGTH_HELPER_TEXT } from '../../../Constants/Constants'; import { requiredErrorMsg } from './formFieldModel'; /** @@ -35,6 +36,15 @@ const countryList = require('country-list') .getNames() .map((item) => item); +const REQUIRED_MAX_YUP = yup.string().required(requiredErrorMsg).max(255, MAX_LENGTH_HELPER_TEXT); +const MAX_YUP = yup.string().max(255, MAX_LENGTH_HELPER_TEXT); +const CONTACT_YUP = yup.object().shape({ + email: yup.string().required(requiredErrorMsg).email('Please enter a valid email'), + firstName: REQUIRED_MAX_YUP, + lastName: REQUIRED_MAX_YUP, + jobtitle: REQUIRED_MAX_YUP, +}); + export const validationSchema = [ // First step - company Info yup.object().shape({ @@ -43,9 +53,17 @@ export const validationSchema = [ address: yup.object().shape({ country: yup .mixed() + .required('Please enter/select a valid country name') .oneOf(countryList, 'Please enter/select a valid country name'), + street: REQUIRED_MAX_YUP, + provinceOrState: MAX_YUP, + postalCode: MAX_YUP, + city: REQUIRED_MAX_YUP, }), - + legalName: REQUIRED_MAX_YUP, + revenue: REQUIRED_MAX_YUP, + employeeCount: REQUIRED_MAX_YUP, + type: REQUIRED_MAX_YUP, twitterHandle: yup .string() .min(2, 'Twitter handle is too short') @@ -53,58 +71,47 @@ export const validationSchema = [ .matches(/^@([A-Za-z0-9_])*$/, 'Please enter a valid Twitter handle'), }), representative: yup.object().shape({ - member: yup.object().shape({ - email: yup.string().email('Please enter a valid email'), - }), - marketing: yup.object().shape({ - email: yup.string().email('Please enter a valid email'), - }), - accounting: yup.object().shape({ - email: yup.string().email('Please enter a valid email'), - }), + member: CONTACT_YUP, + marketing: CONTACT_YUP, + accounting: CONTACT_YUP, + }), + purchasingAndVAT: yup.object().shape({ + purchasingProcess: REQUIRED_MAX_YUP, + vatNumber: MAX_YUP, + countryOfRegistration: MAX_YUP, }), }), // Second step - membership level yup.object().shape({ - 'membershipLevel-label': yup.mixed(), + membershipLevel: REQUIRED_MAX_YUP, }), // Third step - working groups yup.object().shape({ - workingGroups: yup.array().of( - yup.object().shape({ - workingGroup: yup - .object() - .nullable() - .test( - 'workingGroup', - 'Please enter/select a valid working group', - function (selectedWG) { + workingGroups: yup.array().when('skipJoiningWG', { + is: false, + then: yup.array().of( + yup.object().shape({ + workingGroup: yup + .object() + .nullable() + .required('Please enter/select a valid working group') + .test('workingGroup', 'Please enter/select a valid working group', function (selectedWG) { const allWorkingGroups = this.options.parent?.allWorkingGroups; const typedWG = this.options.parent?.['workingGroup-label']; - const isValid = - allWorkingGroups?.includes(typedWG) && selectedWG?.label - ? true - : false; - + const isValid = allWorkingGroups?.includes(typedWG) && selectedWG?.label ? true : false; return typedWG ? isValid : true; - } - ), - workingGroupRepresentative: yup.object().shape({ - email: yup.string().email('Please enter a valid email'), - }), - }) - ), + }), + participationLevel: REQUIRED_MAX_YUP, + workingGroupRepresentative: CONTACT_YUP, + }) + ), + }), }), // Forth, signing Authority yup.object().shape({ - signingAuthorityRepresentative: yup.object().shape({ - firstName: yup.string().required(`${requiredErrorMsg}`), - lastName: yup.string().required(`${requiredErrorMsg}`), - jobtitle: yup.string().required(`${requiredErrorMsg}`), - email: yup.string().email('Please enter a valid email'), - }), + signingAuthorityRepresentative: CONTACT_YUP, }), ]; diff --git a/src/main/www/src/components/UIComponents/FormComponents/formFieldModel.js b/src/main/www/src/components/UIComponents/FormComponents/formFieldModel.js index c6761f8b5c61a9a4ca337967e20abe754ec254a6..8d215ba6d10757578d2fcd2e3f7af9780365f340 100644 --- a/src/main/www/src/components/UIComponents/FormComponents/formFieldModel.js +++ b/src/main/www/src/components/UIComponents/FormComponents/formFieldModel.js @@ -11,14 +11,14 @@ const provinceOrState = 'Province Or State'; const postalCode = 'Postal Code'; const country = 'Country'; const jobtitle = 'Job Title'; -const purchasingProcess = 'Require or Not'; +const purchasingProcess = 'Purchasing Process'; const vatNumber = 'VAT Number'; const countryOfRegistration = 'Country of Registration'; const REVENUE = 'Revenue'; const EMPLOYEE_COUNT = 'Employee Count'; const ORG_TYPE = 'Organization Type'; -export const requiredErrorMsg = 'is required'; +export const requiredErrorMsg = 'Required field'; // Initial values passed to Formik, this defines // the form fields, names, and nesting relations of the whole form @@ -95,11 +95,12 @@ export const initialValues = { jobtitle: '', email: '', id: '', + sameAsCompany: false, }, }, ], - isJoiningWG: false, + skipJoiningWG: false, signingAuthorityRepresentative: { firstName: '', diff --git a/src/main/www/src/components/UIComponents/FormPreprocess/FormChooser.js b/src/main/www/src/components/UIComponents/FormPreprocess/FormChooser.js index 52973c4140aecae84ccff0035dbfb5d2ed96739a..7aa967c9eeb240fe1dfae8a2d0827744b8c907f7 100644 --- a/src/main/www/src/components/UIComponents/FormPreprocess/FormChooser.js +++ b/src/main/www/src/components/UIComponents/FormPreprocess/FormChooser.js @@ -14,7 +14,7 @@ import { import { useCallback, useContext, useEffect, useState } from 'react'; import Loading from '../Loading/Loading'; const styles = { - marginBottom: '20px', + marginBottom: '30px', textAlign: 'center', }; @@ -41,6 +41,7 @@ const FormChooser = ({ }; const handleStartNewForm = () => { + setIsStartNewForm(true); if (getCurrentMode() === MODE_REACT_API) setCurrentFormId(''); // reset the form if user has gone to a further page/step if (furthestPage.index > 0) { @@ -99,7 +100,7 @@ const FormChooser = ({ ) : (
-

+

Welcome back! You can continue the application you previously started or start a new application.

diff --git a/src/main/www/src/components/UIComponents/Inputs/DropdownMenu.js b/src/main/www/src/components/UIComponents/Inputs/DropdownMenu.js index 083b7bbcc9a8bace93a82445cce0cd1e5586b144..ae52d81200580834214ed7e52461818989d78621 100644 --- a/src/main/www/src/components/UIComponents/Inputs/DropdownMenu.js +++ b/src/main/www/src/components/UIComponents/Inputs/DropdownMenu.js @@ -14,11 +14,11 @@ const useStyles = makeStyles(() => ({ }, })); -export default function DropdownMenu({ inputLabel, inputName, inputValue, optionsArray, handleChange, helperText }) { +export default function DropdownMenu({ inputLabel, inputName, inputValue, optionsArray, handleChange, explanationHelperText, error, helperText }) { const classes = useStyles(); return ( - + {inputLabel} - {helperText && {helperText}} + {error && {helperText}} + {explanationHelperText && {explanationHelperText}} ); } diff --git a/src/main/www/src/components/UIComponents/Inputs/Input.js b/src/main/www/src/components/UIComponents/Inputs/Input.js index 7163227f4831c63da41d6d0ba91812a9f676695a..0434d0b035481b2dc09d52754bd7cd95a0439a6a 100644 --- a/src/main/www/src/components/UIComponents/Inputs/Input.js +++ b/src/main/www/src/components/UIComponents/Inputs/Input.js @@ -51,7 +51,7 @@ export default function Input(props) { value={value} onChange={onChange} error={error} - helperText={helperText} + helperText={error && helperText} size="small" variant="outlined" className={classes.root} @@ -62,6 +62,7 @@ export default function Input(props) { className: classes.input, inputProps: { 'aria-labelledby': ariaLabel, + maxLength: 255, }, }} /> diff --git a/src/main/www/src/components/UIComponents/Loading/Loading.js b/src/main/www/src/components/UIComponents/Loading/Loading.js index aa9d3bb3fd2185725e149126572b5a260d901f0a..97cabae7effa705b64d3f15d70afaeb2c5754265 100644 --- a/src/main/www/src/components/UIComponents/Loading/Loading.js +++ b/src/main/www/src/components/UIComponents/Loading/Loading.js @@ -1,8 +1,6 @@ -import React from 'react'; - const Loading = () => { return ( -
+
); diff --git a/src/main/www/src/components/UIComponents/Steppers/Step.js b/src/main/www/src/components/UIComponents/Steppers/Step.js index daa718b3f3f872f35ba4b02ca7e4f384adc4307a..d534febd2ae7d46e378db5fa6517ef04d7455365 100644 --- a/src/main/www/src/components/UIComponents/Steppers/Step.js +++ b/src/main/www/src/components/UIComponents/Steppers/Step.js @@ -1,4 +1,7 @@ -import { NavLink, useRouteMatch } from 'react-router-dom'; +import { useContext } from 'react'; +import { useHistory } from 'react-router-dom'; +import { useRouteMatch } from 'react-router-dom'; +import MembershipContext from '../../../Context/MembershipContext'; /** * Props: @@ -15,24 +18,46 @@ import { NavLink, useRouteMatch } from 'react-router-dom'; * - formRef: Passed from FormikStepper. Can use Formik API * **/ -const Step = (props) => { - const { index, title, pathName } = props; +const Step = ({ + index, + title, + pathName, + submitCompanyInfo, + submitMembershipLevel, + submitWorkingGroups, + submitSigningAuthority, +}) => { const isActive = useRouteMatch(pathName); - + const history = useHistory(); + const { furthestPage } = useContext(MembershipContext); + const handleSubmit = () => { + // Only call submit func when user can navigate using stepper + switch (window.location.hash) { + case '#company-info': + furthestPage.index > 1 && submitCompanyInfo(true); + break; + case '#membership-level': + furthestPage.index > 2 && submitMembershipLevel(true); + break; + case '#working-groups': + furthestPage.index > 3 && submitWorkingGroups(true); + break; + case '#signing-authority': + furthestPage.index > 4 && submitSigningAuthority(true); + break; + default: + break; + } + history.push(pathName); + }; return ( -
- - {index + 2} -
-
- {title} -
+
+ {index + 2} +
+
+ {title}
- +
); }; diff --git a/src/main/www/src/less/App.less b/src/main/www/src/less/App.less index 912afceee312d0bfb095bd218c97002c6a839c3c..ccfa40c8ec475a7539697ce776aa7f209e18a36c 100644 --- a/src/main/www/src/less/App.less +++ b/src/main/www/src/less/App.less @@ -96,6 +96,9 @@ html { display: flex; align-items: center; justify-content: center; + } + + .loadingIcon { margin-top: 5rem; margin-bottom: 3rem; } @@ -115,7 +118,7 @@ html { } .btn { - margin: 0 10px; + margin: 10px; border-radius: @border-radius; } @@ -187,6 +190,7 @@ html { justify-content: center; position: relative; padding-top: 30px; + cursor: pointer; } a { text-decoration: none;