@tellescope/react-components 1.230.2 → 1.232.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/lib/cjs/Forms/forms.js +2 -2
- package/lib/cjs/Forms/forms.js.map +1 -1
- package/lib/cjs/Forms/forms.v2.js +4 -4
- package/lib/cjs/Forms/forms.v2.js.map +1 -1
- package/lib/cjs/Forms/hooks.d.ts +111 -3
- package/lib/cjs/Forms/hooks.d.ts.map +1 -1
- package/lib/cjs/Forms/hooks.js +30 -38
- package/lib/cjs/Forms/hooks.js.map +1 -1
- package/lib/cjs/Forms/inputs.d.ts +2 -2
- package/lib/cjs/Forms/inputs.d.ts.map +1 -1
- package/lib/cjs/Forms/inputs.js +71 -18
- package/lib/cjs/Forms/inputs.js.map +1 -1
- package/lib/cjs/Forms/inputs.v2.d.ts +2 -4
- package/lib/cjs/Forms/inputs.v2.d.ts.map +1 -1
- package/lib/cjs/Forms/inputs.v2.js +13 -242
- package/lib/cjs/Forms/inputs.v2.js.map +1 -1
- package/lib/esm/CMS/components.d.ts +1 -0
- package/lib/esm/CMS/components.d.ts.map +1 -1
- package/lib/esm/Forms/form_responses.d.ts +1 -0
- package/lib/esm/Forms/form_responses.d.ts.map +1 -1
- package/lib/esm/Forms/forms.d.ts +3 -3
- package/lib/esm/Forms/forms.js +2 -2
- package/lib/esm/Forms/forms.js.map +1 -1
- package/lib/esm/Forms/forms.v2.d.ts +3 -3
- package/lib/esm/Forms/forms.v2.js +4 -4
- package/lib/esm/Forms/forms.v2.js.map +1 -1
- package/lib/esm/Forms/hooks.d.ts +112 -3
- package/lib/esm/Forms/hooks.d.ts.map +1 -1
- package/lib/esm/Forms/hooks.js +31 -39
- package/lib/esm/Forms/hooks.js.map +1 -1
- package/lib/esm/Forms/inputs.d.ts +3 -3
- package/lib/esm/Forms/inputs.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.js +72 -19
- package/lib/esm/Forms/inputs.js.map +1 -1
- package/lib/esm/Forms/inputs.v2.d.ts +3 -5
- package/lib/esm/Forms/inputs.v2.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.v2.js +15 -244
- package/lib/esm/Forms/inputs.v2.js.map +1 -1
- package/lib/esm/controls.d.ts +2 -2
- package/lib/esm/inputs.d.ts +1 -1
- package/lib/esm/inputs.native.d.ts +1 -0
- package/lib/esm/inputs.native.d.ts.map +1 -1
- package/lib/esm/state.d.ts +315 -315
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/Forms/forms.tsx +2 -2
- package/src/Forms/forms.v2.tsx +12 -12
- package/src/Forms/hooks.tsx +49 -62
- package/src/Forms/inputs.tsx +73 -6
- package/src/Forms/inputs.v2.tsx +23 -404
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellescope/react-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.232.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/cjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"@reduxjs/toolkit": "^1.6.2",
|
|
48
48
|
"@stripe/react-stripe-js": "^2.9.0",
|
|
49
49
|
"@stripe/stripe-js": "^1.52.1",
|
|
50
|
-
"@tellescope/constants": "1.
|
|
51
|
-
"@tellescope/sdk": "1.
|
|
52
|
-
"@tellescope/types-client": "1.
|
|
53
|
-
"@tellescope/types-models": "1.
|
|
54
|
-
"@tellescope/types-utilities": "1.
|
|
55
|
-
"@tellescope/utilities": "1.
|
|
56
|
-
"@tellescope/validation": "1.
|
|
50
|
+
"@tellescope/constants": "1.232.0",
|
|
51
|
+
"@tellescope/sdk": "1.232.0",
|
|
52
|
+
"@tellescope/types-client": "1.232.0",
|
|
53
|
+
"@tellescope/types-models": "1.232.0",
|
|
54
|
+
"@tellescope/types-utilities": "1.232.0",
|
|
55
|
+
"@tellescope/utilities": "1.232.0",
|
|
56
|
+
"@tellescope/validation": "1.232.0",
|
|
57
57
|
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
|
58
58
|
"@typescript-eslint/parser": "^4.33.0",
|
|
59
59
|
"css-to-react-native": "^3.0.0",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
84
84
|
"react-native": "^0.65.0 || ^0.66.0 || ^0.67.0 || ^0.68.0 || ^0.71.0"
|
|
85
85
|
},
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "b79c7d50da5ff767345e58331f483cc541abef20",
|
|
87
87
|
"publishConfig": {
|
|
88
88
|
"access": "public"
|
|
89
89
|
}
|
package/src/Forms/forms.tsx
CHANGED
|
@@ -316,7 +316,7 @@ export const QuestionForField = ({
|
|
|
316
316
|
<AppointmentBooking formResponseId={formResponseId} enduserId={enduserId} goToPreviousField={goToPreviousField} isPreviousDisabled={isPreviousDisabled} responses={responses} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Appointment Booking'>} form={form} />
|
|
317
317
|
)
|
|
318
318
|
: field.type === 'Stripe' ? (
|
|
319
|
-
<Stripe enduserId={enduserId} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} setCustomerId={setCustomerId} form={form} />
|
|
319
|
+
<Stripe enduserId={enduserId} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} setCustomerId={setCustomerId} form={form} responses={responses} enduser={enduser} />
|
|
320
320
|
)
|
|
321
321
|
: field.type === 'Chargebee' ? (
|
|
322
322
|
<Chargebee field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'Chargebee'>} setCustomerId={setCustomerId} form={form} />
|
|
@@ -325,7 +325,7 @@ export const QuestionForField = ({
|
|
|
325
325
|
<StringLong field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'string' | 'stringLong'>} form={form} />
|
|
326
326
|
)
|
|
327
327
|
: field.type === 'Rich Text' ? (
|
|
328
|
-
<RichText field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Rich Text'>} form={form} />
|
|
328
|
+
<RichText key={field.id} field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Rich Text'>} form={form} />
|
|
329
329
|
)
|
|
330
330
|
: field.type === 'email' ? (
|
|
331
331
|
<Email field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'email'>} form={form} />
|
package/src/Forms/forms.v2.tsx
CHANGED
|
@@ -204,9 +204,9 @@ export const QuestionForField = ({
|
|
|
204
204
|
) {
|
|
205
205
|
return null
|
|
206
206
|
}
|
|
207
|
-
return (
|
|
207
|
+
return (
|
|
208
208
|
// margin leaves room for error message in Question Group
|
|
209
|
-
<Flex column flex={1} style={{ marginBottom: spacing ?? 25 }} id={field.id}>
|
|
209
|
+
<Flex column flex={1} style={{ marginBottom: spacing ?? 25 }} id={field.id}>
|
|
210
210
|
{field.type !== 'Redirect' && field.title &&
|
|
211
211
|
<Typography component="h4" style={{
|
|
212
212
|
marginTop: 15, // ensures PDF display doesn't push description into overlap with logo / title at top of form
|
|
@@ -223,13 +223,15 @@ export const QuestionForField = ({
|
|
|
223
223
|
<div style={{ marginTop: 15 }}></div>
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
{
|
|
226
|
+
<Description field={field} style={{ fontSize: 14, color: '#00000099', marginBottom: 11 }} />
|
|
227
|
+
|
|
228
|
+
{feedback.length > 0 &&
|
|
227
229
|
<Flex column style={{ marginBottom: 11, marginTop: 3, }}>
|
|
228
230
|
{feedback.map((f, i) => (
|
|
229
231
|
<Typography key={i} color="error" style={{ fontSize: 20 }}>
|
|
230
232
|
{f}
|
|
231
233
|
</Typography>
|
|
232
|
-
))}
|
|
234
|
+
))}
|
|
233
235
|
</Flex>
|
|
234
236
|
}
|
|
235
237
|
|
|
@@ -308,7 +310,7 @@ export const QuestionForField = ({
|
|
|
308
310
|
<AppointmentBooking formResponseId={formResponseId} enduserId={enduserId} goToPreviousField={goToPreviousField} isPreviousDisabled={isPreviousDisabled} responses={responses} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Appointment Booking'>} form={form} />
|
|
309
311
|
)
|
|
310
312
|
: field.type === 'Stripe' ? (
|
|
311
|
-
<Stripe enduserId={enduserId} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} setCustomerId={setCustomerId} form={form} />
|
|
313
|
+
<Stripe enduserId={enduserId} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} setCustomerId={setCustomerId} form={form} responses={responses} enduser={enduser} />
|
|
312
314
|
)
|
|
313
315
|
: field.type === 'Chargebee' ? (
|
|
314
316
|
<Chargebee field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'Chargebee'>} setCustomerId={setCustomerId} form={form} />
|
|
@@ -317,7 +319,7 @@ export const QuestionForField = ({
|
|
|
317
319
|
<StringLong field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'string' | 'stringLong'>} form={form} error={!!validationMessage && (!['A response is required', 'A value must be checked', 'A file is required', 'Enter a valid phone number', 'Insurer is required'].includes(validationMessage) || value.touched)} />
|
|
318
320
|
)
|
|
319
321
|
: field.type === 'Rich Text' ? (
|
|
320
|
-
<RichText field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Rich Text'>} form={form} />
|
|
322
|
+
<RichText key={field.id} field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Rich Text'>} form={form} />
|
|
321
323
|
)
|
|
322
324
|
: field.type === 'email' ? (
|
|
323
325
|
<Email field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'email'>} form={form} error={!!validationMessage && (!['A response is required', 'A value must be checked', 'A file is required', 'Enter a valid phone number', 'Insurer is required'].includes(validationMessage) || value.touched)} />
|
|
@@ -435,17 +437,15 @@ export const QuestionForField = ({
|
|
|
435
437
|
</Flex>
|
|
436
438
|
)}
|
|
437
439
|
|
|
438
|
-
<Description field={field} style={{ fontSize: 14, color: '#00000099', marginTop: 4 }} />
|
|
439
|
-
|
|
440
440
|
{field.type !== 'Question Group' &&
|
|
441
|
-
<Typography color="error" style={{ marginTop: 3, height: 10, fontSize: 14, marginBottom: -10 }}>
|
|
441
|
+
<Typography color="error" style={{ marginTop: 3, height: 10, fontSize: 14, marginBottom: -10 }}>
|
|
442
442
|
{(validationMessage === 'A response is required' || validationMessage === 'A value must be checked' || validationMessage === 'A file is required' || 'Enter a valid phone number' || 'Insurer is required')
|
|
443
|
-
? value.touched
|
|
443
|
+
? value.touched
|
|
444
444
|
? form_display_text_for_language(form, validationMessage)
|
|
445
|
-
: null
|
|
445
|
+
: null
|
|
446
446
|
: form_display_text_for_language(form, validationMessage)
|
|
447
447
|
}
|
|
448
|
-
</Typography>
|
|
448
|
+
</Typography>
|
|
449
449
|
}
|
|
450
450
|
</Flex>
|
|
451
451
|
)
|
package/src/Forms/hooks.tsx
CHANGED
|
@@ -684,7 +684,27 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
684
684
|
setResponses(initializeFields())
|
|
685
685
|
}, [formId, initializeFields])
|
|
686
686
|
|
|
687
|
+
// Create templated versions of responses for UI display
|
|
688
|
+
// This applies template value replacements (e.g., {{enduser.BMI}}) to field titles/descriptions
|
|
689
|
+
// The templated responses are used ONLY for display and submission, not for navigation logic
|
|
690
|
+
const templatedResponses = useMemo(() => {
|
|
691
|
+
return responses.map(response => {
|
|
692
|
+
const originalField = fields.find(f => f.id === response.fieldId) || response.field
|
|
687
693
|
|
|
694
|
+
return {
|
|
695
|
+
...response,
|
|
696
|
+
fieldTitle: replace_form_field_template_values(originalField.title || '', { enduser, responses }),
|
|
697
|
+
fieldDescription: replace_form_field_template_values(originalField.description || '', { enduser, responses }),
|
|
698
|
+
fieldHtmlDescription: replace_form_field_template_values(originalField.htmlDescription || '', { enduser, responses }),
|
|
699
|
+
field: {
|
|
700
|
+
...response.field,
|
|
701
|
+
title: replace_form_field_template_values(originalField.title || '', { enduser, responses }),
|
|
702
|
+
description: replace_form_field_template_values(originalField.description || '', { enduser, responses }),
|
|
703
|
+
htmlDescription: replace_form_field_template_values(originalField.htmlDescription || '', { enduser, responses }),
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
})
|
|
707
|
+
}, [responses, fields, enduser])
|
|
688
708
|
|
|
689
709
|
// placeholders for initial files, reset when fields prop changes, since questions are now different (e.g. different form selected)
|
|
690
710
|
const fileInitRef = useRef('')
|
|
@@ -706,15 +726,28 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
706
726
|
}, [formId, initializeFiles])
|
|
707
727
|
|
|
708
728
|
const currentValue = (
|
|
709
|
-
|
|
729
|
+
templatedResponses.find(f => f.fieldId === activeField.value.id)
|
|
710
730
|
)
|
|
711
731
|
const currentFileValue = (
|
|
712
732
|
selectedFiles.find(f => f.fieldId === activeField.value.id)
|
|
713
733
|
)
|
|
714
734
|
|
|
735
|
+
// Create templated version of activeField for UI display
|
|
736
|
+
// This applies template replacements to the field's title/description
|
|
737
|
+
const templatedActiveField = useMemo(() => {
|
|
738
|
+
const templatedResponse = templatedResponses.find(r => r.fieldId === activeField.value.id)
|
|
739
|
+
if (templatedResponse) {
|
|
740
|
+
return {
|
|
741
|
+
...activeField,
|
|
742
|
+
value: templatedResponse.field
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return activeField
|
|
746
|
+
}, [activeField, templatedResponses])
|
|
747
|
+
|
|
715
748
|
const logicOptions: NextFieldLogicOptions = {
|
|
716
749
|
urlLogicValue,
|
|
717
|
-
activeResponses:
|
|
750
|
+
activeResponses: templatedResponses.filter(r => r.includeInSubmit),
|
|
718
751
|
dateOfBirth: enduser?.dateOfBirth,
|
|
719
752
|
gender: enduser?.gender,
|
|
720
753
|
state: enduser?.state,
|
|
@@ -1274,10 +1307,11 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1274
1307
|
|
|
1275
1308
|
if (!accessCode && session.type === 'enduser') throw new Error('enduser session without accessCode')
|
|
1276
1309
|
try {
|
|
1310
|
+
// Use templatedResponses for submission to ensure template values are resolved
|
|
1277
1311
|
const responsesToSubmit = (
|
|
1278
|
-
options?.includedFieldIds
|
|
1279
|
-
? options.includedFieldIds.map(id =>
|
|
1280
|
-
:
|
|
1312
|
+
options?.includedFieldIds
|
|
1313
|
+
? options.includedFieldIds.map(id => templatedResponses.find(r => r.fieldId === id)!)
|
|
1314
|
+
: templatedResponses.filter(r => r.includeInSubmit)
|
|
1281
1315
|
)
|
|
1282
1316
|
|
|
1283
1317
|
// ensure Question Group responses are included
|
|
@@ -1286,7 +1320,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1286
1320
|
if (r.answer.type !== 'Question Group') continue
|
|
1287
1321
|
|
|
1288
1322
|
for (const f of r.answer.value ?? []) {
|
|
1289
|
-
const match =
|
|
1323
|
+
const match = templatedResponses.find(r => r.fieldId === f?.id)
|
|
1290
1324
|
if (!match || responsesToSubmit.find(r => r.fieldId === match.fieldId)) continue
|
|
1291
1325
|
|
|
1292
1326
|
// hidden in group by conditional logic
|
|
@@ -1403,7 +1437,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1403
1437
|
} finally {
|
|
1404
1438
|
setSubmittingStatus(undefined)
|
|
1405
1439
|
}
|
|
1406
|
-
}, [accessCode, automationStepId, enduserId, responses, selectedFiles, session, handleUpload, existingResponses, ga4measurementId, rootResponseId, parentResponseId, calendarEventId, goBackURL, logicOptions, handleFileUpload])
|
|
1440
|
+
}, [accessCode, automationStepId, enduserId, responses, templatedResponses, selectedFiles, session, handleUpload, existingResponses, ga4measurementId, rootResponseId, parentResponseId, calendarEventId, goBackURL, logicOptions, handleFileUpload])
|
|
1407
1441
|
|
|
1408
1442
|
const isNextDisabled = useCallback(() => {
|
|
1409
1443
|
if (uploadingFiles.length) { return true }
|
|
@@ -1419,30 +1453,6 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1419
1453
|
return false
|
|
1420
1454
|
}, [activeField, validateField, uploadingFiles])
|
|
1421
1455
|
|
|
1422
|
-
// Helper function to apply templating to responses
|
|
1423
|
-
// Templates field titles/descriptions with current enduser data and form response values
|
|
1424
|
-
// Can be called whenever we need to update templates (e.g., on "next" button click)
|
|
1425
|
-
const applyTemplatingToResponses = useCallback((
|
|
1426
|
-
currentResponses: Response[]
|
|
1427
|
-
): Response[] => {
|
|
1428
|
-
return currentResponses.map(response => {
|
|
1429
|
-
const originalField = fields.find(f => f.id === response.fieldId) || response.field
|
|
1430
|
-
|
|
1431
|
-
return {
|
|
1432
|
-
...response,
|
|
1433
|
-
fieldTitle: replace_form_field_template_values(originalField.title || '', { enduser, responses: currentResponses }),
|
|
1434
|
-
fieldDescription: replace_form_field_template_values(originalField.description || '', { enduser, responses: currentResponses }),
|
|
1435
|
-
fieldHtmlDescription: replace_form_field_template_values(originalField.htmlDescription || '', { enduser, responses: currentResponses }),
|
|
1436
|
-
field: {
|
|
1437
|
-
...response.field,
|
|
1438
|
-
title: replace_form_field_template_values(originalField.title || '', { enduser, responses: currentResponses }),
|
|
1439
|
-
description: replace_form_field_template_values(originalField.description || '', { enduser, responses: currentResponses }),
|
|
1440
|
-
htmlDescription: replace_form_field_template_values(originalField.htmlDescription || '', { enduser, responses: currentResponses }),
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
})
|
|
1444
|
-
}, [fields, enduser])
|
|
1445
|
-
|
|
1446
1456
|
const autoAdvanceRef = useRef(false)
|
|
1447
1457
|
// don't make option, to avoid user passing invalid data, like an onclick event
|
|
1448
1458
|
const goToNextField = useCallback((answer: FormResponseValue['answer'] | undefined) => {
|
|
@@ -1450,24 +1460,10 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1450
1460
|
if (isNextDisabled() && currentValue?.answer.type !== 'Hidden Value') return
|
|
1451
1461
|
|
|
1452
1462
|
console.log('going to next field')
|
|
1453
|
-
|
|
1454
|
-
// If an answer is provided (e.g., from Hidden Value), update responses first
|
|
1455
|
-
const responsesWithAnswer = answer
|
|
1456
|
-
? responses.map(r =>
|
|
1457
|
-
r.fieldId === currentValue.fieldId
|
|
1458
|
-
? { ...r, answer }
|
|
1459
|
-
: r
|
|
1460
|
-
)
|
|
1461
|
-
: responses
|
|
1462
|
-
|
|
1463
|
-
// Apply templating to all responses including the newly updated answer
|
|
1464
|
-
const templatedResponses = applyTemplatingToResponses(responsesWithAnswer)
|
|
1465
|
-
setResponses(templatedResponses)
|
|
1466
|
-
|
|
1467
1463
|
if (currentValue.answer.type === 'Question Group') {
|
|
1468
1464
|
const responsesToSave = (
|
|
1469
1465
|
(currentValue.field.options?.subFields || [])
|
|
1470
|
-
.map(({ id }) =>
|
|
1466
|
+
.map(({ id }) => responses.find(f => f.fieldId === id)!)
|
|
1471
1467
|
.filter(f => f && f?.answer.type !== 'file' && f?.answer.type !== 'files')
|
|
1472
1468
|
)
|
|
1473
1469
|
if (responsesToSave.length) {
|
|
@@ -1478,7 +1474,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1478
1474
|
})
|
|
1479
1475
|
.catch(console.error)
|
|
1480
1476
|
}
|
|
1481
|
-
}
|
|
1477
|
+
}
|
|
1482
1478
|
else if (currentValue?.answer?.type !== 'file' && currentValue?.answer?.type !== 'files' && (formResponseId || accessCode)) {
|
|
1483
1479
|
session.api.form_responses.save_field_response({
|
|
1484
1480
|
accessCode,
|
|
@@ -1493,26 +1489,17 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1493
1489
|
|
|
1494
1490
|
try { window.scrollTo({ top: 0 }) } catch(err) {} // scroll to top if needed
|
|
1495
1491
|
setActiveField(activeField => {
|
|
1496
|
-
let newField = getNextField(activeField, currentValue,
|
|
1492
|
+
let newField = getNextField(activeField, currentValue, responses, logicOptions)
|
|
1497
1493
|
|
|
1498
1494
|
// when autoadvancing, prevent adding duplicates by checking whether already on stack
|
|
1499
1495
|
if (newField !== undefined && !prevFieldStackRef.current.find(v => v.value.id === activeField?.value.id)) {
|
|
1500
1496
|
prevFieldStackRef.current.push(activeField)
|
|
1501
1497
|
setCurrentPageIndex(i => i + 1)
|
|
1502
1498
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
// Apply templating to the active field by pulling from the templated responses
|
|
1507
|
-
// This ensures the UI displays templated titles/descriptions immediately
|
|
1508
|
-
const templatedResponse = templatedResponses.find(r => r.fieldId === fieldToReturn.value.id)
|
|
1509
|
-
if (templatedResponse) {
|
|
1510
|
-
fieldToReturn.value = templatedResponse.field
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
return fieldToReturn
|
|
1499
|
+
|
|
1500
|
+
return newField || activeField
|
|
1514
1501
|
})
|
|
1515
|
-
}, [prevFieldStackRef, currentValue, isNextDisabled, updateFormResponse, session, responses, logicOptions, accessCode, formResponseId, setActiveField, setCurrentPageIndex
|
|
1502
|
+
}, [prevFieldStackRef, currentValue, isNextDisabled, updateFormResponse, session, responses, logicOptions, accessCode, formResponseId, setActiveField, setCurrentPageIndex])
|
|
1516
1503
|
|
|
1517
1504
|
useEffect(() => {
|
|
1518
1505
|
if (dontAutoadvance) return
|
|
@@ -1636,12 +1623,12 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1636
1623
|
return {
|
|
1637
1624
|
enduserId,
|
|
1638
1625
|
formResponseId,
|
|
1639
|
-
activeField,
|
|
1626
|
+
activeField: templatedActiveField, // Use templated activeField for UI display
|
|
1640
1627
|
currentValue,
|
|
1641
1628
|
currentFileValue,
|
|
1642
1629
|
getResponsesWithQuestionGroupAnswers,
|
|
1643
1630
|
fields,
|
|
1644
|
-
responses,
|
|
1631
|
+
responses: templatedResponses, // Use templated responses - only display fields differ, answer values unchanged
|
|
1645
1632
|
selectedFiles,
|
|
1646
1633
|
onFieldChange,
|
|
1647
1634
|
onAddFile,
|
package/src/Forms/inputs.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { Autocomplete, Box, Button, Checkbox, Chip, Collapse, Divider, FormContr
|
|
|
4
4
|
import { FormInputProps } from "./types"
|
|
5
5
|
import { useDropzone } from "react-dropzone"
|
|
6
6
|
import { CANVAS_TITLE, EMOTII_TITLE, INSURANCE_RELATIONSHIPS, INSURANCE_RELATIONSHIPS_CANVAS, PRIMARY_HEX, RELATIONSHIP_TYPES, TELLESCOPE_GENDERS } from "@tellescope/constants"
|
|
7
|
-
import { MM_DD_YYYY_to_YYYY_MM_DD, capture_is_supported, downloadFile, emit_gtm_event, first_letter_capitalized, form_response_value_to_string, format_stripe_subscription_interval, getLocalTimezone, getPublicFileURL, mm_dd_yyyy, replace_enduser_template_values, truncate_string, update_local_storage, user_display_name } from "@tellescope/utilities"
|
|
7
|
+
import { MM_DD_YYYY_to_YYYY_MM_DD, capture_is_supported, downloadFile, emit_gtm_event, first_letter_capitalized, form_response_value_to_string, format_stripe_subscription_interval, getLocalTimezone, getPublicFileURL, mm_dd_yyyy, object_is_empty, replace_enduser_template_values, responses_satisfy_conditions, truncate_string, update_local_storage, user_display_name } from "@tellescope/utilities"
|
|
8
8
|
import { Address, DatabaseSelectResponse, Enduser, EnduserRelationship, FormResponseValue, InsuranceRelationship, MedicationResponse, MultipleChoiceOptions, FormFieldOptionDetails, TellescopeGender, TIMEZONES_USA } from "@tellescope/types-models"
|
|
9
9
|
import { VALID_STATES, emailValidator, phoneValidator } from "@tellescope/validation"
|
|
10
10
|
import Slider from '@mui/material/Slider';
|
|
@@ -1703,7 +1703,18 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
|
|
|
1703
1703
|
)
|
|
1704
1704
|
}
|
|
1705
1705
|
|
|
1706
|
-
|
|
1706
|
+
// Helper to emit GTM purchase event for Stripe payments (single source of truth)
|
|
1707
|
+
const emitStripePurchaseEvent = (field: FormField, cost: number) => {
|
|
1708
|
+
emit_gtm_event({
|
|
1709
|
+
event: 'form_purchase',
|
|
1710
|
+
productIds: field.options?.productIds || [],
|
|
1711
|
+
fieldId: field.id,
|
|
1712
|
+
value: cost / 100, // Convert cents to dollars
|
|
1713
|
+
currency: 'USD',
|
|
1714
|
+
})
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
export const StripeInput = ({ field, value, onChange, setCustomerId, enduserId, form, responses, enduser }: FormInputProps<'Stripe'> & {
|
|
1707
1718
|
setCustomerId: React.Dispatch<React.SetStateAction<string | undefined>>,
|
|
1708
1719
|
}) => {
|
|
1709
1720
|
const session = useResolvedSession()
|
|
@@ -1718,6 +1729,38 @@ export const StripeInput = ({ field, value, onChange, setCustomerId, enduserId }
|
|
|
1718
1729
|
const [availableProducts, setAvailableProducts] = useState<any[]>([])
|
|
1719
1730
|
const [loadingProducts, setLoadingProducts] = useState(false)
|
|
1720
1731
|
|
|
1732
|
+
// Compute visible products based on conditional logic
|
|
1733
|
+
const visibleProducts = useMemo(() => {
|
|
1734
|
+
if (!showProductSelection || availableProducts.length === 0) {
|
|
1735
|
+
return availableProducts
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
return availableProducts.filter(product => {
|
|
1739
|
+
// Find condition for this product
|
|
1740
|
+
const productCondition = field.options?.productConditions?.find(c => c.productId === product._id)
|
|
1741
|
+
|
|
1742
|
+
// If no condition defined, show by default
|
|
1743
|
+
if (!productCondition?.showCondition || object_is_empty(productCondition.showCondition)) {
|
|
1744
|
+
return true
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// Evaluate condition against current form responses
|
|
1748
|
+
return responses_satisfy_conditions(responses || [], productCondition.showCondition, {
|
|
1749
|
+
dateOfBirth: enduser?.dateOfBirth,
|
|
1750
|
+
gender: enduser?.gender,
|
|
1751
|
+
state: enduser?.state,
|
|
1752
|
+
form,
|
|
1753
|
+
activeResponses: responses,
|
|
1754
|
+
})
|
|
1755
|
+
})
|
|
1756
|
+
}, [availableProducts, field.options?.productConditions, responses, showProductSelection, enduser, form])
|
|
1757
|
+
|
|
1758
|
+
// Automatically deselect products that become hidden
|
|
1759
|
+
useEffect(() => {
|
|
1760
|
+
const visibleProductIds = visibleProducts.map(p => p._id)
|
|
1761
|
+
setSelectedProducts(prev => prev.filter(id => visibleProductIds.includes(id)))
|
|
1762
|
+
}, [visibleProducts])
|
|
1763
|
+
|
|
1721
1764
|
const fetchRef = useRef(false)
|
|
1722
1765
|
useEffect(() => {
|
|
1723
1766
|
if (fetchRef.current) return
|
|
@@ -1784,6 +1827,16 @@ export const StripeInput = ({ field, value, onChange, setCustomerId, enduserId }
|
|
|
1784
1827
|
: 0 // Will be calculated by existing Stripe flow when not in selection mode
|
|
1785
1828
|
)
|
|
1786
1829
|
|
|
1830
|
+
// Emit GTM purchase event once when success screen is displayed
|
|
1831
|
+
const purchaseEmittedRef = useRef(false)
|
|
1832
|
+
useEffect(() => {
|
|
1833
|
+
// Only emit for actual purchases (chargeImmediately), not for saving card details
|
|
1834
|
+
if (value && field.options?.chargeImmediately && !purchaseEmittedRef.current) {
|
|
1835
|
+
emitStripePurchaseEvent(field, cost)
|
|
1836
|
+
purchaseEmittedRef.current = true
|
|
1837
|
+
}
|
|
1838
|
+
}, [value, field, cost])
|
|
1839
|
+
|
|
1787
1840
|
// Handle product selection step
|
|
1788
1841
|
if (showProductSelection) {
|
|
1789
1842
|
if (error) {
|
|
@@ -1815,6 +1868,20 @@ export const StripeInput = ({ field, value, onChange, setCustomerId, enduserId }
|
|
|
1815
1868
|
</Grid>
|
|
1816
1869
|
)
|
|
1817
1870
|
}
|
|
1871
|
+
|
|
1872
|
+
// Check if all products are filtered out by conditional logic
|
|
1873
|
+
if (visibleProducts.length === 0) {
|
|
1874
|
+
return (
|
|
1875
|
+
<Grid container direction="column" spacing={2} alignItems="center">
|
|
1876
|
+
<Grid item>
|
|
1877
|
+
<Typography color="textSecondary">
|
|
1878
|
+
No products are available based on your previous answers.
|
|
1879
|
+
</Typography>
|
|
1880
|
+
</Grid>
|
|
1881
|
+
</Grid>
|
|
1882
|
+
)
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1818
1885
|
const isSingleSelection = field.options?.radio === true
|
|
1819
1886
|
|
|
1820
1887
|
const handleProductSelection = (productId: string) => {
|
|
@@ -1862,7 +1929,7 @@ export const StripeInput = ({ field, value, onChange, setCustomerId, enduserId }
|
|
|
1862
1929
|
<Typography variant="h6">Select Product{isSingleSelection ? '' : 's'}</Typography>
|
|
1863
1930
|
</Grid>
|
|
1864
1931
|
|
|
1865
|
-
{
|
|
1932
|
+
{visibleProducts.map((product) => {
|
|
1866
1933
|
// Use real-time Stripe pricing if available, fallback to Tellescope pricing
|
|
1867
1934
|
const price = product.currentPrice || product.cost
|
|
1868
1935
|
const priceAmount = price?.amount || 0
|
|
@@ -3126,7 +3193,7 @@ export const contact_is_valid = (e: Partial<Enduser>) => {
|
|
|
3126
3193
|
}
|
|
3127
3194
|
}
|
|
3128
3195
|
|
|
3129
|
-
export const RelatedContactsInput = ({ field, value: _value, onChange, ...props }: FormInputProps<'Related Contacts'>) => {
|
|
3196
|
+
export const RelatedContactsInput = ({ field, value: _value, onChange, error: parentError, ...props }: FormInputProps<'Related Contacts'>) => {
|
|
3130
3197
|
// safeguard against any rogue values like empty string
|
|
3131
3198
|
const value = Array.isArray(_value) ? _value : []
|
|
3132
3199
|
|
|
@@ -3201,7 +3268,7 @@ export const RelatedContactsInput = ({ field, value: _value, onChange, ...props
|
|
|
3201
3268
|
<Grid item xs={4}>
|
|
3202
3269
|
<TextField label="Phone Number" size="small" fullWidth
|
|
3203
3270
|
InputProps={defaultInputProps}
|
|
3204
|
-
value={phone} onChange={e => onChange(value.map((v, i) => i === editing ? { ...v, phone: e.target.value } : v), field.id)}
|
|
3271
|
+
value={phone} onChange={e => onChange(value.map((v, i) => i === editing ? { ...v, phone: e.target.value.trim() } : v), field.id)}
|
|
3205
3272
|
/>
|
|
3206
3273
|
</Grid>
|
|
3207
3274
|
}
|
|
@@ -3255,7 +3322,7 @@ export const RelatedContactsInput = ({ field, value: _value, onChange, ...props
|
|
|
3255
3322
|
}
|
|
3256
3323
|
|
|
3257
3324
|
<Grid item sx={{ my: 0.75 }}>
|
|
3258
|
-
<Button variant="outlined" onClick={() => setEditing(-1)} size="small">
|
|
3325
|
+
<Button variant="outlined" onClick={() => setEditing(-1)} size="small" disabled={!!errorMessage || !!parentError}>
|
|
3259
3326
|
Save Contact
|
|
3260
3327
|
</Button>
|
|
3261
3328
|
</Grid>
|