@tellescope/react-components 1.168.0 → 1.170.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 (76) hide show
  1. package/lib/cjs/Forms/form_responses.js +1 -1
  2. package/lib/cjs/Forms/form_responses.js.map +1 -1
  3. package/lib/cjs/Forms/forms.d.ts +9 -2
  4. package/lib/cjs/Forms/forms.d.ts.map +1 -1
  5. package/lib/cjs/Forms/forms.js +50 -42
  6. package/lib/cjs/Forms/forms.js.map +1 -1
  7. package/lib/cjs/Forms/hooks.d.ts +7 -0
  8. package/lib/cjs/Forms/hooks.d.ts.map +1 -1
  9. package/lib/cjs/Forms/hooks.js +96 -68
  10. package/lib/cjs/Forms/hooks.js.map +1 -1
  11. package/lib/cjs/Forms/inputs.d.ts +3 -2
  12. package/lib/cjs/Forms/inputs.d.ts.map +1 -1
  13. package/lib/cjs/Forms/inputs.js +80 -25
  14. package/lib/cjs/Forms/inputs.js.map +1 -1
  15. package/lib/cjs/Forms/types.d.ts +7 -0
  16. package/lib/cjs/Forms/types.d.ts.map +1 -1
  17. package/lib/cjs/Forms/wysiwyg.d.ts +12 -0
  18. package/lib/cjs/Forms/wysiwyg.d.ts.map +1 -0
  19. package/lib/cjs/Forms/wysiwyg.js +225 -0
  20. package/lib/cjs/Forms/wysiwyg.js.map +1 -0
  21. package/lib/cjs/inputs.d.ts +1 -1
  22. package/lib/cjs/inputs.d.ts.map +1 -1
  23. package/lib/cjs/state.d.ts.map +1 -1
  24. package/lib/cjs/state.js +4 -0
  25. package/lib/cjs/state.js.map +1 -1
  26. package/lib/cjs/table.js +1 -1
  27. package/lib/cjs/table.js.map +1 -1
  28. package/lib/esm/CMS/components.d.ts +0 -1
  29. package/lib/esm/CMS/components.d.ts.map +1 -1
  30. package/lib/esm/Forms/form_responses.js +1 -1
  31. package/lib/esm/Forms/form_responses.js.map +1 -1
  32. package/lib/esm/Forms/forms.d.ts +12 -5
  33. package/lib/esm/Forms/forms.d.ts.map +1 -1
  34. package/lib/esm/Forms/forms.js +51 -43
  35. package/lib/esm/Forms/forms.js.map +1 -1
  36. package/lib/esm/Forms/hooks.d.ts +7 -0
  37. package/lib/esm/Forms/hooks.d.ts.map +1 -1
  38. package/lib/esm/Forms/hooks.js +96 -68
  39. package/lib/esm/Forms/hooks.js.map +1 -1
  40. package/lib/esm/Forms/inputs.d.ts +4 -3
  41. package/lib/esm/Forms/inputs.d.ts.map +1 -1
  42. package/lib/esm/Forms/inputs.js +78 -24
  43. package/lib/esm/Forms/inputs.js.map +1 -1
  44. package/lib/esm/Forms/inputs.native.d.ts +0 -1
  45. package/lib/esm/Forms/inputs.native.d.ts.map +1 -1
  46. package/lib/esm/Forms/types.d.ts +7 -0
  47. package/lib/esm/Forms/types.d.ts.map +1 -1
  48. package/lib/esm/Forms/wysiwyg.d.ts +12 -0
  49. package/lib/esm/Forms/wysiwyg.d.ts.map +1 -0
  50. package/lib/esm/Forms/wysiwyg.js +218 -0
  51. package/lib/esm/Forms/wysiwyg.js.map +1 -0
  52. package/lib/esm/controls.d.ts +2 -2
  53. package/lib/esm/inputs.d.ts +2 -2
  54. package/lib/esm/inputs.d.ts.map +1 -1
  55. package/lib/esm/inputs.native.d.ts +0 -1
  56. package/lib/esm/inputs.native.d.ts.map +1 -1
  57. package/lib/esm/layout.d.ts +1 -1
  58. package/lib/esm/state.d.ts +264 -264
  59. package/lib/esm/state.d.ts.map +1 -1
  60. package/lib/esm/state.js +4 -0
  61. package/lib/esm/state.js.map +1 -1
  62. package/lib/esm/table.js +1 -1
  63. package/lib/esm/table.js.map +1 -1
  64. package/lib/esm/theme.native.d.ts +0 -1
  65. package/lib/esm/theme.native.d.ts.map +1 -1
  66. package/lib/tsconfig.tsbuildinfo +1 -1
  67. package/package.json +13 -9
  68. package/src/Forms/form_responses.tsx +1 -1
  69. package/src/Forms/forms.tsx +20 -2
  70. package/src/Forms/hooks.tsx +43 -26
  71. package/src/Forms/inputs.tsx +41 -9
  72. package/src/Forms/types.ts +4 -0
  73. package/src/Forms/wysiwyg.tsx +234 -0
  74. package/src/inputs.tsx +1 -1
  75. package/src/state.tsx +1 -0
  76. package/src/table.tsx +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellescope/react-components",
