@tellescope/react-components 1.160.1 → 1.162.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.
Files changed (45) hide show
  1. package/lib/cjs/Forms/forms.d.ts +4 -2
  2. package/lib/cjs/Forms/forms.d.ts.map +1 -1
  3. package/lib/cjs/Forms/forms.js +11 -5
  4. package/lib/cjs/Forms/forms.js.map +1 -1
  5. package/lib/cjs/Forms/hooks.d.ts +6 -3
  6. package/lib/cjs/Forms/hooks.d.ts.map +1 -1
  7. package/lib/cjs/Forms/hooks.js +92 -77
  8. package/lib/cjs/Forms/hooks.js.map +1 -1
  9. package/lib/cjs/mui.d.ts +1 -0
  10. package/lib/cjs/mui.d.ts.map +1 -1
  11. package/lib/cjs/mui.js.map +1 -1
  12. package/lib/cjs/mui.native.js +1 -1
  13. package/lib/cjs/mui.native.js.map +1 -1
  14. package/lib/esm/CMS/components.d.ts +1 -0
  15. package/lib/esm/CMS/components.d.ts.map +1 -1
  16. package/lib/esm/Forms/forms.d.ts +7 -5
  17. package/lib/esm/Forms/forms.d.ts.map +1 -1
  18. package/lib/esm/Forms/forms.js +12 -6
  19. package/lib/esm/Forms/forms.js.map +1 -1
  20. package/lib/esm/Forms/hooks.d.ts +6 -3
  21. package/lib/esm/Forms/hooks.d.ts.map +1 -1
  22. package/lib/esm/Forms/hooks.js +93 -78
  23. package/lib/esm/Forms/hooks.js.map +1 -1
  24. package/lib/esm/Forms/inputs.d.ts +1 -1
  25. package/lib/esm/Forms/inputs.native.d.ts +1 -0
  26. package/lib/esm/Forms/inputs.native.d.ts.map +1 -1
  27. package/lib/esm/controls.d.ts +2 -2
  28. package/lib/esm/inputs.d.ts +1 -1
  29. package/lib/esm/inputs.native.d.ts +1 -0
  30. package/lib/esm/inputs.native.d.ts.map +1 -1
  31. package/lib/esm/layout.d.ts +1 -1
  32. package/lib/esm/mui.d.ts +1 -0
  33. package/lib/esm/mui.d.ts.map +1 -1
  34. package/lib/esm/mui.js.map +1 -1
  35. package/lib/esm/mui.native.js +1 -1
  36. package/lib/esm/mui.native.js.map +1 -1
  37. package/lib/esm/state.d.ts +256 -256
  38. package/lib/esm/theme.native.d.ts +1 -0
  39. package/lib/esm/theme.native.d.ts.map +1 -1
  40. package/lib/tsconfig.tsbuildinfo +1 -1
  41. package/package.json +9 -9
  42. package/src/Forms/forms.tsx +25 -3
  43. package/src/Forms/hooks.tsx +49 -24
  44. package/src/mui.native.tsx +1 -1
  45. package/src/mui.tsx +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellescope/react-components",
3
- "version": "1.160.1",
3
+ "version": "1.162.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.160.1",
51
- "@tellescope/sdk": "^1.160.1",
52
- "@tellescope/types-client": "^1.160.1",
53
- "@tellescope/types-models": "^1.160.1",
54
- "@tellescope/types-utilities": "^1.160.1",
55
- "@tellescope/utilities": "^1.160.1",
56
- "@tellescope/validation": "^1.160.1",
50
+ "@tellescope/constants": "^1.162.0",
51
+ "@tellescope/sdk": "^1.162.0",
52
+ "@tellescope/types-client": "^1.162.0",
53
+ "@tellescope/types-models": "^1.162.0",
54
+ "@tellescope/types-utilities": "^1.162.0",
55
+ "@tellescope/utilities": "^1.162.0",
56
+ "@tellescope/validation": "^1.162.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",
@@ -80,7 +80,7 @@
80
80
  "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
81
81
  "react-native": "^0.65.0 || ^0.66.0 || ^0.67.0 || ^0.68.0 || ^0.71.0"
82
82
  },
83
- "gitHead": "28dfaf3ce3542b7e879639ddc9264611fece8c62",
83
+ "gitHead": "b58746104f8a4bf539bb7de5f0506e08c710adb4",
84
84
  "publishConfig": {
85
85
  "access": "public"
86
86
  }
