pr360-questionnaire 2.1.10 → 2.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/questionnaire-test.js +10880 -13253
- package/css/app.scss +1 -0
- package/css/components/_forms.scss +80 -0
- package/css/components/_modal.scss +15 -3
- package/css/components/_network-live.scss +210 -0
- package/css/components/_prospect-details.scss +86 -0
- package/css/components/_tables.scss +4 -13
- package/css/questionnaire.lit.scss +48 -0
- package/dist/index.js +1033 -2628
- package/js/questionnaire.ts +145 -7
- package/package.json +1 -1
- package/test/questionnaire-test.ts +48 -1
package/js/questionnaire.ts
CHANGED
|
@@ -29,6 +29,13 @@ export type ContactInfo = {
|
|
|
29
29
|
text: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export type Verification = {
|
|
33
|
+
id: string;
|
|
34
|
+
type: "Verification";
|
|
35
|
+
text: string;
|
|
36
|
+
phoneNumber: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
export type Answer = {
|
|
33
40
|
id: string;
|
|
34
41
|
text: string;
|
|
@@ -36,9 +43,9 @@ export type Answer = {
|
|
|
36
43
|
|
|
37
44
|
@customElement('pr360-questionnaire')
|
|
38
45
|
@liveState({
|
|
39
|
-
properties: ['currentStep', 'visitedNodes', 'phoneNumber', 'links', 'accountActive', 'questionnaireDepth', 'providers'],
|
|
46
|
+
properties: ['currentStep', 'visitedNodes', 'phoneNumber', 'links', 'accountActive', 'questionnaireDepth', 'providers', 'outcome', 'verificationError'],
|
|
40
47
|
events: {
|
|
41
|
-
send: ['answerQuestion', 'submitContactInfo', 'goBack']
|
|
48
|
+
send: ['answerQuestion', 'submitContactInfo', 'goBack', 'setUtmParams', 'submitVerification', 'resendCode']
|
|
42
49
|
},
|
|
43
50
|
provide: {
|
|
44
51
|
scope: window,
|
|
@@ -49,7 +56,7 @@ export class QuestionnaireElement extends LitElement {
|
|
|
49
56
|
static styles = questionnaireStyles;
|
|
50
57
|
|
|
51
58
|
@state()
|
|
52
|
-
currentStep: Question | ContactInfo | Video | undefined;
|
|
59
|
+
currentStep: Question | ContactInfo | Video | Verification | undefined;
|
|
53
60
|
|
|
54
61
|
@state()
|
|
55
62
|
accountActive: boolean = true;
|
|
@@ -68,6 +75,11 @@ export class QuestionnaireElement extends LitElement {
|
|
|
68
75
|
@state()
|
|
69
76
|
providers: string[];
|
|
70
77
|
|
|
78
|
+
@state() outcome: string;
|
|
79
|
+
|
|
80
|
+
@state()
|
|
81
|
+
verificationError: string | null = null;
|
|
82
|
+
|
|
71
83
|
@property()
|
|
72
84
|
@liveStateConfig('url')
|
|
73
85
|
url: string | undefined;
|
|
@@ -102,6 +114,9 @@ export class QuestionnaireElement extends LitElement {
|
|
|
102
114
|
@query('#insurance_provider')
|
|
103
115
|
insuranceProviderSelect: HTMLSelectElement| undefined;
|
|
104
116
|
|
|
117
|
+
@query('#verification_code')
|
|
118
|
+
verificationCodeInput: HTMLInputElement | undefined;
|
|
119
|
+
|
|
105
120
|
@query('form')
|
|
106
121
|
contactInfoForm: HTMLFormElement | undefined;
|
|
107
122
|
|
|
@@ -110,8 +125,69 @@ export class QuestionnaireElement extends LitElement {
|
|
|
110
125
|
return `questionnaires:${this.siteId}`;
|
|
111
126
|
}
|
|
112
127
|
|
|
128
|
+
@liveStateConfig('params')
|
|
129
|
+
get params() {
|
|
130
|
+
return this.getUtmParams();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get liveStateUrl() {
|
|
134
|
+
return this.url;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private getUtmParams(): Record<string, string> {
|
|
138
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
139
|
+
const utmParams = Object.fromEntries(
|
|
140
|
+
Array.from(urlParams.entries()).filter(([key, _]) =>
|
|
141
|
+
key.startsWith('utm_')
|
|
142
|
+
)
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Add additional UTM tracking data
|
|
146
|
+
utmParams['utm_url'] = window.location.href;
|
|
147
|
+
utmParams['utm_referring_page'] = document.referrer || '';
|
|
148
|
+
|
|
149
|
+
return utmParams;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private getReferrerUrl(): string {
|
|
153
|
+
const referrer = document.referrer;
|
|
154
|
+
|
|
155
|
+
if (!referrer) {
|
|
156
|
+
return '';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const referrerUrl = new URL(referrer);
|
|
160
|
+
// Don't track internal referrers (same domain)
|
|
161
|
+
if (referrerUrl.hostname === window.location.hostname) {
|
|
162
|
+
return '';
|
|
163
|
+
}
|
|
164
|
+
return referrer;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private trackQuestionnaireCompletion() {
|
|
168
|
+
window['dataLayer'] = window['dataLayer'] || [];
|
|
169
|
+
|
|
170
|
+
const eventData = {
|
|
171
|
+
'event': 'questionnaireComplete',
|
|
172
|
+
'formData': {outcome: this.outcome},
|
|
173
|
+
'timestamp': new Date().toISOString(),
|
|
174
|
+
'url': window.location.href
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
window['dataLayer'].push(eventData);
|
|
178
|
+
}
|
|
179
|
+
|
|
113
180
|
connectedCallback() {
|
|
114
181
|
super.connectedCallback();
|
|
182
|
+
|
|
183
|
+
// Send UTM parameters to channel after connection is established
|
|
184
|
+
setTimeout(() => {
|
|
185
|
+
const utmParams = this.getUtmParams();
|
|
186
|
+
this.dispatchEvent(new CustomEvent('setUtmParams', {
|
|
187
|
+
detail: utmParams
|
|
188
|
+
}));
|
|
189
|
+
}, 100);
|
|
190
|
+
|
|
115
191
|
if (document.querySelector('script#hs-script-loader') === null) {
|
|
116
192
|
const script = document.createElement('script');
|
|
117
193
|
script.async = true;
|
|
@@ -122,6 +198,14 @@ export class QuestionnaireElement extends LitElement {
|
|
|
122
198
|
}
|
|
123
199
|
}
|
|
124
200
|
|
|
201
|
+
updated() {
|
|
202
|
+
if (this.open) { this.openModal(); }
|
|
203
|
+
|
|
204
|
+
if (this.outcome) {
|
|
205
|
+
this.trackQuestionnaireCompletion();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
125
209
|
render() {
|
|
126
210
|
return html`
|
|
127
211
|
${this.open ? '<div></div>' : html`<button class="button button--begin" ?disabled=${!this.accountActive} part="begin-button" @click=${this.openModal}><slot>Begin</slot></button>`}
|
|
@@ -129,15 +213,12 @@ export class QuestionnaireElement extends LitElement {
|
|
|
129
213
|
`;
|
|
130
214
|
}
|
|
131
215
|
|
|
132
|
-
updated() {
|
|
133
|
-
if (this.open) { this.openModal(); }
|
|
134
|
-
}
|
|
135
|
-
|
|
136
216
|
renderCurrentStep() {
|
|
137
217
|
switch (this.currentStep?.type) {
|
|
138
218
|
case "Question": return this.renderQuestion();
|
|
139
219
|
case "ContactInfo": return this.renderContactInfo();
|
|
140
220
|
case "Video": return this.renderVideo();
|
|
221
|
+
case "Verification": return this.renderVerification();
|
|
141
222
|
}
|
|
142
223
|
}
|
|
143
224
|
|
|
@@ -215,6 +296,38 @@ export class QuestionnaireElement extends LitElement {
|
|
|
215
296
|
`;
|
|
216
297
|
}
|
|
217
298
|
|
|
299
|
+
renderVerification() {
|
|
300
|
+
const verification = this.currentStep as Verification;
|
|
301
|
+
return html`
|
|
302
|
+
<div class="questionnaire fade-in fade-out">
|
|
303
|
+
<div class="questionnaire-illustration"><img src=${this.contactInfoImageUrl()}> </div>
|
|
304
|
+
<div class="form__container">
|
|
305
|
+
<div class="header">
|
|
306
|
+
<h1>Verify Your Phone Number</h1>
|
|
307
|
+
<h2>${verification.text}</h2>
|
|
308
|
+
<p class="verification-phone-number">We've sent a verification code to ${verification.phoneNumber}</p>
|
|
309
|
+
</div>
|
|
310
|
+
<form @submit=${this.submitVerification}>
|
|
311
|
+
<div class="form verification-form">
|
|
312
|
+
<div class="verification-input-container">
|
|
313
|
+
<label for="verification_code">Verification Code</label>
|
|
314
|
+
<input type="text" id="verification_code" name="verification_code" required
|
|
315
|
+
pattern="^[a-zA-Z0-9]+$"
|
|
316
|
+
title="Please enter the verification code" />
|
|
317
|
+
${this.verificationError ? html`<div class="error">${this.verificationError}</div>` : ''}
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
<div class="footer u-text-center">
|
|
321
|
+
<button class="button button--primary u-push-top" type="submit">Submit</button>
|
|
322
|
+
<button class="button--link" type="button" @click=${this.goBackToContactInfo}>Go Back</button>
|
|
323
|
+
<button class="button--link verification-resend-button" type="button" @click=${this.resendCode}>Resend Code</button>
|
|
324
|
+
</div>
|
|
325
|
+
</form>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
`;
|
|
329
|
+
}
|
|
330
|
+
|
|
218
331
|
imageSrc() {
|
|
219
332
|
return this.absolutifyImageUrl((this.currentStep as Question).url);
|
|
220
333
|
}
|
|
@@ -316,4 +429,29 @@ export class QuestionnaireElement extends LitElement {
|
|
|
316
429
|
}));
|
|
317
430
|
}
|
|
318
431
|
}
|
|
432
|
+
|
|
433
|
+
submitVerification(event: Event) {
|
|
434
|
+
event.stopPropagation();
|
|
435
|
+
event.preventDefault();
|
|
436
|
+
|
|
437
|
+
const code = this.verificationCodeInput?.value;
|
|
438
|
+
if (code) {
|
|
439
|
+
this.verificationError = null;
|
|
440
|
+
// Extract node_id from verification id (format: "verification_nodeId")
|
|
441
|
+
const nodeId = this.currentStep?.id.replace('verification_', '');
|
|
442
|
+
this.dispatchEvent(new CustomEvent('submitVerification', {
|
|
443
|
+
detail: { verificationCode: code, nodeId: nodeId }
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
resendCode(event: Event) {
|
|
449
|
+
event.stopPropagation();
|
|
450
|
+
this.dispatchEvent(new CustomEvent('resendCode'));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
goBackToContactInfo(event: Event) {
|
|
454
|
+
event.stopPropagation();
|
|
455
|
+
this.dispatchEvent(new CustomEvent('goBack'));
|
|
456
|
+
}
|
|
319
457
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '../js/questionnaire';
|
|
2
2
|
import { expect } from "@esm-bundle/chai";
|
|
3
|
-
import { ContactInfo, QuestionnaireElement} from '../js/questionnaire';
|
|
3
|
+
import { ContactInfo, QuestionnaireElement, Verification} from '../js/questionnaire';
|
|
4
4
|
import { fixture } from '@open-wc/testing';
|
|
5
5
|
import { sendKeys } from '@web/test-runner-commands';
|
|
6
6
|
import LiveState from 'phx-live-state';
|
|
@@ -84,4 +84,51 @@ describe('questionnaire test', async () => {
|
|
|
84
84
|
console.log(img);
|
|
85
85
|
expect(img?.src).to.equal("https://foo.bar/images/foo");
|
|
86
86
|
});
|
|
87
|
+
|
|
88
|
+
it('renders verification screen', async () => {
|
|
89
|
+
questionnaireElement.currentStep = {
|
|
90
|
+
id: "verification_123",
|
|
91
|
+
type: "Verification",
|
|
92
|
+
text: "Please enter the verification code we sent to your phone.",
|
|
93
|
+
phoneNumber: "555-123-4567"
|
|
94
|
+
} as Verification;
|
|
95
|
+
questionnaireElement.links = {
|
|
96
|
+
contact_info_image: '/images/contact.jpg'
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const button = questionnaireElement.querySelector('button');
|
|
100
|
+
button?.click();
|
|
101
|
+
await questionnaireElement.updateComplete;
|
|
102
|
+
|
|
103
|
+
expect(questionnaireElement.shadowRoot?.textContent).to.contain("Verify Your Phone Number");
|
|
104
|
+
expect(questionnaireElement.shadowRoot?.textContent).to.contain("555-123-4567");
|
|
105
|
+
expect(questionnaireElement.shadowRoot?.querySelector('#verification_code')).to.exist;
|
|
106
|
+
expect(questionnaireElement.shadowRoot?.textContent).to.contain("Submit");
|
|
107
|
+
expect(questionnaireElement.shadowRoot?.textContent).to.contain("Resend Code");
|
|
108
|
+
expect(questionnaireElement.shadowRoot?.textContent).to.contain("Go Back");
|
|
109
|
+
|
|
110
|
+
// Verify Go Back button is at top left (button--link class)
|
|
111
|
+
const goBackButton = Array.from(questionnaireElement.shadowRoot?.querySelectorAll('.button--link') || [])
|
|
112
|
+
.find(btn => btn.textContent?.includes('Go Back'));
|
|
113
|
+
expect(goBackButton).to.exist;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('displays verification error when present', async () => {
|
|
117
|
+
questionnaireElement.currentStep = {
|
|
118
|
+
id: "verification_123",
|
|
119
|
+
type: "Verification",
|
|
120
|
+
text: "Please enter the verification code we sent to your phone.",
|
|
121
|
+
phoneNumber: "555-123-4567"
|
|
122
|
+
} as Verification;
|
|
123
|
+
questionnaireElement.verificationError = "Invalid verification code";
|
|
124
|
+
questionnaireElement.links = {
|
|
125
|
+
contact_info_image: '/images/contact.jpg'
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const button = questionnaireElement.querySelector('button');
|
|
129
|
+
button?.click();
|
|
130
|
+
await questionnaireElement.updateComplete;
|
|
131
|
+
|
|
132
|
+
expect(questionnaireElement.shadowRoot?.textContent).to.contain("Invalid verification code");
|
|
133
|
+
});
|
|
87
134
|
});
|