3
- "version": "1.168.0",
3
+ "version": "1.170.0",
4
4
  "description": "",
5
5
  "main": "./lib/cjs/index.js",
6
6
  "module": "./lib/esm/index.js",
@@ -47,23 +47,27 @@
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.168.0",
51
- "@tellescope/sdk": "^1.168.0",
52
- "@tellescope/types-client": "^1.168.0",
53
- "@tellescope/types-models": "^1.168.0",
54
- "@tellescope/types-utilities": "^1.168.0",
55
- "@tellescope/utilities": "^1.168.0",
56
- "@tellescope/validation": "^1.168.0",
50
+ "@tellescope/constants": "^1.170.0",
51
+ "@tellescope/sdk": "^1.170.0",
52
+ "@tellescope/types-client": "^1.170.0",
53
+ "@tellescope/types-models": "^1.170.0",
54
+ "@tellescope/types-utilities": "^1.170.0",
55
+ "@tellescope/utilities": "^1.170.0",
56
+ "@tellescope/validation": "^1.170.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",
60
+ "draft-js": "^0.11.7",
61
+ "draftjs-to-html": "^0.9.1",
60
62
  "eslint": "^7.32.0",
61
63
  "eslint-plugin-react": "^7.26.1",
62
64
  "formik": "^2.2.9",
63
65
  "heic2any": "^0.0.4",
66
+ "html-to-draftjs": "^1.5.0",
64
67
  "nodemon": "^2.0.13",
65
68
  "react-beautiful-dnd": "^13.1.1",
66
69
  "react-datepicker": "^3.4.1",
70
+ "react-draft-wysiwyg": "^1.15.0",
67
71
  "react-draggable": "^4.4.6",
68
72
  "react-dropzone": "^11.4.2",
69
73
  "react-ga4": "^1.4.1",
@@ -80,7 +84,7 @@
80
84
  "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
81
85
  "react-native": "^0.65.0 || ^0.66.0 || ^0.67.0 || ^0.68.0 || ^0.71.0"
82
86
  },
83
- "gitHead": "2a92eab06c67bafc1445136c8e81dab4a94d260e",
87
+ "gitHead": "d9bbecc704e9ecd06d59ffbdaa155879a109c925",
84
88
  "publishConfig": {
85
89
  "access": "public"
86
90
  }
