@hashicorp/react-components 0.0.0-nightly.ad81ac2 → 0.0.0-nightly.b154bf4

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 (142) hide show
  1. package/.eslintrc.js +1 -2
  2. package/.stylelintrc.js +1 -1
  3. package/README.md +1 -1
  4. package/package.json +3 -8
  5. package/postcss.config.js +1 -1
  6. package/src/components/nav-panel/components/flight-icon/index.tsx +51 -0
  7. package/src/components/nav-panel/components/flight-icon/style.module.css +3 -0
  8. package/src/components/nav-panel/components/link/index.tsx +127 -0
  9. package/src/components/nav-panel/components/link/style.module.css +64 -0
  10. package/src/components/nav-panel/components/link-list/index.tsx +13 -65
  11. package/src/components/nav-panel/components/link-list/style.module.css +1 -50
  12. package/src/components/nav-panel/components/panel/column/style.module.css +2 -2
  13. package/src/components/nav-panel/components/panel/root/style.module.css +5 -5
  14. package/src/components/nav-panel/components/promo/index.tsx +31 -11
  15. package/src/components/nav-panel/components/promo/style.module.css +26 -12
  16. package/src/components/nav-panel/components/text/index.tsx +34 -0
  17. package/src/components/nav-panel/product-panel/index.tsx +54 -17
  18. package/src/components/nav-panel/product-panel/style.module.css +116 -36
  19. package/src/components/nav-panel/standard-panel/index.tsx +3 -0
  20. package/src/components/nav-panel/standard-panel/style.module.css +2 -2
  21. package/src/lib/get-localized-link-props/index.test.ts +30 -0
  22. package/src/lib/get-localized-link-props/index.ts +45 -0
  23. package/src/lib/parse-url/index.ts +0 -2
  24. package/src/lib/send-posthog-event/index.ts +22 -0
  25. package/src/lib/supported-locales/index.ts +10 -0
  26. package/src/lib/track-nav-click-event/index.ts +4 -4
  27. package/src/components/actions/index.tsx +0 -107
  28. package/src/components/actions/style.module.css +0 -28
  29. package/src/components/actions/types.ts +0 -73
  30. package/src/components/call-to-action/index.tsx +0 -205
  31. package/src/components/call-to-action/styles/variant-centered.module.css +0 -93
  32. package/src/components/call-to-action/styles/variant-compact-grid.module.css +0 -88
  33. package/src/components/call-to-action/styles/variant-compact.module.css +0 -91
  34. package/src/components/call-to-action/styles/variant-links.module.css +0 -85
  35. package/src/components/card/README.md +0 -11
  36. package/src/components/card/card.tsx +0 -45
  37. package/src/components/card/index.test.tsx +0 -69
  38. package/src/components/card/index.tsx +0 -14
  39. package/src/components/card/newsroom-card/index.test.tsx +0 -39
  40. package/src/components/card/newsroom-card/index.tsx +0 -37
  41. package/src/components/card/newsroom-card/style.module.css +0 -12
  42. package/src/components/card/partner-card/index.test.tsx +0 -57
  43. package/src/components/card/partner-card/index.tsx +0 -69
  44. package/src/components/card/partner-card/style.module.css +0 -24
  45. package/src/components/card/person-card/index.test.tsx +0 -71
  46. package/src/components/card/person-card/index.tsx +0 -93
  47. package/src/components/card/person-card/style.module.css +0 -33
  48. package/src/components/card/primitives.tsx +0 -177
  49. package/src/components/card/promo-card/index.tsx +0 -51
  50. package/src/components/card/resource-card/index.test.tsx +0 -48
  51. package/src/components/card/resource-card/index.tsx +0 -40
  52. package/src/components/card/style.module.css +0 -145
  53. package/src/components/card/types.ts +0 -66
  54. package/src/components/card/unified-card/index.spec.tsx +0 -40
  55. package/src/components/card/unified-card/index.tsx +0 -58
  56. package/src/components/intro/index.tsx +0 -82
  57. package/src/components/intro/style.module.css +0 -67
  58. package/src/components/intro/types.ts +0 -44
  59. package/src/components/marketo-form/CHANGELOG.md +0 -122
  60. package/src/components/marketo-form/README.md +0 -1
  61. package/src/components/marketo-form/contexts/FormMetadata.ts +0 -13
  62. package/src/components/marketo-form/docs.mdx +0 -197
  63. package/src/components/marketo-form/fixtures/index.ts +0 -185
  64. package/src/components/marketo-form/form/index.tsx +0 -371
  65. package/src/components/marketo-form/index.test.tsx +0 -320
  66. package/src/components/marketo-form/index.ts +0 -11
  67. package/src/components/marketo-form/package.original.json +0 -30
  68. package/src/components/marketo-form/partials/error-message/index.tsx +0 -12
  69. package/src/components/marketo-form/partials/error-message/style.module.css +0 -13
  70. package/src/components/marketo-form/partials/field/index.tsx +0 -90
  71. package/src/components/marketo-form/partials/field-wrapper/index.tsx +0 -26
  72. package/src/components/marketo-form/partials/fields/checkbox-field/index.tsx +0 -32
  73. package/src/components/marketo-form/partials/fields/email-field/index.tsx +0 -35
  74. package/src/components/marketo-form/partials/fields/form-page-url-field/index.tsx +0 -23
  75. package/src/components/marketo-form/partials/fields/hidden-field/index.tsx +0 -38
  76. package/src/components/marketo-form/partials/fields/htmltext-field/index.tsx +0 -21
  77. package/src/components/marketo-form/partials/fields/htmltext-field/style.module.css +0 -18
  78. package/src/components/marketo-form/partials/fields/name-field/index.tsx +0 -19
  79. package/src/components/marketo-form/partials/fields/name-field/style.module.css +0 -13
  80. package/src/components/marketo-form/partials/fields/radio-buttons-field/index.tsx +0 -48
  81. package/src/components/marketo-form/partials/fields/select-field/index.tsx +0 -39
  82. package/src/components/marketo-form/partials/fields/select-field/style.module.css +0 -5
  83. package/src/components/marketo-form/partials/fields/telephone-field/index.tsx +0 -30
  84. package/src/components/marketo-form/partials/fields/text-field/index.tsx +0 -30
  85. package/src/components/marketo-form/partials/fields/textarea-field/index.tsx +0 -29
  86. package/src/components/marketo-form/partials/label/index.tsx +0 -24
  87. package/src/components/marketo-form/partials/label/style.module.css +0 -12
  88. package/src/components/marketo-form/partials/visibility-rule/index.tsx +0 -84
  89. package/src/components/marketo-form/props.js +0 -66
  90. package/src/components/marketo-form/server/api-routes.ts +0 -109
  91. package/src/components/marketo-form/server/client.ts +0 -98
  92. package/src/components/marketo-form/server/index.ts +0 -8
  93. package/src/components/marketo-form/types.ts +0 -267
  94. package/src/components/marketo-form/utils.test.tsx +0 -103
  95. package/src/components/marketo-form/utils.ts +0 -449
  96. package/src/components/next-steps/index.tsx +0 -138
  97. package/src/components/next-steps/style.module.css +0 -270
  98. package/src/components/next-steps/types.ts +0 -63
  99. package/src/components/product-badge/index.test.tsx +0 -11
  100. package/src/components/product-badge/index.tsx +0 -42
  101. package/src/components/product-badge/style.module.css +0 -76
  102. package/src/components/products-used/index.tsx +0 -84
  103. package/src/components/products-used/style.module.css +0 -87
  104. package/src/components/products-used/types.ts +0 -31
  105. package/src/components/related-content/index.test.tsx +0 -74
  106. package/src/components/related-content/index.tsx +0 -56
  107. package/src/components/related-content/style.module.css +0 -89
  108. package/src/components/related-content/types.ts +0 -21
  109. package/src/components/subnav/helpers/areBasePathsMatching.ts +0 -13
  110. package/src/components/subnav/helpers/traverse/index.ts +0 -42
  111. package/src/components/subnav/helpers/useHeightRef.ts +0 -42
  112. package/src/components/subnav/helpers/useNavRef.ts +0 -30
  113. package/src/components/subnav/helpers/useOverflowRef.ts +0 -85
  114. package/src/components/subnav/helpers/useStuckRef.ts +0 -51
  115. package/src/components/subnav/index.tsx +0 -204
  116. package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.test.ts +0 -26
  117. package/src/components/subnav/partials/CtaLinks/github-stars-link/formatStarCount/index.ts +0 -22
  118. package/src/components/subnav/partials/CtaLinks/github-stars-link/index.tsx +0 -87
  119. package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.test.ts +0 -29
  120. package/src/components/subnav/partials/CtaLinks/github-stars-link/parseGithubUrl/index.ts +0 -32
  121. package/src/components/subnav/partials/CtaLinks/github-stars-link/style.module.css +0 -81
  122. package/src/components/subnav/partials/CtaLinks/icons/github-logo.js +0 -20
  123. package/src/components/subnav/partials/CtaLinks/icons/github.svg.js +0 -7
  124. package/src/components/subnav/partials/CtaLinks/icons/star.js +0 -20
  125. package/src/components/subnav/partials/CtaLinks/index.tsx +0 -131
  126. package/src/components/subnav/partials/CtaLinks/style.module.css +0 -45
  127. package/src/components/subnav/partials/DropdownTrigger/index.tsx +0 -35
  128. package/src/components/subnav/partials/DropdownTrigger/style.module.css +0 -39
  129. package/src/components/subnav/partials/MenuItemsDefault/index.tsx +0 -158
  130. package/src/components/subnav/partials/MenuItemsDefault/style.module.css +0 -104
  131. package/src/components/subnav/partials/MenuItemsOverflow/index.tsx +0 -143
  132. package/src/components/subnav/partials/MenuItemsOverflow/style.module.css +0 -88
  133. package/src/components/subnav/partials/TitleLink/index.tsx +0 -113
  134. package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-dark.ts +0 -10
  135. package/src/components/subnav/partials/TitleLink/mktg-logos/all-community-product-logos_on-light.ts +0 -10
  136. package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-dark.ts +0 -23
  137. package/src/components/subnav/partials/TitleLink/mktg-logos/all-primary-logos_on-light.ts +0 -23
  138. package/src/components/subnav/partials/TitleLink/style.module.css +0 -43
  139. package/src/components/subnav/partials/nav-item-text/index.tsx +0 -37
  140. package/src/components/subnav/partials/nav-item-text/style.module.css +0 -13
  141. package/src/components/subnav/style.module.css +0 -66
  142. package/src/components/subnav/subnav-consts.ts +0 -1
