@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.
Files changed (162) hide show
  1. package/package.json +4 -8
  2. package/src/components/nav-panel/components/flight-icon/index.tsx +51 -0
  3. package/src/components/nav-panel/components/flight-icon/style.module.css +3 -0
  4. package/src/components/nav-panel/components/link-list/index.tsx +15 -9
  5. package/src/components/nav-panel/components/link-list/style.module.css +10 -10
  6. package/src/components/nav-panel/components/panel/column/style.module.css +2 -2
  7. package/src/components/nav-panel/components/panel/root/style.module.css +5 -5
  8. package/src/components/nav-panel/components/promo/index.tsx +15 -9
  9. package/src/components/nav-panel/components/promo/style.module.css +26 -12
  10. package/src/components/nav-panel/components/text/index.tsx +34 -0
  11. package/src/components/nav-panel/product-panel/index.tsx +34 -14
  12. package/src/components/nav-panel/product-panel/style.module.css +77 -36
  13. package/src/components/nav-panel/standard-panel/index.tsx +3 -0
  14. package/src/components/nav-panel/standard-panel/style.module.css +2 -2
  15. package/src/lib/get-localized-link-props/index.test.ts +30 -0
  16. package/src/lib/get-localized-link-props/index.ts +45 -0
  17. package/src/lib/parse-url/index.test.ts +40 -1
  18. package/src/lib/parse-url/index.ts +75 -9
  19. package/src/lib/send-posthog-event/index.ts +22 -0
  20. package/src/lib/supported-locales/index.ts +1 -0
  21. package/src/lib/track-nav-click-event/index.ts +4 -4
  22. package/src/lib/v/index.ts +4 -5
  23. package/src/components/actions/index.tsx +0 -107
  24. package/src/components/actions/style.module.css +0 -28
  25. package/src/components/actions/types.ts +0 -73
  26. package/src/components/call-to-action/index.tsx +0 -205
  27. package/src/components/call-to-action/styles/variant-centered.module.css +0 -93
  28. package/src/components/call-to-action/styles/variant-compact-grid.module.css +0 -88
  29. package/src/components/call-to-action/styles/variant-compact.module.css +0 -91
  30. package/src/components/call-to-action/styles/variant-links.module.css +0 -85
  31. package/src/components/card/README.md +0 -11
  32. package/src/components/card/card.tsx +0 -45
  33. package/src/components/card/index.test.tsx +0 -69
  34. package/src/components/card/index.tsx +0 -14
  35. package/src/components/card/newsroom-card/index.test.tsx +0 -39
  36. package/src/components/card/newsroom-card/index.tsx +0 -37
  37. package/src/components/card/newsroom-card/style.module.css +0 -12
  38. package/src/components/card/partner-card/index.test.tsx +0 -57
  39. package/src/components/card/partner-card/index.tsx +0 -69
  40. package/src/components/card/partner-card/style.module.css +0 -24
  41. package/src/components/card/person-card/index.test.tsx +0 -71
  42. package/src/components/card/person-card/index.tsx +0 -93
  43. package/src/components/card/person-card/style.module.css +0 -33
  44. package/src/components/card/primitives.tsx +0 -176
  45. package/src/components/card/promo-card/index.tsx +0 -51
  46. package/src/components/card/resource-card/index.test.tsx +0 -48
  47. package/src/components/card/resource-card/index.tsx +0 -37
  48. package/src/components/card/style.module.css +0 -145
  49. package/src/components/card/types.ts +0 -66
  50. package/src/components/card/unified-card/index.tsx +0 -64
  51. package/src/components/consent-manager/README.md +0 -73
  52. package/src/components/consent-manager/components/banner/banner.module.css +0 -23
  53. package/src/components/consent-manager/components/banner/banner.test.tsx +0 -31
  54. package/src/components/consent-manager/components/banner/banner.tsx +0 -73
  55. package/src/components/consent-manager/components/dialog/dialog.module.css +0 -145
  56. package/src/components/consent-manager/components/dialog/dialog.test.tsx +0 -194
  57. package/src/components/consent-manager/components/dialog/dialog.tsx +0 -339
  58. package/src/components/consent-manager/img/icn_close.tsx +0 -19
  59. package/src/components/consent-manager/index.test.tsx +0 -334
  60. package/src/components/consent-manager/index.tsx +0 -186
  61. package/src/components/consent-manager/loader/README.md +0 -29
  62. package/src/components/consent-manager/loader/categories.ts +0 -22
  63. package/src/components/consent-manager/loader/index.tsx +0 -94
  64. package/src/components/consent-manager/loader/presets/enterprise.ts +0 -12
  65. package/src/components/consent-manager/loader/presets/oss.ts +0 -10
  66. package/src/components/consent-manager/loader/services.ts +0 -45
  67. package/src/components/consent-manager/scripts/custom.tsx +0 -59
  68. package/src/components/consent-manager/scripts/segment.tsx +0 -87
  69. package/src/components/consent-manager/style.module.css +0 -9
  70. package/src/components/consent-manager/types.ts +0 -55
  71. package/src/components/consent-manager/util/cookies.test.ts +0 -75
  72. package/src/components/consent-manager/util/cookies.ts +0 -70
  73. package/src/components/consent-manager/util/integrations.test.ts +0 -115
  74. package/src/components/consent-manager/util/integrations.ts +0 -110
  75. package/src/components/intro/index.tsx +0 -82
  76. package/src/components/intro/style.module.css +0 -67
  77. package/src/components/intro/types.ts +0 -44
  78. package/src/components/marketo-form/CHANGELOG.md +0 -122
  79. package/src/components/marketo-form/README.md +0 -1
  80. package/src/components/marketo-form/contexts/FormMetadata.ts +0 -11
  81. package/src/components/marketo-form/docs.mdx +0 -197
  82. package/src/components/marketo-form/fixtures/index.ts +0 -185
  83. package/src/components/marketo-form/form/index.tsx +0 -369
  84. package/src/components/marketo-form/index.test.tsx +0 -320
  85. package/src/components/marketo-form/index.ts +0 -11
  86. package/src/components/marketo-form/package.original.json +0 -30
  87. package/src/components/marketo-form/partials/error-message/index.tsx +0 -12
  88. package/src/components/marketo-form/partials/error-message/style.module.css +0 -13
  89. package/src/components/marketo-form/partials/field/index.tsx +0 -90
  90. package/src/components/marketo-form/partials/field-wrapper/index.tsx +0 -26
  91. package/src/components/marketo-form/partials/fields/checkbox-field/index.tsx +0 -32
  92. package/src/components/marketo-form/partials/fields/email-field/index.tsx +0 -35
  93. package/src/components/marketo-form/partials/fields/form-page-url-field/index.tsx +0 -21
  94. package/src/components/marketo-form/partials/fields/hidden-field/index.tsx +0 -37
  95. package/src/components/marketo-form/partials/fields/htmltext-field/index.tsx +0 -21
  96. package/src/components/marketo-form/partials/fields/htmltext-field/style.module.css +0 -18
  97. package/src/components/marketo-form/partials/fields/name-field/index.tsx +0 -19
  98. package/src/components/marketo-form/partials/fields/name-field/style.module.css +0 -13
  99. package/src/components/marketo-form/partials/fields/radio-buttons-field/index.tsx +0 -48
  100. package/src/components/marketo-form/partials/fields/select-field/index.tsx +0 -39
  101. package/src/components/marketo-form/partials/fields/select-field/style.module.css +0 -5
  102. package/src/components/marketo-form/partials/fields/telephone-field/index.tsx +0 -30
  103. package/src/components/marketo-form/partials/fields/text-field/index.tsx +0 -30
  104. package/src/components/marketo-form/partials/fields/textarea-field/index.tsx +0 -29
  105. package/src/components/marketo-form/partials/label/index.tsx +0 -24
  106. package/src/components/marketo-form/partials/label/style.module.css +0 -12
  107. package/src/components/marketo-form/partials/visibility-rule/index.tsx +0 -84
  108. package/src/components/marketo-form/props.js +0 -66
  109. package/src/components/marketo-form/server/api-routes.ts +0 -109
  110. package/src/components/marketo-form/server/client.ts +0 -98
  111. package/src/components/marketo-form/server/index.ts +0 -8
  112. package/src/components/marketo-form/types.ts +0 -268
  113. package/src/components/marketo-form/utils.test.tsx +0 -103
  114. package/src/components/marketo-form/utils.ts +0 -449
  115. package/src/components/next-steps/index.tsx +0 -137
  116. package/src/components/next-steps/style.module.css +0 -270
  117. package/src/components/next-steps/types.ts +0 -63
  118. package/src/components/product-badge/index.test.tsx +0 -11
  119. package/src/components/product-badge/index.tsx +0 -42
  120. package/src/components/product-badge/style.module.css +0 -76
  121. package/src/components/products-used/index.tsx +0 -84
  122. package/src/components/products-used/style.module.css +0 -87
  123. package/src/components/products-used/types.ts +0 -31
  124. package/src/components/related-content/index.test.tsx +0 -74
  125. package/src/components/related-content/index.tsx +0 -56
  126. package/src/components/related-content/style.module.css +0 -89
  127. package/src/components/related-content/types.ts +0 -21
  128. package/src/components/subnav/helpers/areBasePathsMatching.ts +0 -13
  129. package/src/components/subnav/helpers/traverse/index.ts +0 -42
  130. package/src/components/subnav/helpers/useHeightRef.ts +0 -42
  131. package/src/components/subnav/helpers/useNavRef.ts +0 -30
  132. package/src/components/subnav/helpers/useOverflowRef.ts +0 -85
  133. package/src/components/subnav/helpers/useStuckRef.ts +0 -51
  134. package/src/components/subnav/index.tsx +0 -199
  135. package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.test.ts +0 -26
  136. package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.ts +0 -22
  137. package/src/components/subnav/partials/CtaLinks/github-stars-link/index.tsx +0 -87
  138. package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.test.ts +0 -29
  139. package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.ts +0 -32
  140. package/src/components/subnav/partials/CtaLinks/github-stars-link/style.module.css +0 -85
  141. package/src/components/subnav/partials/CtaLinks/icons/github-logo.js +0 -20
  142. package/src/components/subnav/partials/CtaLinks/icons/github.svg.js +0 -7
  143. package/src/components/subnav/partials/CtaLinks/icons/star.js +0 -20
  144. package/src/components/subnav/partials/CtaLinks/index.tsx +0 -130
  145. package/src/components/subnav/partials/CtaLinks/style.module.css +0 -45
  146. package/src/components/subnav/partials/DropdownTrigger/index.tsx +0 -35
  147. package/src/components/subnav/partials/DropdownTrigger/style.module.css +0 -36
  148. package/src/components/subnav/partials/MenuItemsDefault/index.tsx +0 -151
  149. package/src/components/subnav/partials/MenuItemsDefault/style.module.css +0 -91
  150. package/src/components/subnav/partials/MenuItemsOverflow/index.tsx +0 -139
  151. package/src/components/subnav/partials/MenuItemsOverflow/style.module.css +0 -82
  152. package/src/components/subnav/partials/TitleLink/index.tsx +0 -113
  153. package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-dark.ts +0 -9
  154. package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-light.ts +0 -9
  155. package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-dark.ts +0 -23
  156. package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-light.ts +0 -23
  157. package/src/components/subnav/partials/TitleLink/style.module.css +0 -43
  158. package/src/components/subnav/partials/nav-item-text/index.tsx +0 -37
  159. package/src/components/subnav/partials/nav-item-text/style.module.css +0 -15
  160. package/src/components/subnav/style.module.css +0 -68
  161. package/src/components/subnav/subnav-consts.ts +0 -1
  162. 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
- }