@@ -31,7 +31,7 @@ export const ResponseAnswer = ({ formResponse, fieldId, isHTML, answer: a, print
31
31
  onImageClick?: (args: { src: string }) => void,
32
32
  isHTML?: boolean,
33
33
  }) => (
34
- (isHTML && typeof a.value === 'string')
34
+ ((isHTML || a.type === 'Rich Text') && typeof a.value === 'string')
35
35
  ? <div dangerouslySetInnerHTML={{ __html: remove_script_tags(a.value) }} />
36
36
  : a.value
37
37
  ? (
@@ -1,8 +1,8 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
2
- import { Button, CircularProgress, Flex, LinearProgress, LoadingButton, Modal, Paper, Styled, Typography, form_display_text_for_language, useFileUpload, useFormResponses, useSession } from "../index"
2
+ import { Button, CircularProgress, FileBlob, FileUploadHandler, Flex, LinearProgress, LoadingButton, Modal, Paper, Styled, Typography, form_display_text_for_language, useFileUpload, useFormResponses, useSession } from "../index"
3
3
  import { useListForFormFields, useOrganizationTheme, useTellescopeForm, WithOrganizationTheme, Response, FileResponse, NextFieldLogicOptions } from "./hooks"
4
4
  import { ChangeHandler, FormInputs } from "./types"
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"
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, RichTextInput, 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"
@@ -135,6 +135,7 @@ export const QuestionForField = ({
135
135
  rootResponseId,
136
136
  isInQuestionGroup,
137
137
  logicOptions,
138
+ uploadingFiles, setUploadingFiles, handleFileUpload,
138
139
  } : {
139
140
  spacing?: number,
140
141
  form?: Form,
@@ -147,6 +148,9 @@ export const QuestionForField = ({
147
148
  isSinglePage?: boolean,
148
149
  isInQuestionGroup?: boolean,
149
150
  logicOptions?: NextFieldLogicOptions,
151
+ handleFileUpload: (blob: FileBlob, fieldId: string) => Promise<any>,
152
+ uploadingFiles: { fieldId: string }[]
153
+ setUploadingFiles: React.Dispatch<React.SetStateAction<{ fieldId: string }[]>>,
150
154
  } & Pick<TellescopeFormProps, "rootResponseId" | "goToNextField" | "groupId" | "groupInstance" | "submit" | "formResponseId" | 'enduserId' | 'isPreviousDisabled' | 'goToPreviousField' | 'enduser' | 'handleDatabaseSelect' | 'onAddFile' | 'onFieldChange' | 'fields' | 'customInputs' | 'responses' | 'selectedFiles' | 'validateField'>) => {
151
155
  const String = customInputs?.['string'] ?? StringInput
152
156
  const StringLong = customInputs?.['stringLong'] ?? StringLongInput
@@ -175,6 +179,7 @@ export const QuestionForField = ({
175
179
  const Emotii = customInputs?.['Emotii'] ?? EmotiiInput
176
180
  const Allergies = customInputs?.['Allergies'] ?? AllergiesInput
177
181
  const Conditions = customInputs?.['Conditions'] ?? ConditionsInput
182
+ const RichText = customInputs?.['Rich Text'] ?? RichTextInput
178
183
 
179
184
  const validationMessage = validateField(field)
180
185
 
@@ -233,6 +238,7 @@ export const QuestionForField = ({
233
238
  ? value.answer.value?.name
234
239
  : ''
235
240
  }
241
+ handleFileUpload={handleFileUpload} uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
236
242
  />
237
243
  )
238
244
  : field.type === 'files' ? (
@@ -242,6 +248,7 @@ export const QuestionForField = ({
242
248
  // ? value.answer.value?.name
243
249
  // : ''
244
250
  // }
251
+ handleFileUpload={handleFileUpload} uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
245
252
  />
246
253
  )
247
254
  : field.type === 'dateString' ? (
@@ -283,6 +290,9 @@ export const QuestionForField = ({
283
290
  : field.type === 'stringLong' ? (
284
291
  <StringLong field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'string' | 'stringLong'>} form={form} />
285
292
  )
293
+ : field.type === 'Rich Text' ? (
294
+ <RichText field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Rich Text'>} form={form} />
295
+ )
286
296
  : field.type === 'email' ? (
287
297
  <Email field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'email'>} form={form} />
288
298
  )
@@ -358,6 +368,8 @@ export const QuestionForField = ({
358
368
  spacing={field.options?.groupPadding}
359
369
  logicOptions={logicOptions}
360
370
  isInQuestionGroup
371
+ uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
372
+ handleFileUpload={handleFileUpload}
361
373
  />
362
374
  </Flex>
363
375
  )
@@ -443,6 +455,7 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
443
455
  groupId,
444
456
  groupInstance,
445
457
  logicOptions,
458
+ uploadingFiles, setUploadingFiles, handleFileUpload,
446
459
  }) => {
447
460
  const beforeunloadHandler = React.useCallback((e: BeforeUnloadEvent) => {
448
461
  try {
@@ -541,6 +554,8 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
541
554
  validateField={validateField}
542
555
  groupId={groupId} groupInstance={groupInstance}
543
556
  logicOptions={logicOptions}
557
+ uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
558
+ handleFileUpload={handleFileUpload}
544
559
  />
545
560
  </Flex>
546
561
  </Flex>
@@ -942,6 +957,7 @@ export const TellescopeSinglePageForm: React.JSXElementConstructor<TellescopeFor
942
957
  enduser,
943
958
  groupId,
944
959
  groupInstance,
960
+ uploadingFiles, setUploadingFiles, handleFileUpload,
945
961
  ...props
946
962
  }) => {
947
963
  const list = useListForFormFields(fields, responses, { form: props.form, gender: enduser?.gender })
@@ -1019,6 +1035,8 @@ export const TellescopeSinglePageForm: React.JSXElementConstructor<TellescopeFor
1019
1035
  responses={responses} selectedFiles={selectedFiles}
1020
1036
  validateField={validateField}
1021
1037
  groupId={groupId} groupInstance={groupInstance}
1038
+ uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
1039
+ handleFileUpload={handleFileUpload}
1022
1040
  />
1023
1041
  </Flex>
1024
1042
  </Flex>
@@ -531,6 +531,7 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
531
531
  const [submittingStatus, setSubmittingStatus] = useState<SubmitStatus>(undefined)
532
532
  const [submitErrorMessage, setSubmitErrorMessage] = useState('')
533
533
  const [currentPageIndex, setCurrentPageIndex] = useState(0)
534
+ const [uploadingFiles, setUploadingFiles] = useState<{ fieldId: string }[]>([])
534
535
  const prevFieldStackRef = useRef<typeof root[]>([])
535
536
 
536
537
  const [repeats, setRepeats] = useState({} as Record<string, string | number>)
@@ -815,6 +816,10 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
815
816
  return null
816
817
  }
817
818
 
819
+ if (value.answer?.type === 'Rich Text' && value.answer?.value?.trim() === '<p></p>' && !field.isOptional) {
820
+ return "Answer is required"
821
+ }
822
+
818
823
  if (value.answer.type === 'Insurance') {
819
824
  if (value.answer.value?.relationshipDetails?.dateOfBirth && !isDateString(value.answer.value.relationshipDetails.dateOfBirth)) {
820
825
  return "Enter date of birth in MM-DD-YYYY format"
@@ -1102,6 +1107,31 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
1102
1107
  return responsesToSubmit
1103
1108
  }, [responses])
1104
1109
 
1110
+ const handleFileUpload = useCallback(async (blob: FileBlob, fieldId: string) => {
1111
+ const responseIndex = responses.findIndex(f => f.fieldId === fieldId)
1112
+ const result: FormResponseAnswerFileValue = { name: blob.name, secureName: '' }
1113
+ const { secureName } = await handleUpload(
1114
+ {
1115
+ name: blob.name,
1116
+ size: blob.size,
1117
+ type: blob.type,
1118
+ enduserId,
1119
+ },
1120
+ blob
1121
+ )
1122
+
1123
+ if (responses[responseIndex].answer.type === 'files') {
1124
+ if (!responses[responseIndex].answer.value) {
1125
+ responses[responseIndex].answer.value = []
1126
+ }
1127
+ (responses[responseIndex].answer.value as any[]).push({
1128
+ ...result, type: blob.type, secureName, name: result.name ?? ''
1129
+ })
1130
+ } else {
1131
+ responses[responseIndex].answer.value = { ...result, type: blob.type, secureName, name: result.name ?? '' }
1132
+ }
1133
+ }, [responses, handleUpload])
1134
+
1105
1135
  const submit = useCallback(async (options?: { onPreRedirect?: () => void, onFileUploadsDone?: () => void, onSuccess?: (r: FormResponse) => void, includedFieldIds?: string[], otherEnduserIds?: string[], onBulkErrors?: (errors: { enduserId: string, message: string }[]) => void }) => {
1106
1136
  setSubmitErrorMessage('')
1107
1137
  const hasFile = selectedFiles.find(f => !!f.blobs?.length) !== undefined
@@ -1113,31 +1143,14 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
1113
1143
  for (const blobInfo of selectedFiles) {
1114
1144
  const { blobs, fieldId } = blobInfo
1115
1145
  if (!blobs) continue
1116
-
1146
+
1147
+ const responseIndex = responses.findIndex(f => f.fieldId === fieldId)
1148
+
1149
+ const response = responses[responseIndex]
1150
+ if (response.field?.options?.autoUploadFiles) { continue } // must have uploaded prior to submission
1151
+
1117
1152
  for (const blob of blobs) {
1118
- const result: FormResponseAnswerFileValue = { name: blob.name, secureName: '' }
1119
- const { secureName } = await handleUpload(
1120
- {
1121
- name: blob.name,
1122
- size: blob.size,
1123
- type: blob.type,
1124
- enduserId,
1125
- },
1126
- blob
1127
- )
1128
-
1129
- const responseIndex = responses.findIndex(f => f.fieldId === fieldId)
1130
-
1131
- if (responses[responseIndex].answer.type === 'files') {
1132
- if (!responses[responseIndex].answer.value) {
1133
- responses[responseIndex].answer.value = []
1134
- }
1135
- (responses[responseIndex].answer.value as any[]).push({
1136
- ...result, type: blob.type, secureName, name: result.name ?? ''
1137
- })
1138
- } else {
1139
- responses[responseIndex].answer.value = { ...result, type: blob.type, secureName, name: result.name ?? '' }
1140
- }
1153
+ await handleFileUpload(blob, fieldId)
1141
1154
  }
1142
1155
  }
1143
1156
 
@@ -1260,9 +1273,11 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
1260
1273
  } finally {
1261
1274
  setSubmittingStatus(undefined)
1262
1275
  }
1263
- }, [accessCode, automationStepId, enduserId, responses, selectedFiles, session, handleUpload, existingResponses, ga4measurementId, rootResponseId, parentResponseId, calendarEventId, goBackURL, logicOptions])
1276
+ }, [accessCode, automationStepId, enduserId, responses, selectedFiles, session, handleUpload, existingResponses, ga4measurementId, rootResponseId, parentResponseId, calendarEventId, goBackURL, logicOptions, handleFileUpload])
1264
1277
 
1265
1278
  const isNextDisabled = useCallback(() => {
1279
+ if (uploadingFiles.length) { return true }
1280
+
1266
1281
  if (activeField.children.length === 0) {
1267
1282
  return true
1268
1283
  }
@@ -1272,7 +1287,7 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
1272
1287
  }
1273
1288
 
1274
1289
  return false
1275
- }, [activeField, validateField])
1290
+ }, [activeField, validateField, uploadingFiles])
1276
1291
 
1277
1292
  const autoAdvanceRef = useRef(false)
1278
1293
  const goToNextField = useCallback(() => {
@@ -1445,5 +1460,7 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
1445
1460
  customization,
1446
1461
  handleDatabaseSelect,
1447
1462
  logicOptions,
1463
+ uploadingFiles, setUploadingFiles,
1464
+ handleFileUpload,
1448
1465
  }
1449
1466
  }
@@ -13,7 +13,7 @@ import LinearProgress from '@mui/material/LinearProgress';
13
13
  import DatePicker from "react-datepicker";
14
14
  import { datepickerCSS } from "./css/react-datepicker" // avoids build issue with RN
15
15
  import { CancelIcon, FileBlob, IconButton, LabeledIconButton, LoadingButton, Styled, form_display_text_for_language, isDateString, useProducts, useResolvedSession } from ".."
16
- import { AllergyCode, CalendarEvent, DatabaseRecord, FormField } from "@tellescope/types-client"
16
+ import { CalendarEvent, DatabaseRecord, FormField } from "@tellescope/types-client"
17
17
  import { css } from '@emotion/css'
18
18
  import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
19
19
  import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
@@ -24,6 +24,7 @@ import LanguageIcon from '@mui/icons-material/Language';
24
24
  import { Elements, PaymentElement, useStripe, useElements, EmbeddedCheckout, EmbeddedCheckoutProvider } from '@stripe/react-stripe-js';
25
25
  import { loadStripe } from '@stripe/stripe-js';
26
26
  import { CheckCircleOutline, Delete, Edit } from "@mui/icons-material"
27
+ import { WYSIWYG } from "./wysiwyg"
27
28
 
28
29
  export const LanguageSelect = ({ value, ...props }: { value: string, onChange: (s: string) => void}) => (
29
30
  <Grid container alignItems="center" justifyContent={"center"} wrap="nowrap" spacing={1}>
@@ -579,7 +580,8 @@ export const InsuranceInput = ({ field, value, onChange, form, responses, enduse
579
580
  }
580
581
  renderInput={(params) => (
581
582
  <TextField {...params} InputProps={{ ...params.InputProps, sx: defaultInputProps.sx }}
582
- required={!field.isOptional} size="small" label="Insurer"
583
+ required={!field.isOptional} size="small" label={"Insurer"}
584
+ placeholder={field.options?.dataSource === CANVAS_TITLE ? "Search insurer..." : "Insurer"}
583
585
  />
584
586
  )}
585
587
  />
@@ -1185,7 +1187,7 @@ export async function convertHEIC (file: FileBlob | string){
1185
1187
  };
1186
1188
 
1187
1189
  const value_is_image = (f?: { type?: string })=> f?.type?.includes('image')
1188
- export const FileInput = ({ value, onChange, field, existingFileName }: FormInputProps<'file'> & { existingFileName?: string }) => {
1190
+ export const FileInput = ({ value, onChange, field, existingFileName, uploadingFiles, handleFileUpload, setUploadingFiles }: FormInputProps<'file'> & { existingFileName?: string }) => {
1189
1191
  const [error, setError] = useState('')
1190
1192
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
1191
1193
  onDrop: useCallback(
@@ -1202,7 +1204,16 @@ export const FileInput = ({ value, onChange, field, existingFileName }: FormInpu
1202
1204
 
1203
1205
  setError('')
1204
1206
  onChange(file, field.id)
1205
- }, [onChange, field.options?.validFileTypes]
1207
+
1208
+ if (field.options?.autoUploadFiles && handleFileUpload) {
1209
+ setUploadingFiles?.(fs => [...fs, { fieldId: field.id }])
1210
+
1211
+ handleFileUpload(file, field.id)
1212
+ .finally(
1213
+ () => setUploadingFiles?.(fs => fs.filter(f => f.fieldId !== field.id))
1214
+ )
1215
+ }
1216
+ }, [onChange, field.options?.validFileTypes, handleFileUpload, setUploadingFiles]
1206
1217
  ),
1207
1218
  })
1208
1219
 
@@ -1221,7 +1232,9 @@ export const FileInput = ({ value, onChange, field, existingFileName }: FormInpu
1221
1232
  }
1222
1233
  }, [value])
1223
1234
 
1224
- // console.log(document.createElement('input').capture )
1235
+ if (uploadingFiles?.find(f => f.fieldId === field.id)) {
1236
+ return <LinearProgress />
1237
+ }
1225
1238
  return (
1226
1239
  <Grid container direction="column">
1227
1240
  <Grid container {...getRootProps()} sx={{
@@ -1287,11 +1300,12 @@ export const safe_create_url = (file: any) => {
1287
1300
  }
1288
1301
  }
1289
1302
 
1290
- export const FilesInput = ({ value, onChange, field, existingFileName }: FormInputProps<'files'> & { existingFileName?: string }) => {
1303
+ export const FilesInput = ({ value, onChange, field, existingFileName, uploadingFiles, handleFileUpload, setUploadingFiles }: FormInputProps<'files'> & { existingFileName?: string }) => {
1291
1304
  const [error, setError] = useState('')
1292
1305
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
1293
1306
  onDrop: useCallback(
1294
- acceptedFiles => {
1307
+ async acceptedFiles => {
1308
+ setUploadingFiles?.(fs => [...fs, { fieldId: field.id }])
1295
1309
  for (const file of acceptedFiles) {
1296
1310
  if (field.options?.validFileTypes?.length) {
1297
1311
  const match = field.options.validFileTypes.find(t => file.type.includes(t.toLowerCase()))
@@ -1299,11 +1313,16 @@ export const FilesInput = ({ value, onChange, field, existingFileName }: FormInp
1299
1313
  return setError(`File must have type: ${field.options.validFileTypes.join(', ')}`)
1300
1314
  }
1301
1315
  }
1316
+
1317
+ if (field.options?.autoUploadFiles && handleFileUpload) {
1318
+ await handleFileUpload(file, field.id).catch(console.error)
1319
+ }
1302
1320
  }
1321
+ setUploadingFiles?.(fs => fs.filter(f => f.fieldId !== field.id))
1303
1322
 
1304
1323
  setError('')
1305
1324
  onChange([...(value ?? []), ...acceptedFiles], field.id)
1306
- }, [onChange, value, field.options?.validFileTypes]
1325
+ }, [onChange, value, field.options?.validFileTypes, handleFileUpload, setUploadingFiles]
1307
1326
  ),
1308
1327
  })
1309
1328
 
@@ -1313,6 +1332,9 @@ export const FilesInput = ({ value, onChange, field, existingFileName }: FormInp
1313
1332
  })
1314
1333
  ), [value])
1315
1334
 
1335
+ if (uploadingFiles?.find(f => f.fieldId === field.id)) {
1336
+ return <LinearProgress />
1337
+ }
1316
1338
  return (
1317
1339
  <Grid container direction="column">
1318
1340
  <Grid container {...getRootProps()} sx={{
@@ -2828,6 +2850,9 @@ export const AppointmentBookingInput = ({ formResponseId, field, value, onChange
2828
2850
  field.options.userTags
2829
2851
  .flatMap(t => {
2830
2852
  // set dynamic tags if found
2853
+ if (t === '{{logic}}') {
2854
+ return new URL(window.location.href).searchParams.get('logic') || '{{logic}}'
2855
+ }
2831
2856
  if (t.startsWith("{{field.") && t.endsWith(".value}}")) {
2832
2857
  const fieldId = t.replace('{{field.', '').replace(".value}}", '')
2833
2858
 
@@ -2852,6 +2877,9 @@ export const AppointmentBookingInput = ({ formResponseId, field, value, onChange
2852
2877
  field.options.userFilterTags
2853
2878
  .flatMap(t => {
2854
2879
  // set dynamic tags if found
2880
+ if (t === '{{logic}}') {
2881
+ return new URL(window.location.href).searchParams.get('logic') || '{{logic}}'
2882
+ }
2855
2883
  if (t.startsWith("{{field.") && t.endsWith(".value}}")) {
2856
2884
  const fieldId = t.replace('{{field.', '').replace(".value}}", '')
2857
2885
 
@@ -3277,4 +3305,8 @@ export const ConditionsInput = ({ goToNextField, goToPreviousField, field, value
3277
3305
  }
3278
3306
  />
3279
3307
  )
3280
- }
3308
+ }
3309
+
3310
+ export const RichTextInput = ({ field, value, onChange }: FormInputProps<'Rich Text'>) => (
3311
+ <WYSIWYG initialHTML={value} onChange={v => onChange(v, field.id)} style={{ width: '100%' }} editorStyle={{ width: '100%' }} />
3312
+ )
@@ -3,6 +3,7 @@ import { DatabaseRecord, Enduser, Form, FormField } from "@tellescope/types-clie
3
3
  import { FileBlob, TreeNode } from "@tellescope/types-utilities";
4
4
  import { JSXElementConstructor } from "react";
5
5
  import { Response } from "./hooks";
6
+ import { FileUploadHandler } from "../inputs";
6
7
 
7
8
  export type FormFieldNode = TreeNode<FormField>
8
9
 
@@ -32,6 +33,9 @@ export interface FormInputProps<K extends keyof AnswerForType> {
32
33
  groupInsance?: string,
33
34
  disabled?: boolean,
34
35
  isSinglePage?: boolean,
36
+ handleFileUpload?: (blob: FileBlob, fieldId: string) => Promise<any>,
37
+ uploadingFiles?: { fieldId: string }[]
38
+ setUploadingFiles?: React.Dispatch<React.SetStateAction<{ fieldId: string }[]>>,
35
39
  }
36
40
 
37
41
  export type FormInputs = {
@@ -0,0 +1,234 @@
1
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { Editor } from 'react-draft-wysiwyg';
3
+ import draftToHtml from 'draftjs-to-html';
4
+ import htmlToDraft from 'html-to-draftjs';
5
+ import { EditorState, ContentState, convertToRaw } from 'draft-js';
6
+ import { Paper } from '@mui/material';
7
+ import { Styled } from '../mui';
8
+ import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
9
+
10
+ const getToolbar = ({ hideEmoji } : { hideEmoji?: boolean }) => ({
11
+ // hide image and embedded in favor of custom sections
12
+ options: [
13
+ // 'inline',
14
+ // 'blockType',
15
+ 'fontSize',
16
+ // 'fontFamily',
17
+ 'list',
18
+ 'textAlign',
19
+ // 'colorPicker',
20
+ 'link',
21
+ ...(hideEmoji ? [] : ['emoji']),
22
+ // 'remove',
23
+ // 'history',
24
+ // 'image',
25
+ // 'embedded',
26
+ ],
27
+ inline: {
28
+ inDropdown: false,
29
+ className: undefined,
30
+ component: undefined,
31
+ dropdownClassName: undefined,
32
+ options: ['bold', 'italic', 'underline', 'strikethrough', 'monospace', 'superscript', 'subscript'],
33
+ // bold: { icon: bold, className: undefined },
34
+ // italic: { icon: italic, className: undefined },
35
+ // underline: { icon: underline, className: undefined },
36
+ // strikethrough: { icon: strikethrough, className: undefined },
37
+ // monospace: { icon: monospace, className: undefined },
38
+ // superscript: { icon: superscript, className: undefined },
39
+ // subscript: { icon: subscript, className: undefined },
40
+ },
41
+ blockType: {
42
+ inDropdown: true,
43
+ options: ['Normal', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Blockquote', 'Code'],
44
+ className: undefined,
45
+ component: undefined,
46
+ dropdownClassName: undefined,
47
+ },
48
+ fontSize: {
49
+ // icon: fontSize,
50
+ options: [8, 9, 10, 11, 12, 14, 16, 18, 24, 30, 36, 48, 60, 72, 96],
51
+ className: undefined,
52
+ component: undefined,
53
+ dropdownClassName: undefined,
54
+ },
55
+ fontFamily: {
56
+ options: ['Arial', 'Georgia', 'Impact', 'Tahoma', 'Times New Roman', 'Verdana'],
57
+ className: undefined,
58
+ component: undefined,
59
+ dropdownClassName: undefined,
60
+ },
61
+ list: {
62
+ inDropdown: false,
63
+ className: undefined,
64
+ component: undefined,
65
+ dropdownClassName: undefined,
66
+ options: ['unordered', 'ordered', 'indent', 'outdent'],
67
+ // unordered: { icon: unordered, className: undefined },
68
+ // ordered: { icon: ordered, className: undefined },
69
+ // indent: { icon: indent, className: undefined },
70
+ // outdent: { icon: outdent, className: undefined },
71
+ },
72
+ textAlign: {
73
+ inDropdown: false,
74
+ className: undefined,
75
+ component: undefined,
76
+ dropdownClassName: undefined,
77
+ options: ['left', 'center', 'right', 'justify'],
78
+ // left: { icon: left, className: undefined },
79
+ // center: { icon: center, className: undefined },
80
+ // right: { icon: right, className: undefined },
81
+ // justify: { icon: justify, className: undefined },
82
+ },
83
+ colorPicker: {
84
+ // icon: color,
85
+ className: undefined,
86
+ component: undefined,
87
+ popupClassName: undefined,
88
+ colors: ['rgb(97,189,109)', 'rgb(26,188,156)', 'rgb(84,172,210)', 'rgb(44,130,201)',
89
+ 'rgb(147,101,184)', 'rgb(71,85,119)', 'rgb(204,204,204)', 'rgb(65,168,95)', 'rgb(0,168,133)',
90
+ 'rgb(61,142,185)', 'rgb(41,105,176)', 'rgb(85,57,130)', 'rgb(40,50,78)', 'rgb(0,0,0)',
91
+ 'rgb(247,218,100)', 'rgb(251,160,38)', 'rgb(235,107,86)', 'rgb(226,80,65)', 'rgb(163,143,132)',
92
+ 'rgb(239,239,239)', 'rgb(255,255,255)', 'rgb(250,197,28)', 'rgb(243,121,52)', 'rgb(209,72,65)',
93
+ 'rgb(184,49,47)', 'rgb(124,112,107)', 'rgb(209,213,216)'],
94
+ },
95
+ link: {
96
+ inDropdown: false,
97
+ className: undefined,
98
+ component: undefined,
99
+ popupClassName: undefined,
100
+ dropdownClassName: undefined,
101
+ showOpenOptionOnHover: true,
102
+ defaultTargetOption: '_blank',
103
+ options: ['link', 'unlink'],
104
+ // link: { icon: link, className: undefined },
105
+ // unlink: { icon: unlink, className: undefined },
106
+ linkCallback: undefined
107
+ },
108
+ emoji: {
109
+ // icon: emoji,
110
+ className: undefined,
111
+ component: undefined,
112
+ popupClassName: undefined,
113
+ emojis: [
114
+ '😀', '😁', '😂', '😃', '😉', '😋', '😎', '😍', '😗', '🤗', '🤔', '😣', '😫', '😴', '😌', '🤓',
115
+ '😛', '😜', '😠', '😇', '😷', '😈', '👻', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '🙈',
116
+ '🙉', '🙊', '👼', '👮', '🕵', '💂', '👳', '🎅', '👸', '👰', '👲', '🙍', '🙇', '🚶', '🏃', '💃',
117
+ '⛷', '🏂', '🏌', '🏄', '🚣', '🏊', '⛹', '🏋', '🚴', '👫', '💪', '👈', '👉', '👉', '👆', '🖕',
118
+ '👇', '🖖', '🤘', '🖐', '👌', '👍', '👎', '✊', '👊', '👏', '🙌', '🙏', '🐵', '🐶', '🐇', '🐥',
119
+ '🐸', '🐌', '🐛', '🐜', '🐝', '🍉', '🍄', '🍔', '🍤', '🍨', '🍪', '🎂', '🍰', '🍾', '🍷', '🍸',
120
+ '🍺', '🌍', '🚑', '⏰', '🌙', '🌝', '🌞', '⭐', '🌟', '🌠', '🌨', '🌩', '⛄', '🔥', '🎄', '🎈',
121
+ '🎉', '🎊', '🎁', '🎗', '🏀', '🏈', '🎲', '🔇', '🔈', '📣', '🔔', '🎵', '🎷', '💰', '🖊', '📅',
122
+ '✅', '❎', '💯',
123
+ ],
124
+ },
125
+ embedded: {
126
+ // icon: embedded,
127
+ className: undefined,
128
+ component: undefined,
129
+ popupClassName: undefined,
130
+ embedCallback: undefined,
131
+ defaultSize: {
132
+ height: 'auto',
133
+ width: 'auto',
134
+ },
135
+ },
136
+ image: {
137
+ // icon: image,
138
+ className: undefined,
139
+ component: undefined,
140
+ popupClassName: undefined,
141
+ urlEnabled: true,
142
+ uploadEnabled: true,
143
+ alignmentEnabled: true,
144
+ // uploadCallback,
145
+ previewImage: true,
146
+ inputAccept: 'image/gif,image/jpeg,image/jpg,image/png,image/svg',
147
+ alt: { present: false, mandatory: false },
148
+ defaultSize: {
149
+ height: 'auto',
150
+ width: 'auto',
151
+ },
152
+ },
153
+ remove: {
154
+ // icon: eraser,
155
+ className: undefined, component: undefined
156
+ },
157
+ history: {
158
+ inDropdown: false,
159
+ className: undefined,
160
+ component: undefined,
161
+ dropdownClassName: undefined,
162
+ options: ['undo', 'redo'],
163
+ // undo: { icon: undo, className: undefined },
164
+ // redo: { icon: redo, className: undefined },
165
+ },
166
+ })
167
+
168
+
169
+
170
+ export const WYSIWYG = ({ updateHtml, initialHTML: _initialHTML='', autoFocus, onChange, style, editorStyle, hideEmoji } : {
171
+ initialHTML?: string, onChange: (html: string) => void, autoFocus?: boolean,
172
+ editorStyle?: React.CSSProperties,
173
+ hideEmoji?: boolean,
174
+ updateHtml?: string,
175
+ } & Styled) => {
176
+ const trimmed = _initialHTML.trim()
177
+ const initialHTML = (
178
+ trimmed.startsWith('<p>') && trimmed.endsWith('</p>')
179
+ ? trimmed
180
+ : `<p>${trimmed}</p>`
181
+ )
182
+
183
+ const [editorState, setEditorState] = useState(EditorState.createWithContent(
184
+ ContentState.createFromBlockArray(htmlToDraft(initialHTML).contentBlocks)
185
+ ))
186
+ const editorStateRef = useRef(editorState)
187
+
188
+ const editorRef: React.MutableRefObject<Editor | null> = useRef(null)
189
+
190
+ useEffect(() => {
191
+ if (!updateHtml) return
192
+
193
+ setEditorState(
194
+ EditorState.createWithContent(
195
+ ContentState.createFromBlockArray(htmlToDraft(updateHtml).contentBlocks)
196
+ )
197
+ )
198
+ }, [updateHtml])
199
+
200
+ useEffect(() => {
201
+ if (!autoFocus) return
202
+
203
+ if (editorRef.current) {
204
+ editorRef.current?.focusEditor?.()
205
+ setEditorState(es => EditorState.moveFocusToEnd(es))
206
+ }
207
+ }, [editorRef, autoFocus])
208
+
209
+ useEffect(() => {
210
+ if (editorStateRef.current === editorState) return
211
+ editorStateRef.current = editorState
212
+
213
+ // unbounce a bit for perf
214
+ const t = setTimeout(() => (
215
+ onChange(draftToHtml(convertToRaw(editorState.getCurrentContent())))
216
+ ), 99)
217
+
218
+ return () => { clearTimeout(t) }
219
+ }, [onChange, editorState])
220
+
221
+ const toolbar = useMemo(() => getToolbar({ hideEmoji }), [hideEmoji])
222
+
223
+ return (
224
+ <Paper sx={{ padding: 1 }} style={style}>
225
+ <Editor spellCheck ref={editorRef} editorStyle={editorStyle}
226
+ editorState={editorState}
227
+ wrapperClassName="demo-wrapper"
228
+ editorClassName="demo-editor"
229
+ onEditorStateChange={setEditorState}
230
+ toolbar={toolbar}
231
+ />
232
+ </Paper>
233
+ )
234
+ }