pr360-questionnaire 2.1.11 → 2.3.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/js/calendar.ts CHANGED
@@ -2,6 +2,7 @@ import { html, LitElement } from "lit";
2
2
  import { property } from 'lit/decorators.js'
3
3
  import { Calendar } from "@fullcalendar/core";
4
4
  import dayGridPlugin from "@fullcalendar/daygrid";
5
+ import timeGridPlugin from "@fullcalendar/timegrid";
5
6
  import resourceTimelinePlugin from "@fullcalendar/resource-timeline";
6
7
  import momentPlugin from "@fullcalendar/moment";
7
8
 
@@ -41,16 +42,16 @@ export class CalendarElement extends LitElement {
41
42
  let calendarEl: HTMLElement = document.getElementById('full-calendar')!;
42
43
  this.calendar = new Calendar(calendarEl, {
43
44
  schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives",
44
- plugins: [resourceTimelinePlugin, momentPlugin, dayGridPlugin],
45
+ plugins: [resourceTimelinePlugin, momentPlugin, dayGridPlugin, timeGridPlugin],
45
46
  slotMinWidth: 70,
46
47
  slotMinTime: "08:00",
47
48
  slotMaxTime: "20:00",
48
49
  stickyHeaderDates: true,
49
- initialView: 'dayGridWeek',
50
+ initialView: 'timeGridWeek',
50
51
  headerToolbar: {
51
52
  left: 'prev,next today',
52
53
  center: "title",
53
- right: 'dayGridMonth, dayGridWeek, dayGridDay'
54
+ right: 'dayGridMonth, timeGridWeek, timeGridDay'
54
55
  },
55
56
  titleFormat: "dddd, MMM D, YYYY",
56
57
  events: JSON.parse(this.events),
@@ -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', 'outcome'],
46
+ properties: ['currentStep', 'visitedNodes', 'phoneNumber', 'links', 'accountActive', 'questionnaireDepth', 'providers', 'outcome', 'verificationError', 'canSelfBook', 'bookingUrl', 'siteSource'],
40
47
  events: {
41
- send: ['answerQuestion', 'submitContactInfo', 'goBack', 'setUtmParams']
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;
@@ -70,6 +77,18 @@ export class QuestionnaireElement extends LitElement {
70
77
 
71
78
  @state() outcome: string;
72
79
 
80
+ @state()
81
+ verificationError: string | null = null;
82
+
83
+ @state()
84
+ canSelfBook: boolean = false;
85
+
86
+ @state()
87
+ bookingUrl: string;
88
+
89
+ @state()
90
+ siteSource: string;
91
+
73
92
  @property()
74
93
  @liveStateConfig('url')
75
94
  url: string | undefined;
@@ -104,6 +123,9 @@ export class QuestionnaireElement extends LitElement {
104
123
  @query('#insurance_provider')
105
124
  insuranceProviderSelect: HTMLSelectElement| undefined;
106
125
 
126
+ @query('#verification_code')
127
+ verificationCodeInput: HTMLInputElement | undefined;
128
+
107
129
  @query('form')
108
130
  contactInfoForm: HTMLFormElement | undefined;
109
131
 
@@ -205,6 +227,7 @@ export class QuestionnaireElement extends LitElement {
205
227
  case "Question": return this.renderQuestion();
206
228
  case "ContactInfo": return this.renderContactInfo();
207
229
  case "Video": return this.renderVideo();
230
+ case "Verification": return this.renderVerification();
208
231
  }
209
232
  }
210
233
 
@@ -217,8 +240,16 @@ export class QuestionnaireElement extends LitElement {
217
240
  <div class="questionnaire-illustration"><img src=${this.contactInfoImageUrl()}> </div>
218
241
  <div class="questionnaire--question"><h2 class="u-padding--bt">${(this.currentStep as Video).text}</h2></div>
219
242
  <vimeo-video controls src=${(this.currentStep as Video).url} class="questionnaire--video"></vimeo-video>
220
- <div data-test-id="questionnaire-info"><h4>Thank you for completing the assessment. We'll be calling you within 24 hours with more information.</h4></div>
221
- <div data-test-id="site-phone-number"><h4>If you'd like to speak with us sooner, </br> please feel free to</h4><a href="tel:${this.phoneNumber}"><h1>Call Us</h1></a></div>
243
+ ${this.canSelfBook ?
244
+ html`
245
+ <div data-test-id="questionnaire-info"><h4>Thank you for completing the assessment. Based on your results ${this.siteSource || 'we'} would like to help you set up an initial call with a professional.</h4></div>
246
+ <button class="button--link" type="button" href=${this.bookingUrl} target="_blank">Book Appointment</button>
247
+ <div data-test-id="site-phone-number"><h4>Or if you'd like to speak with ${this.siteSource || 'us'}, </br> please feel free to</h4><a href="tel:${this.phoneNumber}"><h1>Call ${this.siteSource || 'Us'}</h1></a></div>
248
+ ` :
249
+ html`
250
+ <div data-test-id="questionnaire-info"><h4>Thank you for completing the assessment. ${this.siteSource || 'We'} will be calling you within 24 hours with more information.</h4></div>
251
+ <div data-test-id="site-phone-number"><h4>If you'd like to speak with ${this.siteSource || 'us'} sooner, </br> please feel free to</h4><a href="tel:${this.phoneNumber}"><h1>Call ${this.siteSource || 'Us'}</h1></a></div>
252
+ `}
222
253
  </div>
223
254
  </div>
224
255
  </div>
@@ -231,7 +262,7 @@ export class QuestionnaireElement extends LitElement {
231
262
  <div class="questionnaire-illustration"><img src=${this.contactInfoImageUrl()}> </div>
232
263
  <div class="form__container">
233
264
  <div class="header">
234
- <h1>We think we can help.</h1>
265
+ <h1>${this.siteSource ? `${this.siteSource} can help.` : 'We think we can help.'}</h1>
235
266
  <h2>${(this.currentStep as ContactInfo).text}</h2>
236
267
  </div>
237
268
  <form @submit=${this.submitContactInfo}>
@@ -271,7 +302,7 @@ export class QuestionnaireElement extends LitElement {
271
302
  </div>
272
303
  <div class="footer u-text-center">
273
304
  <button class="button button--primary u-push-top" type="submit">Get Assessment</button>
274
- <p>By clicking Submit, you acknowledge that you have read and agree to the <a href=${this.links['terms_conditions']} target="_blank">Terms of Use</a>
305
+ <p>By clicking Get Assessment, you consent to receive text messages (SMS) and phone calls, and you acknowledge that you have read and agree to the <a href=${this.links['terms_conditions']} target="_blank">Terms of Use</a>
275
306
  and the <a href=${this.links['privacy_policy']} target="_blank">Privacy Policy</a></p>
276
307
  </div>
277
308
  </div>
@@ -282,6 +313,38 @@ export class QuestionnaireElement extends LitElement {
282
313
  `;
283
314
  }
284
315
 
316
+ renderVerification() {
317
+ const verification = this.currentStep as Verification;
318
+ return html`
319
+ <div class="questionnaire fade-in fade-out">
320
+ <div class="questionnaire-illustration"><img src=${this.contactInfoImageUrl()}> </div>
321
+ <div class="form__container">
322
+ <div class="header">
323
+ <h1>Verify Your Phone Number</h1>
324
+ <h2>${verification.text}</h2>
325
+ <p class="verification-phone-number">We've sent a verification code to ${verification.phoneNumber}</p>
326
+ </div>
327
+ <form @submit=${this.submitVerification}>
328
+ <div class="form verification-form">
329
+ <div class="verification-input-container">
330
+ <label for="verification_code">Verification Code</label>
331
+ <input type="text" id="verification_code" name="verification_code" required
332
+ pattern="^[a-zA-Z0-9]+$"
333
+ title="Please enter the verification code" />
334
+ ${this.verificationError ? html`<div class="error">${this.verificationError}</div>` : ''}
335
+ </div>
336
+ </div>
337
+ <div class="footer u-text-center">
338
+ <button class="button button--primary u-push-top" type="submit">Submit</button>
339
+ <button class="button--link" type="button" @click=${this.goBackToContactInfo}>Go Back</button>
340
+ <button class="button--link verification-resend-button" type="button" @click=${this.resendCode}>Resend Code</button>
341
+ </div>
342
+ </form>
343
+ </div>
344
+ </div>
345
+ `;
346
+ }
347
+
285
348
  imageSrc() {
286
349
  return this.absolutifyImageUrl((this.currentStep as Question).url);
287
350
  }
@@ -383,4 +446,29 @@ export class QuestionnaireElement extends LitElement {
383
446
  }));
384
447
  }
385
448
  }
449
+
450
+ submitVerification(event: Event) {
451
+ event.stopPropagation();
452
+ event.preventDefault();
453
+
454
+ const code = this.verificationCodeInput?.value;
455
+ if (code) {
456
+ this.verificationError = null;
457
+ // Extract node_id from verification id (format: "verification_nodeId")
458
+ const nodeId = this.currentStep?.id.replace('verification_', '');
459
+ this.dispatchEvent(new CustomEvent('submitVerification', {
460
+ detail: { verificationCode: code, nodeId: nodeId }
461
+ }));
462
+ }
463
+ }
464
+
465
+ resendCode(event: Event) {
466
+ event.stopPropagation();
467
+ this.dispatchEvent(new CustomEvent('resendCode'));
468
+ }
469
+
470
+ goBackToContactInfo(event: Event) {
471
+ event.stopPropagation();
472
+ this.dispatchEvent(new CustomEvent('goBack'));
473
+ }
386
474
  }
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.11",
4
+ "version": "2.3.0",
5
5
  "main": "dist/index.js",
6
6
  "author": {
7
7
  "email": "chris@launchscout.com",
@@ -32,6 +32,7 @@
32
32
  "mermaid-chart": "launchscout/mermaid-chart",
33
33
  "@fullcalendar/core": "^6.1.8",
34
34
  "@fullcalendar/daygrid": "^6.1.8",
35
+ "@fullcalendar/timegrid": "^6.1.8",
35
36
  "@fullcalendar/moment": "^6.1.11",
36
37
  "@fullcalendar/resource": "^6.1.8",
37
38
  "@fullcalendar/resource-timeline": "^6.1.8"
@@ -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
  });