@hashicorp/react-components 1.1.10 → 1.2.0
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/README.md +1 -1
- package/package.json +8 -13
- package/postcss.config.js +1 -1
- package/src/components/nav-panel/components/flight-icon/index.tsx +53 -0
- package/src/components/nav-panel/components/flight-icon/style.module.css +3 -0
- package/src/components/nav-panel/components/link/cloud-fill.svg +3 -0
- package/src/components/nav-panel/components/link/index.tsx +139 -0
- package/src/components/nav-panel/components/link/layers-fill.svg +5 -0
- package/src/components/nav-panel/components/link/shield-alert-fill.svg +3 -0
- package/src/components/nav-panel/components/link/style.module.css +64 -0
- package/src/components/nav-panel/components/link/zap-fill.svg +3 -0
- package/src/components/nav-panel/components/link-list/index.tsx +12 -64
- package/src/components/nav-panel/components/link-list/style.module.css +1 -50
- 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 +35 -15
- package/src/components/nav-panel/components/promo/style.module.css +32 -28
- package/src/components/nav-panel/components/text/index.tsx +34 -0
- package/src/components/nav-panel/product-panel/index.tsx +71 -20
- package/src/components/nav-panel/product-panel/style.module.css +111 -53
- 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 +57 -2
- package/src/lib/parse-url/index.ts +97 -13
- package/src/lib/send-posthog-event/index.ts +22 -0
- package/src/lib/supported-locales/index.ts +10 -0
- package/src/lib/track-nav-click-event/index.ts +5 -5
- package/src/lib/v/index.ts +9 -4
- package/tsconfig.json +1 -2
- package/.eslintrc.js +0 -4
- package/.stylelintrc.js +0 -3
- 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,334 +0,0 @@
|
|
|
1
|
-
import { fireEvent, render, screen, act } from '@testing-library/react'
|
|
2
|
-
|
|
3
|
-
const defaultProps = {
|
|
4
|
-
version: 0,
|
|
5
|
-
showDialog: false,
|
|
6
|
-
segmentWriteKey: 'iyi06c432UL7SB1r3fQReec4bNwFyzkW',
|
|
7
|
-
privacyPolicyLink: 'https://www.hashicorp.com/privacy',
|
|
8
|
-
companyName: 'HashiCorp',
|
|
9
|
-
segmentServices: [
|
|
10
|
-
{
|
|
11
|
-
name: 'Example Name',
|
|
12
|
-
category: 'Example Category',
|
|
13
|
-
description:
|
|
14
|
-
'A short description of what the service is and how your company uses the data.',
|
|
15
|
-
},
|
|
16
|
-
],
|
|
17
|
-
additionalServices: [
|
|
18
|
-
{
|
|
19
|
-
name: 'Name of the service',
|
|
20
|
-
category: 'Example Category',
|
|
21
|
-
description:
|
|
22
|
-
'A short description of what the service is and how your company uses the data.',
|
|
23
|
-
body: '',
|
|
24
|
-
url: 'http://www.an-optional-url-for-a-script-to-add-to-the-page.com',
|
|
25
|
-
async: true,
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
name: 'Name of the service 2',
|
|
29
|
-
category: 'Example Category',
|
|
30
|
-
description: 'A script with additional elements to be injected',
|
|
31
|
-
body: '',
|
|
32
|
-
url: 'https://source-url-of-script.com',
|
|
33
|
-
async: true,
|
|
34
|
-
addToBody: true,
|
|
35
|
-
dataAttrs: [
|
|
36
|
-
{
|
|
37
|
-
name: 'test',
|
|
38
|
-
value: 'foobar',
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
name: 'Name of the service 3',
|
|
44
|
-
category: 'Example Category',
|
|
45
|
-
description: 'A script with additional elements to be injected',
|
|
46
|
-
body: 'window.foo = "bar"',
|
|
47
|
-
async: true,
|
|
48
|
-
dataAttrs: [
|
|
49
|
-
{
|
|
50
|
-
name: 'test',
|
|
51
|
-
value: 'foobar',
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: 'Name of the conditionally loaded service',
|
|
57
|
-
category: 'Example Category with conditionally loaded service',
|
|
58
|
-
description: 'A script with additional elements to be injected',
|
|
59
|
-
body: 'window.foo = "bar"',
|
|
60
|
-
url: 'https://source-of-conditionally-loaded-script.com',
|
|
61
|
-
async: true,
|
|
62
|
-
dataAttrs: [
|
|
63
|
-
{
|
|
64
|
-
name: 'test',
|
|
65
|
-
value: 'foobar',
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
shouldLoad: () => false,
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
categories: [
|
|
72
|
-
{
|
|
73
|
-
name: 'Example Category',
|
|
74
|
-
description: 'A short description of the category',
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
container: '#consent-manager',
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
beforeAll(() => {
|
|
81
|
-
HTMLDialogElement.prototype.show = vi.fn()
|
|
82
|
-
HTMLDialogElement.prototype.showModal = vi.fn()
|
|
83
|
-
HTMLDialogElement.prototype.close = vi.fn()
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
describe('ConsentManager', () => {
|
|
87
|
-
afterEach(() => {
|
|
88
|
-
vi.resetModules()
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test('shows the banner if the forceShow prop is true', async () => {
|
|
92
|
-
const { default: ConsentManager } = await import('./')
|
|
93
|
-
render(<ConsentManager {...defaultProps} forceShow={true} />)
|
|
94
|
-
expect(screen.getByTestId('consent-banner')).toBeInTheDocument()
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
test('sets existing preferences and does not show the banner if preferences are already set', async () => {
|
|
98
|
-
await runWithMockedImport(
|
|
99
|
-
'./util/cookies',
|
|
100
|
-
{
|
|
101
|
-
loadPreferences: () => {
|
|
102
|
-
return { loadAll: false, segment: false, version: 0 }
|
|
103
|
-
},
|
|
104
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
105
|
-
savePreferences: () => {},
|
|
106
|
-
},
|
|
107
|
-
(MockedConsentManager) => {
|
|
108
|
-
render(<MockedConsentManager {...defaultProps} forceShow={false} />)
|
|
109
|
-
expect(screen.queryByTestId('consent-banner')).not.toBeInTheDocument()
|
|
110
|
-
}
|
|
111
|
-
)
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
test('shows the banner if preferences are set but version has increased', async () => {
|
|
115
|
-
await runWithMockedImport(
|
|
116
|
-
'./util/cookies',
|
|
117
|
-
{
|
|
118
|
-
loadPreferences: () => {
|
|
119
|
-
return { loadAll: false, segment: false, version: 1 }
|
|
120
|
-
},
|
|
121
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
122
|
-
savePreferences: () => {},
|
|
123
|
-
},
|
|
124
|
-
(MockedConsentManager) => {
|
|
125
|
-
render(
|
|
126
|
-
<MockedConsentManager
|
|
127
|
-
{...defaultProps}
|
|
128
|
-
forceShow={false}
|
|
129
|
-
version={2}
|
|
130
|
-
/>
|
|
131
|
-
)
|
|
132
|
-
expect(screen.queryByTestId('consent-banner')).toBeInTheDocument()
|
|
133
|
-
}
|
|
134
|
-
)
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
test('if the manage preferences button is clicked, opens the dialog and makes body un-scrollable', async () => {
|
|
138
|
-
const { default: ConsentManager } = await import('./')
|
|
139
|
-
|
|
140
|
-
render(<ConsentManager {...defaultProps} forceShow={true} />)
|
|
141
|
-
fireEvent.click(screen.getByTestId('manage-preferences'))
|
|
142
|
-
expect(screen.getByTestId('consent-mgr-dialog')).toBeInTheDocument()
|
|
143
|
-
expect(document.body.className).toBe('g-noscroll')
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
test('opens the dialog when the open function is called', async () => {
|
|
147
|
-
const { default: ConsentManager, open } = await import('./')
|
|
148
|
-
render(<ConsentManager {...defaultProps} />)
|
|
149
|
-
act(() => open())
|
|
150
|
-
expect(screen.getByTestId('consent-mgr-dialog')).toBeInTheDocument()
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
test('if accept all button is clicked, any open dialogs are closed and all analytics are loaded', async () => {
|
|
154
|
-
let prefs
|
|
155
|
-
await runWithMockedImport(
|
|
156
|
-
'./util/cookies',
|
|
157
|
-
{
|
|
158
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
159
|
-
loadPreferences: () => {},
|
|
160
|
-
savePreferences: (preferences) => {
|
|
161
|
-
prefs = preferences
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
(MockedConsentManager) => {
|
|
165
|
-
render(<MockedConsentManager {...defaultProps} forceShow={true} />)
|
|
166
|
-
fireEvent.click(screen.getByTestId('accept'))
|
|
167
|
-
}
|
|
168
|
-
)
|
|
169
|
-
expect(prefs.loadAll).toBe(true)
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
test('loads segment and additional services if loadAll is passed', async () => {
|
|
173
|
-
await runWithMockedImport(
|
|
174
|
-
'./util/cookies',
|
|
175
|
-
{
|
|
176
|
-
loadPreferences: () => {
|
|
177
|
-
return { loadAll: true, segment: { foo: 'bar' } }
|
|
178
|
-
},
|
|
179
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
180
|
-
savePreferences: () => {},
|
|
181
|
-
},
|
|
182
|
-
(MockedConsentManager) => {
|
|
183
|
-
render(
|
|
184
|
-
<MockedConsentManager
|
|
185
|
-
{...defaultProps}
|
|
186
|
-
forceShow={false}
|
|
187
|
-
version={2}
|
|
188
|
-
/>
|
|
189
|
-
)
|
|
190
|
-
const html = document.body.innerHTML
|
|
191
|
-
// script was injected
|
|
192
|
-
expect(html).toMatch(
|
|
193
|
-
/<script type="text\/javascript" src="https:\/\/artemis.hashicorp.com\/script/
|
|
194
|
-
)
|
|
195
|
-
// all services loaded, as well as custom segment
|
|
196
|
-
expect(html).toMatch(
|
|
197
|
-
/analytics\.load\("iyi06c432UL7SB1r3fQReec4bNwFyzkW", {"integrations":{"All":true,"Segment\.io":true,"foo":"bar"}}\);/
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
// custom service loaded
|
|
201
|
-
expect(html).toMatch(
|
|
202
|
-
/src="http:\/\/www.an-optional-url-for-a-script-to-add-to-the-page\.com"/
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
// conditionally loaded service not loaded
|
|
206
|
-
expect(html).not.toMatch(
|
|
207
|
-
/src="https:\/\/source-of-conditionally-loaded-script\.com"/
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
// custom inline script
|
|
211
|
-
expect(html).toMatch(/window\.foo = "bar"/)
|
|
212
|
-
}
|
|
213
|
-
)
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
describe('handles custom events', () => {
|
|
217
|
-
test('manage preferences callback', async () => {
|
|
218
|
-
const fn = vi.fn()
|
|
219
|
-
await runWithMockedImport(
|
|
220
|
-
'./components/dialog',
|
|
221
|
-
{
|
|
222
|
-
default: () => {
|
|
223
|
-
return <p data-testid="mocked-dialog">test</p>
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
(MockedConsentManager) => {
|
|
227
|
-
render(
|
|
228
|
-
<MockedConsentManager
|
|
229
|
-
{...defaultProps}
|
|
230
|
-
forceShow={true}
|
|
231
|
-
onManagePreferences={fn}
|
|
232
|
-
/>
|
|
233
|
-
)
|
|
234
|
-
fireEvent.click(screen.getByTestId('manage-preferences'))
|
|
235
|
-
expect(fn).toHaveBeenCalled()
|
|
236
|
-
}
|
|
237
|
-
)
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
test('accept all callback', async () => {
|
|
241
|
-
const fn = vi.fn()
|
|
242
|
-
await runWithMockedImport(
|
|
243
|
-
'./components/dialog',
|
|
244
|
-
{
|
|
245
|
-
default: () => {
|
|
246
|
-
return <p data-testid="mocked-dialog">test</p>
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
|
-
(MockedConsentManager) => {
|
|
250
|
-
render(
|
|
251
|
-
<MockedConsentManager
|
|
252
|
-
{...defaultProps}
|
|
253
|
-
forceShow={true}
|
|
254
|
-
onAcceptAll={fn}
|
|
255
|
-
/>
|
|
256
|
-
)
|
|
257
|
-
fireEvent.click(screen.getByTestId('accept'))
|
|
258
|
-
expect(fn).toHaveBeenCalled()
|
|
259
|
-
}
|
|
260
|
-
)
|
|
261
|
-
})
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
describe('handles conditionally loaded services in dialog', () => {
|
|
265
|
-
test('additional services category is not in dialog when shouldLoad() is false in all category services', async () => {
|
|
266
|
-
const { default: ConsentManager, open } = await import('./')
|
|
267
|
-
render(<ConsentManager {...defaultProps} />)
|
|
268
|
-
act(() => open())
|
|
269
|
-
|
|
270
|
-
const shownCategory = await screen.findByText('Example Category')
|
|
271
|
-
const hiddenCategory = screen.queryByText(
|
|
272
|
-
'Example Category with conditionally loaded service'
|
|
273
|
-
)
|
|
274
|
-
expect(shownCategory).toBeInTheDocument()
|
|
275
|
-
expect(hiddenCategory).not.toBeInTheDocument()
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
test('additional services category is in dialog when shouldLoad() is false in some category services but service is not in dialog', async () => {
|
|
279
|
-
const newService = {
|
|
280
|
-
name: 'Name of the new service',
|
|
281
|
-
category: 'Example Category with conditionally loaded service',
|
|
282
|
-
description: 'A script with additional elements to be injected',
|
|
283
|
-
body: 'window.foo = "bar"',
|
|
284
|
-
async: true,
|
|
285
|
-
dataAttrs: [
|
|
286
|
-
{
|
|
287
|
-
name: 'test',
|
|
288
|
-
value: 'foobar',
|
|
289
|
-
},
|
|
290
|
-
],
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const { default: ConsentManager, open } = await import('./')
|
|
294
|
-
render(
|
|
295
|
-
<ConsentManager
|
|
296
|
-
{...{
|
|
297
|
-
...defaultProps,
|
|
298
|
-
additionalServices: [
|
|
299
|
-
...defaultProps.additionalServices,
|
|
300
|
-
newService,
|
|
301
|
-
],
|
|
302
|
-
}}
|
|
303
|
-
/>
|
|
304
|
-
)
|
|
305
|
-
act(() => open())
|
|
306
|
-
const category = await screen.findByText(
|
|
307
|
-
'Example Category with conditionally loaded service'
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
expect(category).toBeInTheDocument()
|
|
311
|
-
const showMoreButton = category.nextSibling!.nextSibling!
|
|
312
|
-
// Open category accordion item to make services visible
|
|
313
|
-
fireEvent.click(showMoreButton)
|
|
314
|
-
const shouldLoadService = await screen.findByText(
|
|
315
|
-
'Name of the new service'
|
|
316
|
-
)
|
|
317
|
-
const shouldNotLoadService = screen.queryByText(
|
|
318
|
-
'Name of the conditionally loaded service'
|
|
319
|
-
)
|
|
320
|
-
expect(shouldLoadService).toBeInTheDocument()
|
|
321
|
-
expect(shouldNotLoadService).not.toBeInTheDocument()
|
|
322
|
-
})
|
|
323
|
-
})
|
|
324
|
-
})
|
|
325
|
-
|
|
326
|
-
// Given an internal module name and mock implementation, mocks the given module and returns a version
|
|
327
|
-
// of the component with the module mocked.
|
|
328
|
-
async function runWithMockedImport(module, mock, cb) {
|
|
329
|
-
vi.doMock(module, () => mock)
|
|
330
|
-
return import('./').then(({ default: ConsentManager, open }) => {
|
|
331
|
-
cb(ConsentManager, open)
|
|
332
|
-
vi.doUnmock(module)
|
|
333
|
-
})
|
|
334
|
-
}
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from 'react'
|
|
2
|
-
import { EventEmitter } from 'events'
|
|
3
|
-
import classNames from 'classnames'
|
|
4
|
-
import { isInUS } from '@hashicorp/platform-util/geo'
|
|
5
|
-
import { loadPreferences, savePreferences } from './util/cookies'
|
|
6
|
-
import ConsentBanner from './components/banner/banner'
|
|
7
|
-
import ConsentPreferences from './components/dialog/dialog'
|
|
8
|
-
import SegmentScript from './scripts/segment'
|
|
9
|
-
import CustomScripts from './scripts/custom'
|
|
10
|
-
import s from './style.module.css'
|
|
11
|
-
import {
|
|
12
|
-
ConsentManagerCategory,
|
|
13
|
-
ConsentManagerService,
|
|
14
|
-
ConsentManagerPreferences,
|
|
15
|
-
} from './types'
|
|
16
|
-
|
|
17
|
-
interface ConsentManagerProps {
|
|
18
|
-
additionalServices?: ConsentManagerService[]
|
|
19
|
-
categories?: ConsentManagerCategory[]
|
|
20
|
-
className?: string
|
|
21
|
-
cookiePolicyLink?: string
|
|
22
|
-
forceShow?: boolean
|
|
23
|
-
privacyPolicyLink?: string
|
|
24
|
-
segmentServices?: ConsentManagerService[]
|
|
25
|
-
segmentWriteKey?: string
|
|
26
|
-
showDialog?: boolean
|
|
27
|
-
version?: number
|
|
28
|
-
onManagePreferences?: () => void
|
|
29
|
-
onAcceptAll?: () => void
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const emitter = new EventEmitter()
|
|
33
|
-
|
|
34
|
-
export function open() {
|
|
35
|
-
emitter.emit('openDialog')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function saveAndLoadAnalytics(preferences: ConsentManagerPreferences) {
|
|
39
|
-
emitter.emit('saveAndLoadAnalytics', preferences)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export default function ConsentManager(props: ConsentManagerProps) {
|
|
43
|
-
const [preferences, setPreferences] = useState<{
|
|
44
|
-
loadAll?: boolean
|
|
45
|
-
version?: number
|
|
46
|
-
}>(loadPreferences() ?? {})
|
|
47
|
-
const [showDialog, setShowDialog] = useState(props.showDialog ?? false)
|
|
48
|
-
const [showBanner, setShowBanner] = useState(() => {
|
|
49
|
-
// 1. If prop override is set, always show the consent bar.
|
|
50
|
-
if (props.forceShow) return true
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
const hasEmptyPreferencesOrVersionMismatch =
|
|
54
|
-
Object.keys(preferences).length === 0 ||
|
|
55
|
-
preferences.version !== props.version
|
|
56
|
-
|
|
57
|
-
const closeDialog = () => {
|
|
58
|
-
document.body.classList.remove('g-noscroll')
|
|
59
|
-
setShowDialog(false)
|
|
60
|
-
setShowBanner(false)
|
|
61
|
-
setPreferences(loadPreferences() ?? {})
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const saveAndLoadAnalytics = useCallback(
|
|
65
|
-
(preferences) => {
|
|
66
|
-
if (typeof preferences === 'undefined') {
|
|
67
|
-
preferences = { loadAll: false, segment: false }
|
|
68
|
-
}
|
|
69
|
-
savePreferences(preferences, props.version)
|
|
70
|
-
|
|
71
|
-
// If analytics have already been added to page, it's likely you're updating your preferences
|
|
72
|
-
// We reload the page to re-initiate the script with the updated integrations
|
|
73
|
-
if (window.analytics && window.analytics.initialized) {
|
|
74
|
-
window.location.reload()
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
closeDialog()
|
|
79
|
-
},
|
|
80
|
-
[props.version]
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
const openDialog = useCallback(() => {
|
|
84
|
-
document.body.classList.add('g-noscroll')
|
|
85
|
-
setShowBanner(false)
|
|
86
|
-
setShowDialog(true)
|
|
87
|
-
}, [])
|
|
88
|
-
|
|
89
|
-
// Setup the event handler to open the dialog imperatively
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
emitter.on('openDialog', openDialog)
|
|
92
|
-
return () => {
|
|
93
|
-
emitter.off('openDialog', openDialog)
|
|
94
|
-
}
|
|
95
|
-
}, [openDialog])
|
|
96
|
-
|
|
97
|
-
// Setup the event handler to save and load analytics imperatively
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
emitter.on('saveAndLoadAnalytics', saveAndLoadAnalytics)
|
|
100
|
-
return () => {
|
|
101
|
-
emitter.off('saveAndLoadAnalytics', saveAndLoadAnalytics)
|
|
102
|
-
}
|
|
103
|
-
}, [saveAndLoadAnalytics])
|
|
104
|
-
|
|
105
|
-
// Show banner if there are no preferences or the version mismatches
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
setShowBanner(hasEmptyPreferencesOrVersionMismatch)
|
|
108
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- We only want to run this check once
|
|
109
|
-
}, [])
|
|
110
|
-
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
if (hasEmptyPreferencesOrVersionMismatch && isInUS()) {
|
|
113
|
-
saveAndLoadAnalytics({ loadAll: true })
|
|
114
|
-
setShowBanner(true)
|
|
115
|
-
}
|
|
116
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- We only want to run this check once
|
|
117
|
-
}, [])
|
|
118
|
-
|
|
119
|
-
const escFunction = useCallback(
|
|
120
|
-
(event) => {
|
|
121
|
-
if (showDialog && event.key === 'Escape') {
|
|
122
|
-
closeDialog()
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
[showDialog]
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
useEffect(() => {
|
|
129
|
-
document.addEventListener('keydown', escFunction, false)
|
|
130
|
-
|
|
131
|
-
return () => {
|
|
132
|
-
document.removeEventListener('keydown', escFunction, false)
|
|
133
|
-
}
|
|
134
|
-
}, [escFunction])
|
|
135
|
-
|
|
136
|
-
const filteredAdditionalServices = props.additionalServices?.filter(
|
|
137
|
-
(service) => service.shouldLoad === undefined || service.shouldLoad()
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<div className={classNames(s.root, props.className)}>
|
|
142
|
-
{/* Consent banner at the bottom */}
|
|
143
|
-
{showBanner && (
|
|
144
|
-
<ConsentBanner
|
|
145
|
-
preferences={preferences}
|
|
146
|
-
privacyPolicyLink={props.privacyPolicyLink}
|
|
147
|
-
cookiePolicyLink={props.cookiePolicyLink}
|
|
148
|
-
onManagePreferences={() => {
|
|
149
|
-
openDialog()
|
|
150
|
-
if (props.onManagePreferences) {
|
|
151
|
-
props.onManagePreferences()
|
|
152
|
-
}
|
|
153
|
-
}}
|
|
154
|
-
onAccept={() => {
|
|
155
|
-
saveAndLoadAnalytics({ loadAll: true })
|
|
156
|
-
if (props.onAcceptAll) {
|
|
157
|
-
props.onAcceptAll()
|
|
158
|
-
}
|
|
159
|
-
}}
|
|
160
|
-
/>
|
|
161
|
-
)}
|
|
162
|
-
{/* Consent manager preferences dialog */}
|
|
163
|
-
<ConsentPreferences
|
|
164
|
-
version={props.version}
|
|
165
|
-
segmentWriteKey={props.segmentWriteKey}
|
|
166
|
-
segmentServices={props.segmentServices}
|
|
167
|
-
additionalServices={filteredAdditionalServices}
|
|
168
|
-
preferences={preferences}
|
|
169
|
-
categories={props.categories || []}
|
|
170
|
-
privacyPolicyLink={props.privacyPolicyLink}
|
|
171
|
-
cookiePolicyLink={props.cookiePolicyLink}
|
|
172
|
-
saveAndLoadAnalytics={saveAndLoadAnalytics}
|
|
173
|
-
showDialog={showDialog}
|
|
174
|
-
/>
|
|
175
|
-
|
|
176
|
-
<SegmentScript
|
|
177
|
-
preferences={preferences}
|
|
178
|
-
writeKey={props.segmentWriteKey}
|
|
179
|
-
/>
|
|
180
|
-
<CustomScripts
|
|
181
|
-
preferences={preferences}
|
|
182
|
-
services={filteredAdditionalServices}
|
|
183
|
-
/>
|
|
184
|
-
</div>
|
|
185
|
-
)
|
|
186
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# Consent Manager Loader
|
|
2
|
-
|
|
3
|
-
The consent manager component is large and complex, with many configurable options. The consent manager loader interface makes a bunch of assumptions and strips down the number of configurable options to very few, which is enough to do the trick for most of our web properties.
|
|
4
|
-
|
|
5
|
-
The loader also ships with a couple **presets**, which load in standard sets of tracking scripts that we tend to adhere towards. Currently, the available options are `oss` and `enterprise`.
|
|
6
|
-
|
|
7
|
-
## Usage
|
|
8
|
-
|
|
9
|
-
Code block below shows basic usage:
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
import createConsentManager from '@web/react-components/src/components/consent-manager/loader'
|
|
13
|
-
|
|
14
|
-
const { ConsentManager, openConsentManager } = createConsentManager({
|
|
15
|
-
preset: 'oss',
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
// sample implementation
|
|
19
|
-
export default function SomePage() {
|
|
20
|
-
return (
|
|
21
|
-
<>
|
|
22
|
-
<p>my page content</p>
|
|
23
|
-
<ConsentManager />
|
|
24
|
-
</>
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Check out the [types](types.ts) to get an overview of the full options that can be passed in!
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export default [
|
|
2
|
-
{
|
|
3
|
-
name: 'Functional',
|
|
4
|
-
description:
|
|
5
|
-
'Functional services provide a utility to the website, such as the ability to log in, or to get live support. Disabling any of these scripts will cause that utility to be missing from the site.',
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
name: 'Analytics',
|
|
9
|
-
description:
|
|
10
|
-
'Analytics services keep track of page traffic and user behavior while browsing the site. We use this data internally to improve the usability and performance of the site. Disabling any of these scripts makes it more difficult for us to understand how our site is being used, and slower to improve it.',
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
name: 'Email Marketing',
|
|
14
|
-
description:
|
|
15
|
-
'Email Marketing services track user behavior while browsing the site. We use this data internally in our marketing efforts to provide users contextually relevant information based off of their behaviors. Disabling any of these scripts makes it more difficult for us to provide you contextually relevant information.',
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
name: 'Advertising',
|
|
19
|
-
description:
|
|
20
|
-
"Advertising services allow us to keep track of whether our company advertising campaigns have been successful or not. We absolutely never display any third party advertisements on our own websites, or sell any user data to third parties. Disabling any of these scripts makes it more difficult for us to gauge whether our company's advertisements are resonating with current and potential future customers.",
|
|
21
|
-
},
|
|
22
|
-
]
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import ConsentManagerComponent, { open, saveAndLoadAnalytics } from '../'
|
|
3
|
-
import defaultCategories from './categories'
|
|
4
|
-
import ossPreset from './presets/oss'
|
|
5
|
-
import enterprisePreset from './presets/enterprise'
|
|
6
|
-
import {
|
|
7
|
-
ConsentManagerCategory,
|
|
8
|
-
ConsentManagerPreferences,
|
|
9
|
-
ConsentManagerProps,
|
|
10
|
-
ConsentManagerService,
|
|
11
|
-
} from '../types'
|
|
12
|
-
|
|
13
|
-
const VERSION = 4
|
|
14
|
-
const COMPANY_NAME = 'HashiCorp'
|
|
15
|
-
const PRIVACY_LINK = 'https://www.hashicorp.com/privacy'
|
|
16
|
-
const COOKIES_LINK = 'https://www.hashicorp.com/cookies'
|
|
17
|
-
|
|
18
|
-
export default function createConsentManager({
|
|
19
|
-
segmentWriteKey = process.env.SEGMENT_WRITE_KEY,
|
|
20
|
-
preset,
|
|
21
|
-
segmentServices,
|
|
22
|
-
otherServices,
|
|
23
|
-
categories,
|
|
24
|
-
forceShow = false,
|
|
25
|
-
onAcceptAll,
|
|
26
|
-
onManagePreferences,
|
|
27
|
-
}: {
|
|
28
|
-
segmentWriteKey?: string
|
|
29
|
-
preset?: 'oss' | 'enterprise'
|
|
30
|
-
segmentServices?: ConsentManagerService[]
|
|
31
|
-
otherServices?: ConsentManagerService[]
|
|
32
|
-
categories?: ConsentManagerCategory[]
|
|
33
|
-
forceShow?: boolean
|
|
34
|
-
onAcceptAll?: () => void
|
|
35
|
-
onManagePreferences?: () => void
|
|
36
|
-
}): {
|
|
37
|
-
ConsentManager: typeof ConsentManagerComponent
|
|
38
|
-
openConsentManager: () => void
|
|
39
|
-
saveAndLoadAnalytics: (preferences: ConsentManagerPreferences) => void
|
|
40
|
-
} {
|
|
41
|
-
// if hashi env is present, check against it. if not, fall back to checking node env
|
|
42
|
-
const isProd = process.env.HASHI_ENV
|
|
43
|
-
? process.env.HASHI_ENV === 'production'
|
|
44
|
-
: process.env.NODE_ENV === 'production'
|
|
45
|
-
|
|
46
|
-
if (!process.env.HASHI_ENV)
|
|
47
|
-
console.warn(
|
|
48
|
-
'Consent manager expects the "HASHI_ENV" environment variable to be set in order to ensure proper analytics tracking. Please make sure "HASHI_ENV" is set appropriately within your app.'
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
// set the correct segment key based on environment
|
|
52
|
-
const segmentKey = isProd
|
|
53
|
-
? segmentWriteKey
|
|
54
|
-
: '0EXTgkNx0Ydje2PGXVbRhpKKoe5wtzcE'
|
|
55
|
-
|
|
56
|
-
// next we build the config objct, kicking it off with the default values
|
|
57
|
-
let config: ConsentManagerProps = {
|
|
58
|
-
version: VERSION,
|
|
59
|
-
companyName: COMPANY_NAME,
|
|
60
|
-
privacyPolicyLink: PRIVACY_LINK,
|
|
61
|
-
cookiePolicyLink: COOKIES_LINK,
|
|
62
|
-
segmentWriteKey: segmentKey,
|
|
63
|
-
categories: defaultCategories,
|
|
64
|
-
forceShow,
|
|
65
|
-
onAcceptAll,
|
|
66
|
-
onManagePreferences,
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// add preset values if present
|
|
70
|
-
if (preset === 'oss') config = { ...config, ...ossPreset }
|
|
71
|
-
if (preset === 'enterprise') config = { ...config, ...enterprisePreset }
|
|
72
|
-
|
|
73
|
-
// add any other custom values nondestructively if present
|
|
74
|
-
if (segmentServices) {
|
|
75
|
-
if (!config.segmentServices) config.segmentServices = []
|
|
76
|
-
config.segmentServices.push(...segmentServices)
|
|
77
|
-
}
|
|
78
|
-
if (otherServices) {
|
|
79
|
-
if (!config.additionalServices) config.additionalServices = []
|
|
80
|
-
config.additionalServices.push(...otherServices)
|
|
81
|
-
}
|
|
82
|
-
if (categories) config.categories!.push(...categories)
|
|
83
|
-
|
|
84
|
-
// finally, we return a HOC that will render the fully configured consent manager
|
|
85
|
-
return {
|
|
86
|
-
ConsentManager: function ConsentManagerWrapper() {
|
|
87
|
-
return (
|
|
88
|
-
<ConsentManagerComponent {...config} categories={config.categories!} />
|
|
89
|
-
)
|
|
90
|
-
},
|
|
91
|
-
openConsentManager: open,
|
|
92
|
-
saveAndLoadAnalytics,
|
|
93
|
-
}
|
|
94
|
-
}
|