diff --git a/.changeset/orange-meals-mate.md b/.changeset/orange-meals-mate.md new file mode 100644 index 000000000..a7811c0e8 --- /dev/null +++ b/.changeset/orange-meals-mate.md @@ -0,0 +1,5 @@ +--- +'@hashicorp/react-marketo-form': minor +--- + +Add support for htmltext fields diff --git a/packages/marketo-form/docs.mdx b/packages/marketo-form/docs.mdx index b89d74c6f..c51005026 100644 --- a/packages/marketo-form/docs.mdx +++ b/packages/marketo-form/docs.mdx @@ -158,11 +158,12 @@ Renders a form from Marketo. }, { id: "Consent_Privacy_Policy__c", + label: "Send me news about HashiCorp products, releases and events.", dataType: "checkbox", validationMessage: "This field is required.", rowNumber: 6, columnNumber: 0, - required: true, + required: false, formPrefill: true, fieldMetaData: { initiallyChecked: false, @@ -171,6 +172,17 @@ Renders a form from Marketo. ruleType: "alwaysShow", }, }, + { + id: "SHRtbFRleHRfMjAyMy0wNC0wNVQyMjo0NDo0Ny45OTZa", + labelWidth: 260, + dataType: "htmltext", + rowNumber: 9, + columnNumber: 0, + visibilityRules: { + ruleType: "alwaysShow" + }, + text: "By submitting this form, you acknowledge and agree that HashiCorp will process your personal information in accordance with the Privacy Policy." + } ], }} submitTitle="Download Now" diff --git a/packages/marketo-form/form/index.tsx b/packages/marketo-form/form/index.tsx index e6a7ee780..2fc922d9d 100644 --- a/packages/marketo-form/form/index.tsx +++ b/packages/marketo-form/form/index.tsx @@ -155,7 +155,7 @@ const Form = ({ let errors: Record = {} fields.result.forEach((field) => { - if (field.required) { + if ('required' in field && field.required) { if ( !(field.id in values) || values[field.id] === '' || diff --git a/packages/marketo-form/partials/field/index.tsx b/packages/marketo-form/partials/field/index.tsx index 71533b3a2..3233459c6 100644 --- a/packages/marketo-form/partials/field/index.tsx +++ b/packages/marketo-form/partials/field/index.tsx @@ -9,8 +9,8 @@ import TelephoneField from '../fields/telephone-field' import TextareaField from '../fields/textarea-field' import SelectField from '../fields/select-field' import CheckboxField from '../fields/checkbox-field' -import PrivacyPolicyField from '../fields/privacy-policy-field' import HiddenField from '../fields/hidden-field' +import HtmltextField from '../fields/htmltext-field' import FormPageUrlField from '../fields/form-page-url-field' import type { MarketoFormField, MarketoFormComponents } from '../../types' @@ -57,9 +57,6 @@ const Field = ({ const Component = components.checkbox! return } - if (field.id === 'Consent_Privacy_Policy__c') { - return - } return case 'hidden': if (components && 'hidden' in components) { @@ -70,6 +67,12 @@ const Field = ({ return } return + case 'htmltext': + if (components && 'htmltext' in components) { + const Component = components.htmltext! + return + } + return default: console.error(`Unknown form field type: ${(field as any).Datatype}`) } diff --git a/packages/marketo-form/partials/fields/htmltext-field/index.tsx b/packages/marketo-form/partials/fields/htmltext-field/index.tsx new file mode 100644 index 000000000..cbbe80573 --- /dev/null +++ b/packages/marketo-form/partials/fields/htmltext-field/index.tsx @@ -0,0 +1,21 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import FieldWrapper from '../../field-wrapper' +import type { MarketoFormHtmltextField } from '../../../types' +import styles from './style.module.css' + +const Index = ({ field }: { field: MarketoFormHtmltextField }) => { + return ( + + + + ) +} + +export default Index diff --git a/packages/marketo-form/partials/fields/htmltext-field/style.module.css b/packages/marketo-form/partials/fields/htmltext-field/style.module.css new file mode 100644 index 000000000..afd5cda91 --- /dev/null +++ b/packages/marketo-form/partials/fields/htmltext-field/style.module.css @@ -0,0 +1,6 @@ +.htmltext { + composes: g-type-body-small from global; + color: var(--wpl-neutral-500); + margin-top: var(--wpl-spacing-02); + margin-bottom: var(--wpl-spacing-05); +} diff --git a/packages/marketo-form/partials/fields/privacy-policy-field/index.tsx b/packages/marketo-form/partials/fields/privacy-policy-field/index.tsx deleted file mode 100644 index 56270c5a0..000000000 --- a/packages/marketo-form/partials/fields/privacy-policy-field/index.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -import { useContext } from 'react' -import { useFormContext } from 'react-hook-form' -import CheckboxInput from '@hashicorp/react-form-fields/checkbox' -import FieldWrapper from '../../field-wrapper' -import { FormMetadataContext } from '../../../contexts/FormMetadata' -import type { MarketoFormCheckboxField } from '../../../types' -import { useErrorMessage } from '../../../utils' -import style from './style.module.css' - -const LABELS_BY_LANGUAGE = { - English: ( - <> - I agree to HashiCorp’s{' '} - - Privacy Policy - - * - - ), - French: ( - <> - J'accepte la{' '} - - politique de confidentialité - {' '} - d'HashiCorp.* - - ), - Korean: ( - <> - HashiCorp의{' '} - - 개인정보 처리방침 - - 에 동의합니다* - - ), - Japanese: ( - <> - HashiCorpの - - プライバシーポリシー - - に同意します。 - * - - ), - German: ( - <> - Ich stimme den{' '} - - Datenschutzrichtlinien - {' '} - von HashiCorp zu.* - - ), - Spanish: ( - <> - Estoy de acuerdo con la{' '} - - política de privacidad - {' '} - de HashiCorp.* - - ), - 'Portuguese (Brazil)': ( - <> - Eu concordo com a{' '} - - Política de Privacidade - {' '} - da HashiCorp.* - - ), - Italian: ( - <> - Accetto{' '} - - l'informativa sulla privacy - {' '} - di HashiCorp. - * - - ), -} - -const PrivacyPolicyField = ({ field }: { field: MarketoFormCheckboxField }) => { - const metadata = useContext(FormMetadataContext) - const { register, watch } = useFormContext() - const error = useErrorMessage(field.id) - const checked = watch(field.id, false) - - return ( - - - - ) -} - -export default PrivacyPolicyField diff --git a/packages/marketo-form/partials/fields/privacy-policy-field/style.module.css b/packages/marketo-form/partials/fields/privacy-policy-field/style.module.css deleted file mode 100644 index 1ba0758a3..000000000 --- a/packages/marketo-form/partials/fields/privacy-policy-field/style.module.css +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: MPL-2.0 - */ - -.required { - color: #e5242a; -} diff --git a/packages/marketo-form/partials/visibility-rule/index.tsx b/packages/marketo-form/partials/visibility-rule/index.tsx index 1d75f007e..bb5df25a4 100644 --- a/packages/marketo-form/partials/visibility-rule/index.tsx +++ b/packages/marketo-form/partials/visibility-rule/index.tsx @@ -6,7 +6,28 @@ import { useMemo } from 'react' import { useFormContext } from 'react-hook-form' import Field from '../field' -import type { MarketoFormField, MarketoFormComponents } from '../../types' +import type { + MarketoFormField, + MarketoFormComponents, + VisibilityRule as VisibilityRuleType, +} from '../../types' + +function ruleApplies(rule: VisibilityRuleType, value: any): boolean { + switch (rule.operator) { + case 'is': + return ( + rule.values.includes(value) || + (value === false && rule.values.includes('no')) || + (value === true && rule.values.includes('yes')) + ) + case 'isNot': + return !rule.values.includes(value) + case 'isEmpty': + return value === null || typeof value === 'undefined' || value === '' + default: + return false + } +} const FieldWithVisibilityRule = ({ field, @@ -20,17 +41,26 @@ const FieldWithVisibilityRule = ({ // Currently we only support the first defined rule. const value = watch(field.visibilityRules!.rules![0].subjectField) - const show = useMemo(() => { - const { operator, values } = field.visibilityRules!.rules![0] - return ( - operator === 'is' && - (values.includes(value) || - (value === false && values.includes('no')) || - (value === true && values.includes('yes'))) + const hasMatchingRule = useMemo(() => { + return field.visibilityRules!.rules!.some((rule) => + ruleApplies(rule, value) ) }, [field, value]) - return show ? : null + if ( + hasMatchingRule && + (field.visibilityRules!.ruleType === 'show' || + field.visibilityRules!.ruleType === 'hide') + ) { + return field.visibilityRules!.ruleType === 'show' ? ( + + ) : null + } + + // If the ruleType is show, that means the field is hidden by default. + return field.visibilityRules!.ruleType === 'show' ? null : ( + + ) } const VisibilityRule = ({ @@ -40,7 +70,11 @@ const VisibilityRule = ({ field: MarketoFormField components?: MarketoFormComponents }) => { - if (field.visibilityRules && field.visibilityRules.ruleType === 'show') { + if ( + field.visibilityRules && + (field.visibilityRules.ruleType === 'show' || + field.visibilityRules.ruleType === 'hide') + ) { return } diff --git a/packages/marketo-form/server/client.ts b/packages/marketo-form/server/client.ts index 60a567b6c..282ee1bfc 100644 --- a/packages/marketo-form/server/client.ts +++ b/packages/marketo-form/server/client.ts @@ -57,6 +57,37 @@ export async function getForm(formId: number) { return { fields, metadata } } +export async function getDraftFormProps(formId: number) { + const { access_token } = await getToken() + + const fieldsResponse = await fetch( + `${process.env.MARKETO_ENDPOINT}/asset/v1/form/${formId}/fields.json?status=draft`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${access_token}`, + }, + } + ) + + const metadataResponse = await fetch( + `${process.env.MARKETO_ENDPOINT}/asset/v1/form/${formId}.json?status=draft`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${access_token}`, + }, + } + ) + + const [fields, metadata] = await Promise.all([ + fieldsResponse.json(), + metadataResponse.json(), + ]) + + return { fields, metadata } +} + export async function getFormProps( id: number ): Promise { diff --git a/packages/marketo-form/types.ts b/packages/marketo-form/types.ts index 0a3702961..c358af22e 100644 --- a/packages/marketo-form/types.ts +++ b/packages/marketo-form/types.ts @@ -13,6 +13,7 @@ export type MarketoFormDatatype = | 'select' | 'checkbox' | 'hidden' + | 'htmltext' export interface Autofill { value: string @@ -22,19 +23,21 @@ export interface Autofill { export interface VisibilityRule { subjectField: string - operator: 'is' + operator: 'is' | 'isNot' | 'isEmpty' values: string[] altLabel: string } export interface VisibilityRules { rules?: VisibilityRule[] - ruleType: 'alwaysShow' | 'show' + ruleType: 'alwaysShow' | 'show' | 'hide' } export interface MarketoBaseFormField { id: string dataType: MarketoFormDatatype + rowNumber: number + columnNumber: number defaultValue?: string label?: string required: boolean @@ -51,16 +54,19 @@ export interface MarketoFormTextField extends MarketoBaseFormField { export interface MarketoFormEmailField extends MarketoBaseFormField { dataType: 'email' hintText?: string + formPrefill: boolean } export interface MarketoFormTelephoneField extends MarketoBaseFormField { dataType: 'telephone' hintText?: string + formPrefill: boolean } export interface MarketoFormTextAreaField extends MarketoBaseFormField { dataType: 'textArea' hintText?: string + formPrefill: boolean } export interface SelectValue { @@ -77,6 +83,7 @@ export interface SelectFieldMetaData { export interface MarketoFormSelectField extends MarketoBaseFormField { dataType: 'select' fieldMetaData: SelectFieldMetaData + formPrefill: boolean } export interface CheckboxFieldMetaData { @@ -86,12 +93,23 @@ export interface CheckboxFieldMetaData { export interface MarketoFormCheckboxField extends MarketoBaseFormField { dataType: 'checkbox' fieldMetaData: CheckboxFieldMetaData + formPrefill: boolean } export interface MarketoFormHiddenField extends MarketoBaseFormField { dataType: 'hidden' } +export interface MarketoFormHtmltextField { + id: string + labelWidth: number + dataType: 'htmltext' + rowNumber: number + columnNumber: number + visibilityRules?: VisibilityRules + text: string +} + export type MarketoFormField = | MarketoFormTextField | MarketoFormEmailField @@ -100,6 +118,7 @@ export type MarketoFormField = | MarketoFormSelectField | MarketoFormCheckboxField | MarketoFormHiddenField + | MarketoFormHtmltextField export interface MarketoFormMetadata { id: number @@ -116,6 +135,20 @@ export interface MarketoFormMetadata { labelPosition: string fontFamily: string fontSize: string + folder?: { + type: string + value: number + folderName: string + } + knownVisitor?: { + type: string + template: unknown + } + thankYouList?: { + followupType: string + followupValue: string + default: boolean + }[] buttonLocation: number buttonLabel: string waitingLabel: string @@ -150,6 +183,7 @@ export interface MarketoFormComponents { select?: (props: { field: MarketoFormSelectField }) => JSX.Element checkbox?: (props: { field: MarketoFormCheckboxField }) => JSX.Element hidden?: (props: { field: MarketoFormHiddenField }) => JSX.Element + htmltext?: (props: { field: MarketoFormHtmltextField }) => JSX.Element } /** diff --git a/packages/marketo-form/utils.ts b/packages/marketo-form/utils.ts index d21f139a3..54bef1304 100644 --- a/packages/marketo-form/utils.ts +++ b/packages/marketo-form/utils.ts @@ -100,7 +100,7 @@ export function convertToRESTFields( // Returns the label for a field. export function formattedLabel(field: MarketoFormField): string { - if (field.label) { + if ('label' in field && field.label) { return field.label } @@ -162,7 +162,11 @@ export function calculateDefaultValues( ): Record { const initialValues: Record = {} fields.forEach((field) => { - if (field.defaultValue && field.defaultValue !== '') { + if ( + 'defaultValue' in field && + field.defaultValue && + field.defaultValue !== '' + ) { if (field.dataType === 'select') { // For some reason, the Marketo API returns the value for