@hashicorp/react-components 1.1.10 → 1.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -8
- package/src/components/nav-panel/components/flight-icon/index.tsx +51 -0
- package/src/components/nav-panel/components/flight-icon/style.module.css +3 -0
- package/src/components/nav-panel/components/link-list/index.tsx +15 -9
- package/src/components/nav-panel/components/link-list/style.module.css +10 -10
- package/src/components/nav-panel/components/panel/column/style.module.css +2 -2
- package/src/components/nav-panel/components/panel/root/style.module.css +5 -5
- package/src/components/nav-panel/components/promo/index.tsx +15 -9
- package/src/components/nav-panel/components/promo/style.module.css +26 -12
- package/src/components/nav-panel/components/text/index.tsx +34 -0
- package/src/components/nav-panel/product-panel/index.tsx +34 -14
- package/src/components/nav-panel/product-panel/style.module.css +77 -36
- package/src/components/nav-panel/standard-panel/index.tsx +3 -0
- package/src/components/nav-panel/standard-panel/style.module.css +2 -2
- package/src/lib/get-localized-link-props/index.test.ts +30 -0
- package/src/lib/get-localized-link-props/index.ts +45 -0
- package/src/lib/parse-url/index.test.ts +40 -1
- package/src/lib/parse-url/index.ts +75 -9
- package/src/lib/send-posthog-event/index.ts +22 -0
- package/src/lib/supported-locales/index.ts +1 -0
- package/src/lib/track-nav-click-event/index.ts +4 -4
- package/src/lib/v/index.ts +4 -5
- package/src/components/actions/index.tsx +0 -107
- package/src/components/actions/style.module.css +0 -28
- package/src/components/actions/types.ts +0 -73
- package/src/components/call-to-action/index.tsx +0 -205
- package/src/components/call-to-action/styles/variant-centered.module.css +0 -93
- package/src/components/call-to-action/styles/variant-compact-grid.module.css +0 -88
- package/src/components/call-to-action/styles/variant-compact.module.css +0 -91
- package/src/components/call-to-action/styles/variant-links.module.css +0 -85
- package/src/components/card/README.md +0 -11
- package/src/components/card/card.tsx +0 -45
- package/src/components/card/index.test.tsx +0 -69
- package/src/components/card/index.tsx +0 -14
- package/src/components/card/newsroom-card/index.test.tsx +0 -39
- package/src/components/card/newsroom-card/index.tsx +0 -37
- package/src/components/card/newsroom-card/style.module.css +0 -12
- package/src/components/card/partner-card/index.test.tsx +0 -57
- package/src/components/card/partner-card/index.tsx +0 -69
- package/src/components/card/partner-card/style.module.css +0 -24
- package/src/components/card/person-card/index.test.tsx +0 -71
- package/src/components/card/person-card/index.tsx +0 -93
- package/src/components/card/person-card/style.module.css +0 -33
- package/src/components/card/primitives.tsx +0 -176
- package/src/components/card/promo-card/index.tsx +0 -51
- package/src/components/card/resource-card/index.test.tsx +0 -48
- package/src/components/card/resource-card/index.tsx +0 -37
- package/src/components/card/style.module.css +0 -145
- package/src/components/card/types.ts +0 -66
- package/src/components/card/unified-card/index.tsx +0 -64
- package/src/components/consent-manager/README.md +0 -73
- package/src/components/consent-manager/components/banner/banner.module.css +0 -23
- package/src/components/consent-manager/components/banner/banner.test.tsx +0 -31
- package/src/components/consent-manager/components/banner/banner.tsx +0 -73
- package/src/components/consent-manager/components/dialog/dialog.module.css +0 -145
- package/src/components/consent-manager/components/dialog/dialog.test.tsx +0 -194
- package/src/components/consent-manager/components/dialog/dialog.tsx +0 -339
- package/src/components/consent-manager/img/icn_close.tsx +0 -19
- package/src/components/consent-manager/index.test.tsx +0 -334
- package/src/components/consent-manager/index.tsx +0 -186
- package/src/components/consent-manager/loader/README.md +0 -29
- package/src/components/consent-manager/loader/categories.ts +0 -22
- package/src/components/consent-manager/loader/index.tsx +0 -94
- package/src/components/consent-manager/loader/presets/enterprise.ts +0 -12
- package/src/components/consent-manager/loader/presets/oss.ts +0 -10
- package/src/components/consent-manager/loader/services.ts +0 -45
- package/src/components/consent-manager/scripts/custom.tsx +0 -59
- package/src/components/consent-manager/scripts/segment.tsx +0 -87
- package/src/components/consent-manager/style.module.css +0 -9
- package/src/components/consent-manager/types.ts +0 -55
- package/src/components/consent-manager/util/cookies.test.ts +0 -75
- package/src/components/consent-manager/util/cookies.ts +0 -70
- package/src/components/consent-manager/util/integrations.test.ts +0 -115
- package/src/components/consent-manager/util/integrations.ts +0 -110
- package/src/components/intro/index.tsx +0 -82
- package/src/components/intro/style.module.css +0 -67
- package/src/components/intro/types.ts +0 -44
- package/src/components/marketo-form/CHANGELOG.md +0 -122
- package/src/components/marketo-form/README.md +0 -1
- package/src/components/marketo-form/contexts/FormMetadata.ts +0 -11
- package/src/components/marketo-form/docs.mdx +0 -197
- package/src/components/marketo-form/fixtures/index.ts +0 -185
- package/src/components/marketo-form/form/index.tsx +0 -369
- package/src/components/marketo-form/index.test.tsx +0 -320
- package/src/components/marketo-form/index.ts +0 -11
- package/src/components/marketo-form/package.original.json +0 -30
- package/src/components/marketo-form/partials/error-message/index.tsx +0 -12
- package/src/components/marketo-form/partials/error-message/style.module.css +0 -13
- package/src/components/marketo-form/partials/field/index.tsx +0 -90
- package/src/components/marketo-form/partials/field-wrapper/index.tsx +0 -26
- package/src/components/marketo-form/partials/fields/checkbox-field/index.tsx +0 -32
- package/src/components/marketo-form/partials/fields/email-field/index.tsx +0 -35
- package/src/components/marketo-form/partials/fields/form-page-url-field/index.tsx +0 -21
- package/src/components/marketo-form/partials/fields/hidden-field/index.tsx +0 -37
- package/src/components/marketo-form/partials/fields/htmltext-field/index.tsx +0 -21
- package/src/components/marketo-form/partials/fields/htmltext-field/style.module.css +0 -18
- package/src/components/marketo-form/partials/fields/name-field/index.tsx +0 -19
- package/src/components/marketo-form/partials/fields/name-field/style.module.css +0 -13
- package/src/components/marketo-form/partials/fields/radio-buttons-field/index.tsx +0 -48
- package/src/components/marketo-form/partials/fields/select-field/index.tsx +0 -39
- package/src/components/marketo-form/partials/fields/select-field/style.module.css +0 -5
- package/src/components/marketo-form/partials/fields/telephone-field/index.tsx +0 -30
- package/src/components/marketo-form/partials/fields/text-field/index.tsx +0 -30
- package/src/components/marketo-form/partials/fields/textarea-field/index.tsx +0 -29
- package/src/components/marketo-form/partials/label/index.tsx +0 -24
- package/src/components/marketo-form/partials/label/style.module.css +0 -12
- package/src/components/marketo-form/partials/visibility-rule/index.tsx +0 -84
- package/src/components/marketo-form/props.js +0 -66
- package/src/components/marketo-form/server/api-routes.ts +0 -109
- package/src/components/marketo-form/server/client.ts +0 -98
- package/src/components/marketo-form/server/index.ts +0 -8
- package/src/components/marketo-form/types.ts +0 -268
- package/src/components/marketo-form/utils.test.tsx +0 -103
- package/src/components/marketo-form/utils.ts +0 -449
- package/src/components/next-steps/index.tsx +0 -137
- package/src/components/next-steps/style.module.css +0 -270
- package/src/components/next-steps/types.ts +0 -63
- package/src/components/product-badge/index.test.tsx +0 -11
- package/src/components/product-badge/index.tsx +0 -42
- package/src/components/product-badge/style.module.css +0 -76
- package/src/components/products-used/index.tsx +0 -84
- package/src/components/products-used/style.module.css +0 -87
- package/src/components/products-used/types.ts +0 -31
- package/src/components/related-content/index.test.tsx +0 -74
- package/src/components/related-content/index.tsx +0 -56
- package/src/components/related-content/style.module.css +0 -89
- package/src/components/related-content/types.ts +0 -21
- package/src/components/subnav/helpers/areBasePathsMatching.ts +0 -13
- package/src/components/subnav/helpers/traverse/index.ts +0 -42
- package/src/components/subnav/helpers/useHeightRef.ts +0 -42
- package/src/components/subnav/helpers/useNavRef.ts +0 -30
- package/src/components/subnav/helpers/useOverflowRef.ts +0 -85
- package/src/components/subnav/helpers/useStuckRef.ts +0 -51
- package/src/components/subnav/index.tsx +0 -199
- package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.test.ts +0 -26
- package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.ts +0 -22
- package/src/components/subnav/partials/CtaLinks/github-stars-link/index.tsx +0 -87
- package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.test.ts +0 -29
- package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.ts +0 -32
- package/src/components/subnav/partials/CtaLinks/github-stars-link/style.module.css +0 -85
- package/src/components/subnav/partials/CtaLinks/icons/github-logo.js +0 -20
- package/src/components/subnav/partials/CtaLinks/icons/github.svg.js +0 -7
- package/src/components/subnav/partials/CtaLinks/icons/star.js +0 -20
- package/src/components/subnav/partials/CtaLinks/index.tsx +0 -130
- package/src/components/subnav/partials/CtaLinks/style.module.css +0 -45
- package/src/components/subnav/partials/DropdownTrigger/index.tsx +0 -35
- package/src/components/subnav/partials/DropdownTrigger/style.module.css +0 -36
- package/src/components/subnav/partials/MenuItemsDefault/index.tsx +0 -151
- package/src/components/subnav/partials/MenuItemsDefault/style.module.css +0 -91
- package/src/components/subnav/partials/MenuItemsOverflow/index.tsx +0 -139
- package/src/components/subnav/partials/MenuItemsOverflow/style.module.css +0 -82
- package/src/components/subnav/partials/TitleLink/index.tsx +0 -113
- package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-dark.ts +0 -9
- package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-light.ts +0 -9
- package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-dark.ts +0 -23
- package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-light.ts +0 -23
- package/src/components/subnav/partials/TitleLink/style.module.css +0 -43
- package/src/components/subnav/partials/nav-item-text/index.tsx +0 -37
- package/src/components/subnav/partials/nav-item-text/style.module.css +0 -15
- package/src/components/subnav/style.module.css +0 -68
- package/src/components/subnav/subnav-consts.ts +0 -1
- package/src/components/subnav/theme.module.css +0 -50
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
calculateDefaultValues,
|
|
3
|
-
formattedLabel,
|
|
4
|
-
groupFields,
|
|
5
|
-
includesSkippedRecords,
|
|
6
|
-
} from './utils'
|
|
7
|
-
import { UTM_FORM_PROPS, VISIBILITY_RULE_FORM_PROPS } from './fixtures'
|
|
8
|
-
|
|
9
|
-
describe('formattedLabel', () => {
|
|
10
|
-
it('does not append asterisk for non-required fields', () => {
|
|
11
|
-
expect(
|
|
12
|
-
formattedLabel({
|
|
13
|
-
id: 'FirstName',
|
|
14
|
-
label: 'First Name',
|
|
15
|
-
dataType: 'text',
|
|
16
|
-
validationMessage: 'This field is required.',
|
|
17
|
-
rowNumber: 3,
|
|
18
|
-
columnNumber: 0,
|
|
19
|
-
required: false,
|
|
20
|
-
visibilityRules: {
|
|
21
|
-
ruleType: 'alwaysShow',
|
|
22
|
-
},
|
|
23
|
-
})
|
|
24
|
-
).toBe('First Name')
|
|
25
|
-
})
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
describe('groupFields', () => {
|
|
29
|
-
it('groups fields', () => {
|
|
30
|
-
const groups = groupFields(UTM_FORM_PROPS.fields.result, {
|
|
31
|
-
name: {
|
|
32
|
-
fields: ['FirstName', 'LastName'],
|
|
33
|
-
component: () => <span>Not used</span>,
|
|
34
|
-
},
|
|
35
|
-
})
|
|
36
|
-
expect(groups).toEqual({
|
|
37
|
-
name: [UTM_FORM_PROPS.fields.result[0], UTM_FORM_PROPS.fields.result[1]],
|
|
38
|
-
utm_medium__c: [UTM_FORM_PROPS.fields.result[2]],
|
|
39
|
-
form_page_url__c: [UTM_FORM_PROPS.fields.result[3]],
|
|
40
|
-
})
|
|
41
|
-
})
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
describe('calculateDefaultValues', () => {
|
|
45
|
-
it('calculates default values', () => {
|
|
46
|
-
expect(
|
|
47
|
-
calculateDefaultValues(VISIBILITY_RULE_FORM_PROPS.fields.result)
|
|
48
|
-
).toEqual({
|
|
49
|
-
FirstName: '',
|
|
50
|
-
LastName: '',
|
|
51
|
-
Phone: '',
|
|
52
|
-
fastTracktoSales: false,
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('accounts for passed in values', () => {
|
|
57
|
-
expect(
|
|
58
|
-
calculateDefaultValues(UTM_FORM_PROPS.fields.result, {
|
|
59
|
-
form_page_url__c: 'http',
|
|
60
|
-
})
|
|
61
|
-
).toEqual({
|
|
62
|
-
FirstName: '',
|
|
63
|
-
LastName: '',
|
|
64
|
-
utm_medium__c: '',
|
|
65
|
-
form_page_url__c: 'http',
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
describe('includesSkippedRecords', () => {
|
|
71
|
-
it('returns true when containing skipped records', () => {
|
|
72
|
-
const response = {
|
|
73
|
-
requestId: 'f8a8#185cc1e96b9',
|
|
74
|
-
result: [
|
|
75
|
-
{
|
|
76
|
-
status: 'skipped' as const,
|
|
77
|
-
reasons: [
|
|
78
|
-
{
|
|
79
|
-
code: '1003',
|
|
80
|
-
message:
|
|
81
|
-
"Invalid value for field 'email' and value 'dylan.staley@hashicorp.co.k'",
|
|
82
|
-
},
|
|
83
|
-
],
|
|
84
|
-
},
|
|
85
|
-
],
|
|
86
|
-
success: true,
|
|
87
|
-
}
|
|
88
|
-
expect(includesSkippedRecords(response)).toBe(true)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('returns false when not containing skipped records', () => {
|
|
92
|
-
const response = {
|
|
93
|
-
requestId: 'f8a8#185cc1e96b9',
|
|
94
|
-
result: [
|
|
95
|
-
{
|
|
96
|
-
status: 'created' as const,
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
success: true,
|
|
100
|
-
}
|
|
101
|
-
expect(includesSkippedRecords(response)).toBe(false)
|
|
102
|
-
})
|
|
103
|
-
})
|
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) HashiCorp, Inc.
|
|
3
|
-
* SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { isAnalyticsMethodAvailable } from '@hashicorp/platform-analytics'
|
|
7
|
-
import type {
|
|
8
|
-
MarketoFormField,
|
|
9
|
-
MarketoFormMetadataResponse,
|
|
10
|
-
MarketoSubmissionResponse,
|
|
11
|
-
OnSubmitProps,
|
|
12
|
-
OnSubmitData,
|
|
13
|
-
} from './types'
|
|
14
|
-
import { useFormState } from 'react-hook-form'
|
|
15
|
-
import cookies from 'js-cookie'
|
|
16
|
-
|
|
17
|
-
// Marketo stores field names in two versions, SOAP and REST. Some API
|
|
18
|
-
// endpoints return SOAP names, while others accept REST names. This object
|
|
19
|
-
// contains all fields from our Marketo instance that have a REST name that is
|
|
20
|
-
// different from the SOAP name.
|
|
21
|
-
const soapToRESTFieldNames: Record<string, string> = {
|
|
22
|
-
Address: 'address',
|
|
23
|
-
AnnualRevenue: 'annualRevenue',
|
|
24
|
-
AnonymousIP: 'anonymousIP',
|
|
25
|
-
BillingCity: 'billingCity',
|
|
26
|
-
BillingCountry: 'billingCountry',
|
|
27
|
-
BillingPostalCode: 'billingPostalCode',
|
|
28
|
-
BillingState: 'billingState',
|
|
29
|
-
BillingStreet: 'billingStreet',
|
|
30
|
-
City: 'city',
|
|
31
|
-
Company: 'company',
|
|
32
|
-
Country: 'country',
|
|
33
|
-
DoNotCall: 'doNotCall',
|
|
34
|
-
DoNotCallReason: 'doNotCallReason',
|
|
35
|
-
Email: 'email',
|
|
36
|
-
EmailInvalid: 'emailInvalid',
|
|
37
|
-
EmailInvalidCause: 'emailInvalidCause',
|
|
38
|
-
Fax: 'fax',
|
|
39
|
-
FirstName: 'firstName',
|
|
40
|
-
Industry: 'industry',
|
|
41
|
-
InferredCompany: 'inferredCompany',
|
|
42
|
-
InferredCountry: 'inferredCountry',
|
|
43
|
-
LastName: 'lastName',
|
|
44
|
-
LeadRole: 'leadRole',
|
|
45
|
-
LeadScore: 'leadScore',
|
|
46
|
-
LeadSource: 'leadSource',
|
|
47
|
-
LeadStatus: 'leadStatus',
|
|
48
|
-
MainPhone: 'mainPhone',
|
|
49
|
-
MarketoSocialFacebookDisplayName: 'facebookDisplayName',
|
|
50
|
-
MarketoSocialFacebookId: 'facebookId',
|
|
51
|
-
MarketoSocialFacebookPhotoURL: 'facebookPhotoURL',
|
|
52
|
-
MarketoSocialFacebookProfileURL: 'facebookProfileURL',
|
|
53
|
-
MarketoSocialFacebookReach: 'facebookReach',
|
|
54
|
-
MarketoSocialFacebookReferredEnrollments: 'facebookReferredEnrollments',
|
|
55
|
-
MarketoSocialFacebookReferredVisits: 'facebookReferredVisits',
|
|
56
|
-
MarketoSocialGender: 'gender',
|
|
57
|
-
MarketoSocialLastReferredEnrollment: 'lastReferredEnrollment',
|
|
58
|
-
MarketoSocialLastReferredVisit: 'lastReferredVisit',
|
|
59
|
-
MarketoSocialLinkedInDisplayName: 'linkedInDisplayName',
|
|
60
|
-
MarketoSocialLinkedInId: 'linkedInId',
|
|
61
|
-
MarketoSocialLinkedInPhotoURL: 'linkedInPhotoURL',
|
|
62
|
-
MarketoSocialLinkedInProfileURL: 'linkedInProfileURL',
|
|
63
|
-
MarketoSocialLinkedInReach: 'linkedInReach',
|
|
64
|
-
MarketoSocialLinkedInReferredEnrollments: 'linkedInReferredEnrollments',
|
|
65
|
-
MarketoSocialLinkedInReferredVisits: 'linkedInReferredVisits',
|
|
66
|
-
MarketoSocialSyndicationId: 'syndicationId',
|
|
67
|
-
MarketoSocialTotalReferredEnrollments: 'totalReferredEnrollments',
|
|
68
|
-
MarketoSocialTotalReferredVisits: 'totalReferredVisits',
|
|
69
|
-
MarketoSocialTwitterDisplayName: 'twitterDisplayName',
|
|
70
|
-
MarketoSocialTwitterId: 'twitterId',
|
|
71
|
-
MarketoSocialTwitterPhotoURL: 'twitterPhotoURL',
|
|
72
|
-
MarketoSocialTwitterProfileURL: 'twitterProfileURL',
|
|
73
|
-
MarketoSocialTwitterReach: 'twitterReach',
|
|
74
|
-
MarketoSocialTwitterReferredEnrollments: 'twitterReferredEnrollments',
|
|
75
|
-
MarketoSocialTwitterReferredVisits: 'twitterReferredVisits',
|
|
76
|
-
MiddleName: 'middleName',
|
|
77
|
-
MktoPersonNotes: 'mktoPersonNotes',
|
|
78
|
-
MobilePhone: 'mobilePhone',
|
|
79
|
-
NumberOfEmployees: 'numberOfEmployees',
|
|
80
|
-
Phone: 'phone',
|
|
81
|
-
PostalCode: 'postalCode',
|
|
82
|
-
Rating: 'rating',
|
|
83
|
-
Salutation: 'salutation',
|
|
84
|
-
SICCode: 'sicCode',
|
|
85
|
-
State: 'state',
|
|
86
|
-
Title: 'title',
|
|
87
|
-
Unsubscribed: 'unsubscribed',
|
|
88
|
-
UnsubscribedReason: 'unsubscribedReason',
|
|
89
|
-
Website: 'website',
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Given an object with SOAP named keys, returns an object with SOAP
|
|
93
|
-
// names replaced with their REST variants.
|
|
94
|
-
export function convertToRESTFields(
|
|
95
|
-
data: Record<string, unknown>
|
|
96
|
-
): Record<string, unknown> {
|
|
97
|
-
const restData: Record<string, unknown> = {}
|
|
98
|
-
for (const [key, value] of Object.entries(data)) {
|
|
99
|
-
if (key in soapToRESTFieldNames) {
|
|
100
|
-
restData[soapToRESTFieldNames[key]] = value
|
|
101
|
-
} else {
|
|
102
|
-
restData[key] = value
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return restData
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Returns the label for a field.
|
|
109
|
-
export function formattedLabel(field: MarketoFormField): string {
|
|
110
|
-
if ('label' in field && field.label) {
|
|
111
|
-
return field.label
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return field.id
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Returns a Record<string, MarketoFormField[]> that groups fields that should
|
|
118
|
-
// be rendered by a single component.
|
|
119
|
-
//
|
|
120
|
-
// This function allows consumers to define multiple fields that should be
|
|
121
|
-
// rendered by a single component. The configuration is in the following form:
|
|
122
|
-
// const config = {
|
|
123
|
-
// groupName: {
|
|
124
|
-
// fields: ["FieldNameOne", "FieldNameTwo"],
|
|
125
|
-
// component: RenderComponent
|
|
126
|
-
// }
|
|
127
|
-
// }
|
|
128
|
-
// In this instance, MarketoForm will call RenderComponent with a `fields` prop
|
|
129
|
-
// containing the MarketoFormField object for FieldNameOne and FieldNameTwo.
|
|
130
|
-
export function groupFields(
|
|
131
|
-
fields: MarketoFormField[],
|
|
132
|
-
groups: Record<
|
|
133
|
-
string,
|
|
134
|
-
{
|
|
135
|
-
fields: string[]
|
|
136
|
-
component: (props: { fields: MarketoFormField[] }) => JSX.Element
|
|
137
|
-
}
|
|
138
|
-
>
|
|
139
|
-
): Record<string, MarketoFormField[]> {
|
|
140
|
-
// Final value that will be returned. Keys are group names and values are
|
|
141
|
-
// an array of MarketoFormField objects.
|
|
142
|
-
const grouped: Record<string, MarketoFormField[]> = {}
|
|
143
|
-
|
|
144
|
-
fields.forEach((field) => {
|
|
145
|
-
// Check if groups contains a group targeting this field
|
|
146
|
-
const customGroup = Object.entries(groups).filter(([_, config]) => {
|
|
147
|
-
return config.fields.includes(field.id)
|
|
148
|
-
})
|
|
149
|
-
if (customGroup.length > 0) {
|
|
150
|
-
const customGroupName = customGroup[0][0]
|
|
151
|
-
if (customGroupName in grouped) {
|
|
152
|
-
grouped[customGroupName].push(field)
|
|
153
|
-
} else {
|
|
154
|
-
grouped[customGroupName] = [field]
|
|
155
|
-
}
|
|
156
|
-
} else {
|
|
157
|
-
grouped[field.id] = [field]
|
|
158
|
-
}
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
return grouped
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// @TODO: export this from the @hashicorp/platform-analytics/utm-params-capture & import in this file
|
|
165
|
-
const UTM_ALLOW_LIST = [
|
|
166
|
-
'utm_source',
|
|
167
|
-
'utm_medium',
|
|
168
|
-
'utm_offer',
|
|
169
|
-
'utm_campaign',
|
|
170
|
-
'utm_content',
|
|
171
|
-
'utm_channel_bucket',
|
|
172
|
-
]
|
|
173
|
-
|
|
174
|
-
export function getUtmCookies() {
|
|
175
|
-
const allCookies = cookies.get()
|
|
176
|
-
|
|
177
|
-
if (!allCookies) {
|
|
178
|
-
return {}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const utmCookies: Record<string, string> = {}
|
|
182
|
-
|
|
183
|
-
for (const [key, value] of Object.entries(allCookies)) {
|
|
184
|
-
if (UTM_ALLOW_LIST.includes(key)) {
|
|
185
|
-
utmCookies[`${key}__c`] = value
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return utmCookies
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export const STORED_FIELD_NAMES = ['FirstName', 'LastName', 'Email']
|
|
193
|
-
|
|
194
|
-
// Returns an object representing the default state of the form as defined by
|
|
195
|
-
// the Marketo form's configuration.
|
|
196
|
-
export function calculateDefaultValues(
|
|
197
|
-
fields: MarketoFormField[],
|
|
198
|
-
values?: Record<string, any>
|
|
199
|
-
): Record<string, string | boolean> {
|
|
200
|
-
const initialValues: Record<string, string | boolean> = {}
|
|
201
|
-
const utmCookies = getUtmCookies()
|
|
202
|
-
fields.forEach((field) => {
|
|
203
|
-
if (
|
|
204
|
-
'defaultValue' in field &&
|
|
205
|
-
field.defaultValue &&
|
|
206
|
-
field.defaultValue !== ''
|
|
207
|
-
) {
|
|
208
|
-
if (field.dataType === 'select') {
|
|
209
|
-
// For some reason, the Marketo API returns the value for <select> fields
|
|
210
|
-
// as a JSON encoded string.
|
|
211
|
-
initialValues[field.id] = JSON.parse(field.defaultValue)[0]
|
|
212
|
-
} else {
|
|
213
|
-
initialValues[field.id] = field.defaultValue
|
|
214
|
-
}
|
|
215
|
-
} else if (field.dataType === 'checkbox') {
|
|
216
|
-
initialValues[field.id] = field.fieldMetaData.initiallyChecked
|
|
217
|
-
} else if (utmCookies[field.id]) {
|
|
218
|
-
initialValues[field.id] = utmCookies[field.id]
|
|
219
|
-
} else if (field.dataType !== 'htmltext') {
|
|
220
|
-
// htmltext is a presentational element, and shouldn't have a value
|
|
221
|
-
initialValues[field.id] = ''
|
|
222
|
-
}
|
|
223
|
-
})
|
|
224
|
-
return { ...initialValues, ...values }
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export function useErrorMessage(id: string): string | undefined {
|
|
228
|
-
const { errors, touchedFields } = useFormState()
|
|
229
|
-
|
|
230
|
-
if (!touchedFields[id] || !errors[id]) {
|
|
231
|
-
return
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return errors[id]?.message as string
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export function segmentIdentify(leadFormFields: Record<string, unknown>) {
|
|
238
|
-
// This function is wrapped in a try/catch to prevent Segment errors from
|
|
239
|
-
// interrupting the form submission workflow.
|
|
240
|
-
try {
|
|
241
|
-
if (isAnalyticsMethodAvailable('identify')) {
|
|
242
|
-
const traits: Record<string, unknown> = {}
|
|
243
|
-
|
|
244
|
-
// Segment traits mostly map 1:1 to Marketo REST field names. However,
|
|
245
|
-
// since that's not always the case, we check and set each
|
|
246
|
-
// field individually.
|
|
247
|
-
// Reference: https://segment.com/docs/connections/spec/identify/
|
|
248
|
-
if ('email' in leadFormFields) {
|
|
249
|
-
traits['email'] = leadFormFields['email']
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if ('firstName' in leadFormFields) {
|
|
253
|
-
traits['firstName'] = leadFormFields['firstName']
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if ('lastName' in leadFormFields) {
|
|
257
|
-
traits['lastName'] = leadFormFields['lastName']
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if ('phone' in leadFormFields) {
|
|
261
|
-
traits['phone'] = leadFormFields['phone']
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if ('title' in leadFormFields) {
|
|
265
|
-
traits['title'] = leadFormFields['title']
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if ('company' in leadFormFields) {
|
|
269
|
-
traits['company'] = { name: leadFormFields['company'] }
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if ('country' in leadFormFields) {
|
|
273
|
-
traits['address'] = { country: leadFormFields['country'] }
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (Object.keys(traits).length > 0) {
|
|
277
|
-
window.analytics.identify(traits)
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
} catch (err) {
|
|
281
|
-
console.error(err)
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Returns true if any of the results have a status of skipped
|
|
286
|
-
export function includesSkippedRecords(
|
|
287
|
-
res: MarketoSubmissionResponse
|
|
288
|
-
): boolean {
|
|
289
|
-
return res.result.some((r) => r.status === 'skipped')
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
export function isValidEmail(value: string): boolean {
|
|
293
|
-
// Source: https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email)
|
|
294
|
-
// Slightly modified to require domains instead of bare TLDs
|
|
295
|
-
/* eslint-disable-next-line no-useless-escape */
|
|
296
|
-
return /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/i.test(
|
|
297
|
-
value
|
|
298
|
-
)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
interface HandleSubmitFormProps extends OnSubmitProps {
|
|
302
|
-
metadata: MarketoFormMetadataResponse
|
|
303
|
-
data: OnSubmitData
|
|
304
|
-
reset: () => void
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export const handleSubmitForm = async ({
|
|
308
|
-
data,
|
|
309
|
-
transformData,
|
|
310
|
-
onSubmitError,
|
|
311
|
-
onSubmitStart,
|
|
312
|
-
onSubmitSuccess,
|
|
313
|
-
resetOnSubmission,
|
|
314
|
-
metadata,
|
|
315
|
-
reset,
|
|
316
|
-
}: HandleSubmitFormProps) => {
|
|
317
|
-
const leadFormData = transformData ? transformData(data) : data
|
|
318
|
-
|
|
319
|
-
if (onSubmitStart) {
|
|
320
|
-
onSubmitStart()
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const leadFormFields = convertToRESTFields(leadFormData)
|
|
324
|
-
|
|
325
|
-
segmentIdentify(leadFormFields)
|
|
326
|
-
|
|
327
|
-
const res = await fetch(`${window.location.origin}/api/marketo/submit`, {
|
|
328
|
-
method: 'POST',
|
|
329
|
-
headers: {
|
|
330
|
-
'Content-Type': 'application/json',
|
|
331
|
-
},
|
|
332
|
-
body: JSON.stringify({
|
|
333
|
-
input: [
|
|
334
|
-
{
|
|
335
|
-
leadFormFields,
|
|
336
|
-
visitorData: {
|
|
337
|
-
pageURL: window.location.href,
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
],
|
|
341
|
-
formId: metadata.result[0].id,
|
|
342
|
-
}),
|
|
343
|
-
})
|
|
344
|
-
const marketoResponse = (await res.json()) as MarketoSubmissionResponse
|
|
345
|
-
if (
|
|
346
|
-
res.status === 200 &&
|
|
347
|
-
marketoResponse.success &&
|
|
348
|
-
!includesSkippedRecords(marketoResponse)
|
|
349
|
-
) {
|
|
350
|
-
if (onSubmitSuccess) {
|
|
351
|
-
onSubmitSuccess(leadFormData)
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Remove fields that shouldn't be stored in local storage
|
|
355
|
-
const dataToPersist = Object.fromEntries(
|
|
356
|
-
Object.entries(data).filter(([k]) => STORED_FIELD_NAMES.includes(k))
|
|
357
|
-
)
|
|
358
|
-
localStorage.setItem('marketoFormData', JSON.stringify(dataToPersist))
|
|
359
|
-
|
|
360
|
-
if (resetOnSubmission) {
|
|
361
|
-
reset()
|
|
362
|
-
}
|
|
363
|
-
} else {
|
|
364
|
-
console.error(marketoResponse)
|
|
365
|
-
if (onSubmitError) {
|
|
366
|
-
onSubmitError()
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
throw new Error('An error occurred while submitting form.')
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
export const resolverErrors = (
|
|
374
|
-
values: Record<string, string | boolean>,
|
|
375
|
-
fields: MarketoFormField[]
|
|
376
|
-
) => {
|
|
377
|
-
const errors: Record<string, { message: string }> = {}
|
|
378
|
-
|
|
379
|
-
fields.forEach((field) => {
|
|
380
|
-
if ('required' in field && field.required) {
|
|
381
|
-
if (
|
|
382
|
-
!(field.id in values) ||
|
|
383
|
-
values[field.id] === '' ||
|
|
384
|
-
values[field.id] === false ||
|
|
385
|
-
values[field.id] === undefined ||
|
|
386
|
-
values[field.id] === null ||
|
|
387
|
-
(typeof values[field.id] === 'string' &&
|
|
388
|
-
(values[field.id] as string).trim() === '')
|
|
389
|
-
) {
|
|
390
|
-
errors[field.id] = {
|
|
391
|
-
message: field.validationMessage ?? 'This field is required.',
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Validate that select field value is an allowed value
|
|
397
|
-
if (field.dataType === 'select') {
|
|
398
|
-
if (
|
|
399
|
-
field.id in values &&
|
|
400
|
-
field.fieldMetaData.values &&
|
|
401
|
-
!field.fieldMetaData.values
|
|
402
|
-
.map((v) => v.value)
|
|
403
|
-
.includes(values[field.id] as string)
|
|
404
|
-
) {
|
|
405
|
-
errors[field.id] = {
|
|
406
|
-
message: field.validationMessage ?? 'Please select an option.',
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (field.dataType === 'email') {
|
|
412
|
-
if (field.id in values && !isValidEmail(values[field.id] as string)) {
|
|
413
|
-
errors[field.id] = {
|
|
414
|
-
message:
|
|
415
|
-
field.validationMessage ?? 'Please enter a valid email address.',
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
return errors
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* @param fieldsArr MarketoFormField[]
|
|
426
|
-
* @returns MarketoFormField[] with columnSplit property set
|
|
427
|
-
*/
|
|
428
|
-
export function setFieldsColumnSplit(fieldsArr: MarketoFormField[]) {
|
|
429
|
-
if (!fieldsArr) {
|
|
430
|
-
return []
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
return fieldsArr.map((f) => {
|
|
434
|
-
const columnSplit = getFieldColumnSplit(fieldsArr, f)
|
|
435
|
-
return { ...f, columnSplit }
|
|
436
|
-
})
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* @param allFields MarketoFormField[]
|
|
441
|
-
* @param field MarketoFormField
|
|
442
|
-
* @returns the number of fields in the same row as the given field in Marketo
|
|
443
|
-
*/
|
|
444
|
-
export function getFieldColumnSplit(
|
|
445
|
-
allFields: MarketoFormField[],
|
|
446
|
-
field: MarketoFormField
|
|
447
|
-
) {
|
|
448
|
-
return allFields.filter((item) => item.rowNumber === field.rowNumber)?.length
|
|
449
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) HashiCorp, Inc.
|
|
3
|
-
* SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState } from 'react'
|
|
7
|
-
import Badge from '@hashicorp/react-badge'
|
|
8
|
-
import classNames from 'classnames'
|
|
9
|
-
import ExpandableArrow from '@hashicorp/react-expandable-arrow'
|
|
10
|
-
import Intro from '../intro'
|
|
11
|
-
import Link from 'next/link'
|
|
12
|
-
import type { NextStepsProps } from './types'
|
|
13
|
-
import StandaloneLink from '@hashicorp/react-design-system-components/src/components/standalone-link'
|
|
14
|
-
import { IconArrowRight16 } from '@hashicorp/flight-icons/svg-react/arrow-right-16'
|
|
15
|
-
import useProductMeta from '@hashicorp/platform-product-meta'
|
|
16
|
-
import s from './style.module.css'
|
|
17
|
-
|
|
18
|
-
export default function NextSteps({
|
|
19
|
-
appearance = 'light',
|
|
20
|
-
theme = 'hashicorp',
|
|
21
|
-
heading,
|
|
22
|
-
description,
|
|
23
|
-
steps,
|
|
24
|
-
cta,
|
|
25
|
-
}: NextStepsProps) {
|
|
26
|
-
const { themeClass } = useProductMeta(theme)
|
|
27
|
-
return (
|
|
28
|
-
<section
|
|
29
|
-
className={classNames(s.nextSteps, s[appearance], themeClass)}
|
|
30
|
-
data-testid="next-steps"
|
|
31
|
-
>
|
|
32
|
-
<div className={s.container}>
|
|
33
|
-
<div className={s.content}>
|
|
34
|
-
<Intro
|
|
35
|
-
appearance={appearance}
|
|
36
|
-
heading={heading}
|
|
37
|
-
description={description}
|
|
38
|
-
/>
|
|
39
|
-
</div>
|
|
40
|
-
<div className={s.stepsWrapper}>
|
|
41
|
-
<ul className={s.stepsList}>
|
|
42
|
-
{steps.map((step, index) => {
|
|
43
|
-
const isFeatured = steps.length === 3 && index === 0
|
|
44
|
-
const variant = index === 0 ? 'primary' : 'secondary'
|
|
45
|
-
return (
|
|
46
|
-
<li
|
|
47
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
48
|
-
key={index}
|
|
49
|
-
className={classNames(
|
|
50
|
-
s.stepsListItem,
|
|
51
|
-
isFeatured && s.stepsListItemFeature
|
|
52
|
-
)}
|
|
53
|
-
>
|
|
54
|
-
<Tile
|
|
55
|
-
badge={step.badge}
|
|
56
|
-
theme={theme}
|
|
57
|
-
variant={variant}
|
|
58
|
-
cta={{
|
|
59
|
-
title: step.cta.title,
|
|
60
|
-
url: step.cta.url,
|
|
61
|
-
}}
|
|
62
|
-
heading={step.heading}
|
|
63
|
-
description={step.description}
|
|
64
|
-
/>
|
|
65
|
-
</li>
|
|
66
|
-
)
|
|
67
|
-
})}
|
|
68
|
-
</ul>
|
|
69
|
-
{cta ? (
|
|
70
|
-
<div className={s.cta}>
|
|
71
|
-
<p className={s.ctaCopy}>{cta.copy}</p>
|
|
72
|
-
<StandaloneLink
|
|
73
|
-
href={cta.href}
|
|
74
|
-
text={cta.ctaText}
|
|
75
|
-
icon={
|
|
76
|
-
<IconArrowRight16
|
|
77
|
-
width="100%"
|
|
78
|
-
height="100%"
|
|
79
|
-
style={{ display: 'block' }}
|
|
80
|
-
/>
|
|
81
|
-
}
|
|
82
|
-
iconPosition="trailing"
|
|
83
|
-
/>
|
|
84
|
-
</div>
|
|
85
|
-
) : null}
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
</section>
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function Tile({ theme, variant, heading, badge, description, cta }) {
|
|
93
|
-
const [isHovered, setIsHovered] = useState(false)
|
|
94
|
-
return (
|
|
95
|
-
<Link href={cta.url} legacyBehavior>
|
|
96
|
-
<a
|
|
97
|
-
className={classNames(s.tile, s[variant])}
|
|
98
|
-
onMouseOver={() => {
|
|
99
|
-
setIsHovered(true)
|
|
100
|
-
}}
|
|
101
|
-
onMouseOut={() => {
|
|
102
|
-
setIsHovered(false)
|
|
103
|
-
}}
|
|
104
|
-
>
|
|
105
|
-
<div className={s.tileInner}>
|
|
106
|
-
<h3 className={s.tileHeading}>
|
|
107
|
-
<span className={s.tileHeadingText}>{heading}</span>
|
|
108
|
-
{badge ? <BadgeWrapper theme={theme}>{badge}</BadgeWrapper> : null}
|
|
109
|
-
</h3>
|
|
110
|
-
{description ? (
|
|
111
|
-
<p className={s.tileDescription}>{description}</p>
|
|
112
|
-
) : null}
|
|
113
|
-
<span className={s.tileCta}>
|
|
114
|
-
<span className={s.tileCtaLabel}>{cta.title}</span>
|
|
115
|
-
<span className={s.tileCtaIcon}>
|
|
116
|
-
<ExpandableArrow expanded={isHovered} />
|
|
117
|
-
</span>
|
|
118
|
-
</span>
|
|
119
|
-
</div>
|
|
120
|
-
<span className={s.tileScrim} />
|
|
121
|
-
</a>
|
|
122
|
-
</Link>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const BadgeWrapper = ({ theme, children }) => {
|
|
127
|
-
const badgeTheme = theme === 'hashicorp' ? 'action' : theme
|
|
128
|
-
const page = theme === 'hashicorp' ? 'action' : 'actionFaint'
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<span className={s.tileBadge}>
|
|
132
|
-
<Badge theme={badgeTheme} page={page}>
|
|
133
|
-
{children}
|
|
134
|
-
</Badge>
|
|
135
|
-
</span>
|
|
136
|
-
)
|
|
137
|
-
}
|