diff --git a/FeatureFlags.js b/FeatureFlags.js index a7bfdf73f..079f56ce1 100644 --- a/FeatureFlags.js +++ b/FeatureFlags.js @@ -44,6 +44,7 @@ const CAREERS_CONTACT_FORM = [...GLOBAL_PROD, ...GLOBAL_DEV] const CONTACT_EQUINOR_FORM = [...GLOBAL_PROD, ...GLOBAL_DEV] const ORDER_REPORT_FORM = [...GLOBAL_PROD, ...GLOBAL_DEV] const CAREER_FAIR_AND_VISITS_FORM = [...GLOBAL_PROD, ...GLOBAL_DEV, 'brazil'] +const PENSION_FORM = [...GLOBAL_PROD, ...GLOBAL_DEV] const FANCY_MENU = [...GLOBAL_PROD, ...GLOBAL_DEV] /* LANDING_PAGE requires FANCY_MENU to work */ @@ -73,13 +74,15 @@ export default (dataset) => ({ CAREER_FAIR_AND_VISITS_FORM.includes(dataset) || CONTACT_EQUINOR_FORM.includes(dataset) || ORDER_REPORT_FORM.includes(dataset) || - SUBSCRIBE_FORM.includes(dataset), + SUBSCRIBE_FORM.includes(dataset) || + PENSION_FORM.includes(dataset), HAS_SUBSCRIBE_FORM: SUBSCRIBE_FORM.includes(dataset), HAS_CAREERS_CONTACT_FORM: CAREERS_CONTACT_FORM.includes(dataset), HAS_CAREER_FAIR_AND_VISITS_FORM: CAREER_FAIR_AND_VISITS_FORM.includes(dataset), HAS_ORDER_REPORT_FORM: ORDER_REPORT_FORM.includes(dataset), HAS_CONTACT_EQUINOR_FORM: CONTACT_EQUINOR_FORM.includes(dataset), + HAS_PENSION_FORM: PENSION_FORM.includes(dataset), HAS_FANCY_MENU: FANCY_MENU.includes(dataset), /* LANDING_PAGE requires FANCY_MENU to work */ diff --git a/sanityv3/schemas/objects/form.tsx b/sanityv3/schemas/objects/form.tsx index 1b6e264fb..3eb7ea0d4 100644 --- a/sanityv3/schemas/objects/form.tsx +++ b/sanityv3/schemas/objects/form.tsx @@ -22,6 +22,7 @@ type FormType = | 'careersContactForm' | 'orderReportsForm' | 'careerFairAndVisitsForm' + | 'pensionForm' const ingressContentType = configureBlockContent({ h2: false, @@ -67,6 +68,7 @@ export default { title: 'Career fairs and visits', value: 'careerFairAndVisitsForm', }, + Flags.HAS_PENSION_FORM && { title: 'Pension form', value: 'pensionForm' }, ].filter((e) => e), layout: 'dropdown', }, @@ -110,6 +112,8 @@ export default { return 'Careers contact form' } else if (type == 'orderReportsForm') { return 'Order reports' + } else if(type == 'pensionForm') { + return 'Pension form' } return 'Career fairs and visits' } diff --git a/sanityv3/schemas/textSnippets.ts b/sanityv3/schemas/textSnippets.ts index ba545fb70..69192df4f 100644 --- a/sanityv3/schemas/textSnippets.ts +++ b/sanityv3/schemas/textSnippets.ts @@ -10,6 +10,7 @@ export const groups = { contactForm: { title: 'Contact form', hidden: !Flags.HAS_CONTACT_EQUINOR_FORM }, careerContactForm: { title: 'Careers Contact Form', hidden: !Flags.HAS_CAREERS_CONTACT_FORM }, orderAnnualReportsForm: { title: 'Order annual reports form', hidden: !Flags.HAS_ORDER_REPORT_FORM }, + pensionForm: { title: 'Pension form', hidden: !Flags.HAS_PENSION_FORM }, form: { title: 'Form', hidden: !Flags.HAS_FORMS }, cookie: { title: 'Cookie' }, others: { title: 'Others' }, @@ -339,6 +340,81 @@ const snippets: textSnippet = { defaultValue: 'Submit form', group: groups.contactForm, }, + pension_form_name: { + title: 'Name', + defaultValue: 'Name *', + group: groups.pensionForm, + }, + pension_form_name_placeholder: { + title: 'Name Placeholder', + defaultValue: 'Jane Doe', + group: groups.pensionForm, + }, + pension_form_name_validation: { + title: 'Name validation', + defaultValue: 'Please fill out your name', + group: groups.pensionForm, + }, + + pension_form_email: { + title: 'Email', + defaultValue: 'Email *', + group: groups.pensionForm, + }, + pension_form_email_validation: { + title: 'Email validation', + defaultValue: 'Please fill out a valid email address', + group: groups.pensionForm, + }, + pension_form_category: { + title: 'Category', + defaultValue: 'Category', + group: groups.pensionForm, + }, + pension_form_category_pension: { + title: 'Pension Category', + defaultValue: 'Pension', + group: groups.pensionForm, + }, + pension_form_category_travel_insurance: { + title: 'Travel Insurance Category', + defaultValue: 'Travel Insurance', + group: groups.pensionForm, + }, + pension_form_category_other: { + title: 'Other Pension/Insurance Related Category', + defaultValue: 'Other Pension/Insurance Related', + group: groups.pensionForm, + }, + + pension_form_what_is_your_request: { + title: 'What is your request?', + defaultValue: 'What is your request?', + group: groups.pensionForm, + }, + pension_form_what_is_your_request_placeholder: { + title: `Requests Placeholder`, + defaultValue: `Please don't enter any personal information`, + group: groups.pensionForm, + }, + pension_form_what_is_your_request_validation: { + title: 'Requests Validation', + defaultValue: 'Please let us know how we may help you', + group: groups.pensionForm, + }, + + pension_form_submit: { + title: 'Submit Button Text', + defaultValue: 'Submit Form', + group: groups.pensionForm, + }, + + pension_form_all_fields_mandatory: { + title: 'All fields with * are mandatory', + defaultValue: 'All fields with * are mandatory', + group: groups.pensionForm, + }, + career_fair_form_organisation: { title: 'Organisation', defaultValue: 'School / Organisation', diff --git a/web/pageComponents/topicPages/Form/Form.tsx b/web/pageComponents/topicPages/Form/Form.tsx index 6feea06ff..dfa3216a5 100644 --- a/web/pageComponents/topicPages/Form/Form.tsx +++ b/web/pageComponents/topicPages/Form/Form.tsx @@ -9,6 +9,8 @@ import CareersContactForm from './careersContactForm/CareersContactForm' import type { FormData } from '../../../types/index' import { twMerge } from 'tailwind-merge' import CallToActions from '@sections/CallToActions' +import PensionForm from './PensionForm' +import React from 'react' const StyledHeading = styled(TitleText)` padding: 0 0 var(--space-large) 0; @@ -32,6 +34,8 @@ const Form = ({ data, anchor, className }: { data: FormData; anchor?: string; cl return case 'careersContactForm': return + case 'pensionForm': + return case 'orderReportsForm': return ( <> diff --git a/web/pageComponents/topicPages/Form/PensionForm.tsx b/web/pageComponents/topicPages/Form/PensionForm.tsx new file mode 100644 index 000000000..f14ae0208 --- /dev/null +++ b/web/pageComponents/topicPages/Form/PensionForm.tsx @@ -0,0 +1,263 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Icon } from '@equinor/eds-core-react' +import { useForm, Controller } from 'react-hook-form' +import { error_filled } from '@equinor/eds-icons' +import { FormattedMessage, useIntl } from 'react-intl' +import { FormButton, FormTextField, FormSelect, FormSubmitSuccessBox, FormSubmitFailureBox } from '@components' +import { BaseSyntheticEvent, useState } from 'react' +import FriendlyCaptcha from './FriendlyCaptcha' +import { PensionFormCatalogType } from '../../../types' +import React from 'react' + +type PensionFormValues = { + name: string + email: string + phone: string + pensionCategory: string + requests: string +} + +const PensionForm = () => { + const intl = useIntl() + const [submitButtonEnabled, setSubmitButtonEnabled] = useState(false) + const [isServerError, setServerError] = useState(false) + const [isSuccessfullySubmitted, setSuccessfullySubmitted] = useState(false) + + const onSubmit = async (data: PensionFormValues, event?: BaseSyntheticEvent) => { + const res = await fetch('/api/forms/service-now-pension', { + body: JSON.stringify({ + data, + frcCaptchaSolution: (event?.target as any)['frc-captcha-solution'].value, + catalogType: getCatalog(data.pensionCategory), + }), + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + }) + + setServerError(res.status != 200) + setSuccessfullySubmitted(res.status == 200) + } + + const getCatalog = (category: string): PensionFormCatalogType | null => { + if (category.includes(intl.formatMessage({ id: 'pension_form_pension', defaultMessage: 'Pension' }))) + return 'pension' + else if ( + category.includes(intl.formatMessage({ id: 'pension_form_travel_insurance', defaultMessage: 'Travel Insurance' })) + ) + return 'travelInsurance' + else if ( + category.includes( + intl.formatMessage({ + id: 'pension_form_other_pension_insurance_related', + defaultMessage: 'Other Pension/Insurance Related', + }), + ) + ) + return 'otherPensionInsuranceRelated' + else return null + } + + const { + handleSubmit, + control, + reset, + formState: { isSubmitted, isSubmitting, isSubmitSuccessful }, + } = useForm({ + defaultValues: { + name: '', + email: '', + phone: '', + pensionCategory: intl.formatMessage({id: 'pension_form_select_topic', defaultMessage: 'Select topic'}), + requests: '', + }, + }) + + return ( + <> +
{ + reset() + setSubmitButtonEnabled(false) + setSuccessfullySubmitted(false) + }} + > + {!isSuccessfullySubmitted && !isServerError && ( + <> + {/* Name field */} + { + const { name } = props + return ( + : undefined} + helperText={error?.message} + {...(invalid && { variant: 'error' })} + /> + ) + }} + /> + + {/* Email field */} + { + const { name } = props + return ( + : undefined} + helperText={error?.message} + aria-required="true" + {...(invalid && { variant: 'error' })} + /> + ) + }} + /> + + {/* Pension Category field */} + { + const { name } = props + return ( + + + + + + + ) + }} + /> + + {/* requests field */} + { + const { name } = props + return ( + : undefined} + helperText={error?.message} + {...(invalid && { variant: 'error' })} + /> + ) + }} + /> + +
+ +
+ + { + setSubmitButtonEnabled(true) + }} + errorCallback={(error: any) => { + console.error('FriendlyCaptcha encountered an error', error) + setSubmitButtonEnabled(true) + }} + /> + + + {isSubmitting ? ( + + ) : ( + + )} + + + )} + + {isSubmitSuccessful && } + {isServerError && } + + + ) +} + +export default PensionForm diff --git a/web/pages/api/forms/service-now-pension.ts b/web/pages/api/forms/service-now-pension.ts new file mode 100644 index 000000000..e1ee8e299 --- /dev/null +++ b/web/pages/api/forms/service-now-pension.ts @@ -0,0 +1,60 @@ +import { NextApiRequest, NextApiResponse } from 'next' +import { PensionFormCatalogType } from '../../../types' +import { sendRequestToServiceNow } from './service-now-base' +import { validateFormRequest } from './validateFormRequest' + +const getCatalogIdentifier = (catalogType: PensionFormCatalogType | null) => { + switch (catalogType) { + case 'pension': + return '6777904f938a2950eaf1f4527cba1048'; + case 'travelInsurance': + return '1818180393ca2950eaf1f4527cba101d'; + case 'otherPensionInsuranceRelated': + return '6777904f938a2950eaf1f4527cba1048'; + default: + return '6777904f938a2950eaf1f4527cba1048'; + } + }; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const result = await validateFormRequest(req, 'pension form') + if (result.status !== 200) { + return res.status(result.status).json({ msg: result.message }) + } + + const catalogIdentifier = getCatalogIdentifier(req.body.catalogType) + + const data = req.body.data + const email = encodeURI(data.email) + const category = encodeURI(data.category) + const requests = encodeURI(data.message) + const name = encodeURI(data.name) + + const urlString = + process.env.SERVICE_NOW_FORM_URL + + '/api/stasa/statoildotcomproject/ContactUs/' + + process.env.SERVICE_NOW_FORM_CATALOG_ITEM + + '/' + + catalogIdentifier + + '?Email=' + + email + + '&Category=' + + category + + '&Requests=' + + requests + + '&Name=' + + name + + await sendRequestToServiceNow(urlString) + .then((response) => { + if (JSON.parse(response).status == 'failure' || JSON.parse(response).Status?.includes('Failure')) { + console.log('Failed to create ticket in service-now') + res.status(500).end() + } + res.status(200).end() + }) + .catch((error) => { + console.log('Error occured while sending request to ServiceNow', error) + res.status(500).end() + }) +} diff --git a/web/types/types.ts b/web/types/types.ts index 1d4e1bad8..4e33d0f30 100644 --- a/web/types/types.ts +++ b/web/types/types.ts @@ -411,7 +411,7 @@ export type IframeCarouselData = { export type ContactFormCatalogType = 'humanRightsInformationRequest' | 'loginIssues' export type CareersContactFormCatalogType = 'suspectedRecruitmentScamRequest' | 'emergingTalentsQueries' | 'others' - +export type PensionFormCatalogType= 'pension'|'travelInsurance'|'otherPensionInsuranceRelated'; export type KeyNumberItemData = { type: 'keyNumberItem' id: string