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/build/questionnaire-test.js +10856 -13266
- package/css/app.scss +3 -0
- package/css/base/_layout.scss +64 -10
- package/css/components/_booking.scss +449 -0
- package/css/components/_buttons.scss +83 -1
- package/css/components/_dropdown.scss +103 -0
- package/css/components/_forms.scss +80 -0
- package/css/components/_inputs.scss +5 -0
- package/css/components/_modal.scss +23 -3
- package/css/components/_network-live.scss +210 -0
- package/css/components/_tables.scss +123 -26
- package/css/questionnaire.lit.scss +48 -0
- package/dist/index.js +998 -2630
- package/js/calendar.ts +4 -3
- package/js/questionnaire.ts +95 -7
- package/package.json +2 -1
- package/test/questionnaire-test.ts +48 -1
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: '
|
|
50
|
+
initialView: 'timeGridWeek',
|
|
50
51
|
headerToolbar: {
|
|
51
52
|
left: 'prev,next today',
|
|
52
53
|
center: "title",
|
|
53
|
-
right: 'dayGridMonth,
|
|
54
|
+
right: 'dayGridMonth, timeGridWeek, timeGridDay'
|
|
54
55
|
},
|
|
55
56
|
titleFormat: "dddd, MMM D, YYYY",
|
|
56
57
|
events: JSON.parse(this.events),
|
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', '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
|
-
|
|
221
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
});
|