@@ -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,138 +0,0 @@
1
- /**
2
- * Copyright (c) HashiCorp, Inc.
3
- * SPDX-License-Identifier: MPL-2.0
4
- */
5
- 'use client'
6
-
7
- import { useState } from 'react'
8
- import Badge from '@hashicorp/react-badge'
9
- import classNames from 'classnames'
10
- import ExpandableArrow from '@hashicorp/react-expandable-arrow'
11
- import Intro from '../intro'
12
- import Link from 'next/link'
13
- import type { NextStepsProps } from './types'
14
- import { HDSPlusStandaloneLink } from '@hashicorp/react-mds/src/hds-plus/standalone-link'
15
- import { IconArrowRight16 } from '@hashicorp/flight-icons/svg-react/arrow-right-16'
16
- import useProductMeta from '@hashicorp/platform-product-meta'
17
- import s from './style.module.css'
18
-
19
- export default function NextSteps({
20
- appearance = 'light',
21
- theme = 'hashicorp',
22
- heading,
23
- description,
24
- steps,
25
- cta,
26
- }: NextStepsProps) {
27
- const { themeClass } = useProductMeta(theme)
28
- return (
29
- <section
30
- className={classNames(s.nextSteps, s[appearance], themeClass)}
31
- data-testid="next-steps"
32
- >
33
- <div className={s.container}>
34
- <div className={s.content}>
35
- <Intro
36
- appearance={appearance}
37
- heading={heading}
38
- description={description}
39
- />
40
- </div>
41
- <div className={s.stepsWrapper}>
42
- <ul className={s.stepsList}>
43
- {steps.map((step, index) => {
44
- const isFeatured = steps.length === 3 && index === 0
45
- const variant = index === 0 ? 'primary' : 'secondary'
46
- return (
47
- <li
48
- // eslint-disable-next-line react/no-array-index-key
49
- key={index}
50
- className={classNames(
51
- s.stepsListItem,
52
- isFeatured && s.stepsListItemFeature
53
- )}
54
- >
55
- <Tile
56
- badge={step.badge}
57
- theme={theme}
58
- variant={variant}
59
- cta={{
60
- title: step.cta.title,
61
- url: step.cta.url,
62
- }}
63
- heading={step.heading}
64
- description={step.description}
65
- />
66
- </li>
67
- )
68
- })}
69
- </ul>
70
- {cta ? (
71
- <div className={s.cta}>
72
- <p className={s.ctaCopy}>{cta.copy}</p>
73
- <HDSPlusStandaloneLink
74
- href={cta.href}
75
- text={cta.ctaText}
76
- icon={
77
- <IconArrowRight16
78
- width="100%"
79
- height="100%"
80
- style={{ display: 'block' }}
81
- />
82
- }
83
- iconPosition="trailing"
84
- />
85
- </div>
86
- ) : null}
87
- </div>
88
- </div>
89
- </section>
90
- )
91
- }
92
-
93
- function Tile({ theme, variant, heading, badge, description, cta }) {
94
- const [isHovered, setIsHovered] = useState(false)
95
- return (
96
- <Link href={cta.url} legacyBehavior>
97
- <a
98
- className={classNames(s.tile, s[variant])}
99
- onMouseOver={() => {
100
- setIsHovered(true)
101
- }}
102
- onMouseOut={() => {
103
- setIsHovered(false)
104
- }}
105
- >
106
- <div className={s.tileInner}>
107
- <h3 className={s.tileHeading}>
108
- <span className={s.tileHeadingText}>{heading}</span>
109
- {badge ? <BadgeWrapper theme={theme}>{badge}</BadgeWrapper> : null}
110
- </h3>
111
- {description ? (
112
- <p className={s.tileDescription}>{description}</p>
113
- ) : null}
114
- <span className={s.tileCta}>
115
- <span className={s.tileCtaLabel}>{cta.title}</span>
116
- <span className={s.tileCtaIcon}>
117
- <ExpandableArrow expanded={isHovered} />
118
- </span>
119
- </span>
120
- </div>
121
- <span className={s.tileScrim} />
122
- </a>
123
- </Link>
124
- )
125
- }
126
-
127
- const BadgeWrapper = ({ theme, children }) => {
128
- const badgeTheme = theme === 'hashicorp' ? 'action' : theme
129
- const page = theme === 'hashicorp' ? 'action' : 'actionFaint'
130
-
131
- return (
132
- <span className={s.tileBadge}>
133
- <Badge theme={badgeTheme} page={page}>
134
- {children}
135
- </Badge>
136
- </span>
137
- )
138
- }