@@ -1,12 +1,12 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
2
- import { Button, CircularProgress, Flex, LoadingButton, Modal, Paper, Styled, Typography, form_display_text_for_language, useFileUpload, useFormResponses, useSession } from "../index"
3
- import { useListForFormFields, useOrganizationTheme, useTellescopeForm, WithOrganizationTheme, Response, FileResponse } from "./hooks"
2
+ import { Button, CircularProgress, Flex, LinearProgress, LoadingButton, Modal, Paper, Styled, Typography, form_display_text_for_language, useFileUpload, useFormResponses, useSession } from "../index"
3
+ import { useListForFormFields, useOrganizationTheme, useTellescopeForm, WithOrganizationTheme, Response, FileResponse, NextFieldLogicOptions } from "./hooks"
4
4
  import { ChangeHandler, FormInputs } from "./types"
5
5
  import { AddressInput, AllergiesInput, AppointmentBookingInput, ConditionsInput, DatabaseSelectInput, DateInput, DateStringInput, DropdownInput, EmailInput, EmotiiInput, FileInput, FilesInput, HeightInput, HiddenValueInput, InsuranceInput, LanguageSelect, MedicationsInput, MultipleChoiceInput, NumberInput, PhoneInput, Progress, RankingInput, RatingInput, RedirectInput, RelatedContactsInput, SignatureInput, StringInput, StringLongInput, StripeInput, TableInput, TimeInput, defaultButtonStyles } from "./inputs"
6
6
  import { PRIMARY_HEX } from "@tellescope/constants"
7
7
  import { FormResponse, FormField, Form, Enduser } from "@tellescope/types-client"
8
8
  import { FormResponseAnswerFileValue, OrganizationTheme } from "@tellescope/types-models"
9
- import { field_can_autosubmit, formatted_date, objects_equivalent, remove_script_tags, truncate_string } from "@tellescope/utilities"
9
+ import { field_can_autosubmit, formatted_date, object_is_empty, objects_equivalent, read_local_storage, remove_script_tags, responses_satisfy_conditions, truncate_string } from "@tellescope/utilities"
10
10
  import { Divider } from "@mui/material"
11
11
 
