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.
@@ -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,7 +1,7 @@
1
1
  {
2
2
  "name": "pr360-questionnaire",
3
3
  "description": "An element to render a questionnaire for PatientReach 360.",
4
- "version": "2.1.10",
4
+ "version": "2.2.11",
5
5
  "main": "dist/index.js",
6
6
  "author": {
7
7
  "email": "chris@launchscout.com",
@@ -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
  });