12
12
  export const TellescopeFormContainer = ({ businessId, organizationIds, ...props } : {
@@ -133,6 +133,8 @@ export const QuestionForField = ({
133
133
  spacing,
134
134
  isSinglePage,
135
135
  rootResponseId,
136
+ isInQuestionGroup,
137
+ logicOptions,
136
138
  } : {
137
139
  spacing?: number,
138
140
  form?: Form,
@@ -143,6 +145,8 @@ export const QuestionForField = ({
143
145
  field: FormField,
144
146
  setCustomerId: React.Dispatch<React.SetStateAction<string | undefined>>,
145
147
  isSinglePage?: boolean,
148
+ isInQuestionGroup?: boolean,
149
+ logicOptions?: NextFieldLogicOptions,
146
150
  } & Pick<TellescopeFormProps, "rootResponseId" | "goToNextField" | "groupId" | "groupInstance" | "submit" | "formResponseId" | 'enduserId' | 'isPreviousDisabled' | 'goToPreviousField' | 'enduser' | 'handleDatabaseSelect' | 'onAddFile' | 'onFieldChange' | 'fields' | 'customInputs' | 'responses' | 'selectedFiles' | 'validateField'>) => {
147
151
  const String = customInputs?.['string'] ?? StringInput
148
152
  const StringLong = customInputs?.['stringLong'] ?? StringLongInput
@@ -184,6 +188,13 @@ export const QuestionForField = ({
184
188
  ), [field.feedback, value])
185
189
 
186
190
  if (!value) return null
191
+ if (
192
+ isInQuestionGroup
193
+ && field.groupShowCondition && !object_is_empty(field.groupShowCondition)
194
+ && !responses_satisfy_conditions(responses, field.groupShowCondition, logicOptions)
195
+ ) {
196
+ return null
197
+ }
187
198
  return (
188
199
  // margin leaves room for error message in Question Group
189
200
  <Flex column flex={1} style={{ marginBottom: spacing ?? 25 }} id={field.id}>
@@ -345,6 +356,8 @@ export const QuestionForField = ({
345
356
  responses={responses} selectedFiles={selectedFiles}
346
357
  validateField={validateField} enduserId={enduserId}
347
358
  spacing={field.options?.groupPadding}
359
+ logicOptions={logicOptions}
360
+ isInQuestionGroup
348
361
  />
349
362
  </Flex>
350
363
  )
@@ -429,6 +442,7 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
429
442
  formResponseId,
430
443
  groupId,
431
444
  groupInstance,
445
+ logicOptions,
432
446
  }) => {
433
447
  const beforeunloadHandler = React.useCallback((e: BeforeUnloadEvent) => {
434
448
  try {
@@ -526,6 +540,7 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
526
540
  responses={responses} selectedFiles={selectedFiles}
527
541
  validateField={validateField}
528
542
  groupId={groupId} groupInstance={groupInstance}
543
+ logicOptions={logicOptions}
529
544
  />
530
545
  </Flex>
531
546
  </Flex>
@@ -623,6 +638,13 @@ export const ThanksMessage = ({
623
638
  <Typography style={{ marginTop: 25, alignSelf: 'center' }}>{thanksMessage || DEFAULT_THANKS_MESSAGE}</Typography>
624
639
  )
625
640
  }
641
+ {read_local_storage('redirecting_public_group') === 'true' &&
642
+ <>
643
+ <Typography style={{ marginTop: 25, alignSelf: 'center' }}>
644
+ Redirecting to next form... <CircularProgress size={20} color="primary" />
645
+ </Typography>
646
+ </>
647
+ }
626
648
  {downloadComponent &&
627
649
  <Flex justifyContent="center" style={{ marginTop: 15, marginBottom: 15 }}>
628
650
  {downloadComponent}
@@ -9,7 +9,7 @@ import { WithTheme, contact_is_valid, useFileUpload, useFormFields, useFormRespo
9
9
  import ReactGA from "react-ga4";
10
10
 
11
11
  import isEmail from "validator/lib/isEmail"
12
- import { append_current_utm_params, field_can_autoadvance, getLocalTimezone, get_time_values, get_utm_params, is_object, object_is_empty, responses_satisfy_conditions } from "@tellescope/utilities"
12
+ import { append_current_utm_params, field_can_autoadvance, getLocalTimezone, get_time_values, get_utm_params, is_object, object_is_empty, responses_satisfy_conditions, update_local_storage } from "@tellescope/utilities"
13
13
 
14
14
  export const useFlattenedTree = (root?: FormFieldNode) => {
15
15
  const flat: FormField[] = []
@@ -194,14 +194,16 @@ export const useTreeForFormFields = (_fields: FormField[]) => {
194
194
  return nodesForId[fields.find(s => s.previousFields.find(p => p.type === 'root'))?.id ?? '']
195
195
  }
196
196
 
197
- export const getNextField = (activeField: FormFieldNode, currentValue: Response, responses: FormResponseValue[], options?: {
197
+ export type NextFieldLogicOptions = {
198
198
  urlLogicValue?: string,
199
199
  dateOfBirth?: string,
200
200
  gender?: string,
201
201
  state?: string,
202
202
  form?: Form,
203
203
  activeResponses?: FormResponseValue[], // current and previous answers (not future answers)
204
- }) => {
204
+ }
205
+
206
+ export const getNextField = (activeField: FormFieldNode, currentValue: Response, responses: FormResponseValue[], options?: NextFieldLogicOptions) => {
205
207
  if (activeField.children.length === 0) {
206
208
  return
207
209
  }
@@ -360,6 +362,7 @@ interface UseTellescopeFormOptions {
360
362
  context?: string,
361
363
  urlLogicValue?: string,
362
364
  enduser?: Partial<Enduser>,
365
+ isPublicForm?: boolean,
363
366
  }
364
367
 
365
368
  const OrganizationThemeContext = createContext(null as any as {
@@ -508,7 +511,7 @@ const shouldCallout = (field: FormField | undefined, value: FormResponseValueAns
508
511
 
509
512
  export type Response = FormResponseValue & { touched: boolean, includeInSubmit: boolean, field: FormField }
510
513
  export type FileResponse = { fieldId: string, fieldTitle: string, blobs?: FileBlob[] }
511
- export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlanId, calendarEventId, context, ga4measurementId, rootResponseId, parentResponseId, accessCode, existingResponses, automationStepId, enduserId, formResponseId, fields, isInternalNote, formTitle, submitRedirectURL,enduser }: UseTellescopeFormOptions) => {
514
+ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customization, carePlanId, calendarEventId, context, ga4measurementId, rootResponseId, parentResponseId, accessCode, existingResponses, automationStepId, enduserId, formResponseId, fields, isInternalNote, formTitle, submitRedirectURL,enduser }: UseTellescopeFormOptions) => {
512
515
  const { amPm, hoursAmPm, minutes } = get_time_values(new Date())
513
516
 
514
517
  const root = useTreeForFormFields(fields)
@@ -675,6 +678,15 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
675
678
  selectedFiles.find(f => f.fieldId === activeField.value.id)
676
679
  )
677
680
 
681
+ const logicOptions: NextFieldLogicOptions = {
682
+ urlLogicValue,
683
+ activeResponses: responses.filter(r => r.includeInSubmit),
684
+ dateOfBirth: enduser?.dateOfBirth,
685
+ gender: enduser?.gender,
686
+ state: enduser?.state,
687
+ form,
688
+ }
689
+
678
690
  const handleDatabaseSelect = useCallback((databaseRecords: Pick<DatabaseRecord, "values" | "databaseId">[]) => {
679
691
  try {
680
692
  // no need to update if there's no prepopulation
@@ -794,6 +806,15 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
794
806
  if (!value) return "Value not provided"
795
807
  if (!file) return "File not provided"
796
808
 
809
+ // if question is in group, and question is hidden in group, don't validate
810
+ if (
811
+ field.isInGroup
812
+ && field.groupShowCondition && !object_is_empty(field.groupShowCondition)
813
+ && !responses_satisfy_conditions(responses, field.groupShowCondition, logicOptions)
814
+ ) {
815
+ return null
816
+ }
817
+
797
818
  if (value.answer.type === 'Insurance') {
798
819
  if (value.answer.value?.relationshipDetails?.dateOfBirth && !isDateString(value.answer.value.relationshipDetails.dateOfBirth)) {
799
820
  return "Enter date of birth in MM-DD-YYYY format"
@@ -1014,7 +1035,7 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
1014
1035
  }
1015
1036
 
1016
1037
  return null
1017
- }, [responses, selectedFiles, currentValue, activeField, repeats, sessionType])
1038
+ }, [responses, selectedFiles, currentValue, activeField, repeats, sessionType, logicOptions])
1018
1039
 
1019
1040
  // nested Question Group fields are disabled, so it's safe to avoid recursion multiple times
1020
1041
  const validateField = useCallback((field: FormField) => {
@@ -1058,14 +1079,7 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
1058
1079
  const nextField = (
1059
1080
  !currentValue ? undefined
1060
1081
  : (
1061
- getNextField(activeField, currentValue, responses, {
1062
- urlLogicValue,
1063
- form,
1064
- activeResponses: responses.filter(r => r.includeInSubmit),
1065
- dateOfBirth: enduser?.dateOfBirth,
1066
- gender: enduser?.gender,
1067
- state: enduser?.state,
1068
- })
1082
+ getNextField(activeField, currentValue, responses, logicOptions)
1069
1083
  )
1070
1084
  )
1071
1085
 
@@ -1155,6 +1169,12 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
1155
1169
  const match = responses.find(r => r.fieldId === f?.id)
1156
1170
  if (!match || responsesToSubmit.find(r => r.fieldId === match.fieldId)) continue
1157
1171
 
1172
+ // hidden in group by conditional logic
1173
+ if ((
1174
+ match.field.groupShowCondition && !object_is_empty(match.field.groupShowCondition)
1175
+ && !responses_satisfy_conditions(responses, match.field.groupShowCondition, logicOptions)
1176
+ )) continue
1177
+
1158
1178
  responsesToSubmit.push(match)
1159
1179
  }
1160
1180
  }
@@ -1162,7 +1182,8 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
1162
1182
  const errors: { enduserId: string, message: string }[] = []
1163
1183
  for (const eId of [enduserId, ...(options?.otherEnduserIds ?? [])]) {
1164
1184
  try {
1165
- const { formResponse } = await session.api.form_responses.submit_form_response({
1185
+ update_local_storage('redirecting_public_group', '')
1186
+ const { formResponse, nextFormGroupPublicURL } = await session.api.form_responses.submit_form_response({
1166
1187
  accessCode : (
1167
1188
  accessCode
1168
1189
  || (await (
@@ -1186,6 +1207,12 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
1186
1207
  productIds: responsesToSubmit.flatMap(r => r.field?.options?.productIds ?? []),
1187
1208
  utm: get_utm_params(),
1188
1209
  })
1210
+
1211
+ // do actual redirect later to prevent popup
1212
+ if (isPublicForm && nextFormGroupPublicURL) {
1213
+ update_local_storage('redirecting_public_group', 'true')
1214
+ }
1215
+
1189
1216
  if (!options?.otherEnduserIds?.length) { // only track for signle submission
1190
1217
  if (ga4measurementId) {
1191
1218
  ReactGA.event({
@@ -1206,6 +1233,10 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
1206
1233
  options?.onSuccess?.(formResponse)
1207
1234
  }
1208
1235
  }
1236
+
1237
+ if (isPublicForm && nextFormGroupPublicURL) {
1238
+ window.location.href = nextFormGroupPublicURL
1239
+ }
1209
1240
  } catch(err: any) {
1210
1241
  if (options?.onBulkErrors) { // only track for signle submission
1211
1242
  errors.push({ enduserId: eId, message: err?.message || err?.toString() })
@@ -1229,7 +1260,7 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
1229
1260
  } finally {
1230
1261
  setSubmittingStatus(undefined)
1231
1262
  }
1232
- }, [accessCode, automationStepId, enduserId, responses, selectedFiles, session, handleUpload, existingResponses, ga4measurementId, rootResponseId, parentResponseId, calendarEventId, goBackURL])
1263
+ }, [accessCode, automationStepId, enduserId, responses, selectedFiles, session, handleUpload, existingResponses, ga4measurementId, rootResponseId, parentResponseId, calendarEventId, goBackURL, logicOptions])
1233
1264
 
1234
1265
  const isNextDisabled = useCallback(() => {
1235
1266
  if (activeField.children.length === 0) {
@@ -1274,14 +1305,7 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
1274
1305
 
1275
1306
  try { window.scrollTo({ top: 0 }) } catch(err) {} // scroll to top if needed
1276
1307
  setActiveField(activeField => {
1277
- let newField = getNextField(activeField, currentValue, responses, {
1278
- urlLogicValue,
1279
- form,
1280
- activeResponses: responses.filter(r => r.includeInSubmit),
1281
- dateOfBirth: enduser?.dateOfBirth,
1282
- gender: enduser?.gender,
1283
- state: enduser?.state,
1284
- })
1308
+ let newField = getNextField(activeField, currentValue, responses, logicOptions)
1285
1309
 
1286
1310
  // when autoadvancing, prevent adding duplicates by checking whether already on stack
1287
1311
  if (newField !== undefined && !prevFieldStackRef.current.find(v => v.value.id === activeField?.value.id)) {
@@ -1291,7 +1315,7 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
1291
1315
 
1292
1316
  return newField || activeField
1293
1317
  })
1294
- }, [prevFieldStackRef, currentValue, isNextDisabled, updateFormResponse, session, responses, urlLogicValue, form, enduser?.dateOfBirth])
1318
+ }, [prevFieldStackRef, currentValue, isNextDisabled, updateFormResponse, session, responses, logicOptions])
1295
1319
 
1296
1320
  useEffect(() => {
1297
1321
  if (!autoAdvanceRef.current) return
@@ -1420,5 +1444,6 @@ export const useTellescopeForm = ({ form, urlLogicValue, customization, carePlan
1420
1444
  setCustomerId,
1421
1445
  customization,
1422
1446
  handleDatabaseSelect,
1447
+ logicOptions,
1423
1448
  }
1424
1449
  }
@@ -189,7 +189,7 @@ export const Typography = ({ children, onClick, onPress, color, style, ...props
189
189
  const colorStyle = { color: color ? useTheme().colors[color] : undefined }
190
190
 
191
191
  return (
192
- <MuiText onPress={onPress ?? onClick} style={{ ...colorStyle, ...convert_CSS_to_RNStyles(style) }}>
192
+ <MuiText {...props} onPress={onPress ?? onClick} style={{ ...colorStyle, ...convert_CSS_to_RNStyles(style) }}>
193
193
  {children}
194
194
  </MuiText>
195
195
  )
package/src/mui.tsx CHANGED
@@ -263,6 +263,7 @@ export type TypographyProps = {
263
263
  component?: 'span' | 'p' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5',
264
264
  noWrap?: boolean,
265
265
  title?: string,
266
+ selectable?: boolean,
266
267
  } & Clickable
267
268
  export const Typography = ({ children, onClick, onPress, component='span', ...props}: TypographyProps & ClickableWeb) => {
268
269
  return <MuiTypography onClick={onClick ?? onPress} component={component} {...props}>{children}</MuiTypography>