@tellescope/react-components 1.247.0 → 1.249.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/form_responses.d.ts +11 -0
- package/lib/cjs/Forms/form_responses.d.ts.map +1 -1
- package/lib/cjs/Forms/form_responses.js +38 -2
- package/lib/cjs/Forms/form_responses.js.map +1 -1
- package/lib/cjs/Forms/forms.d.ts +4 -3
- package/lib/cjs/Forms/forms.d.ts.map +1 -1
- package/lib/cjs/Forms/forms.js +41 -15
- package/lib/cjs/Forms/forms.js.map +1 -1
- package/lib/cjs/Forms/forms.v2.d.ts +2 -2
- package/lib/cjs/Forms/forms.v2.d.ts.map +1 -1
- package/lib/cjs/Forms/forms.v2.js +8 -8
- package/lib/cjs/Forms/forms.v2.js.map +1 -1
- package/lib/cjs/Forms/hooks.d.ts +2 -0
- package/lib/cjs/Forms/hooks.d.ts.map +1 -1
- package/lib/cjs/Forms/hooks.js +57 -16
- package/lib/cjs/Forms/hooks.js.map +1 -1
- package/lib/cjs/Forms/inputs.d.ts +1 -1
- package/lib/cjs/Forms/inputs.d.ts.map +1 -1
- package/lib/cjs/Forms/inputs.js +6 -3
- package/lib/cjs/Forms/inputs.js.map +1 -1
- package/lib/cjs/Forms/inputs.v2.d.ts +1 -2
- package/lib/cjs/Forms/inputs.v2.d.ts.map +1 -1
- package/lib/cjs/Forms/inputs.v2.js +1 -44
- package/lib/cjs/Forms/inputs.v2.js.map +1 -1
- package/lib/cjs/Forms/types.d.ts +1 -0
- package/lib/cjs/Forms/types.d.ts.map +1 -1
- package/lib/cjs/state.d.ts +7 -1
- package/lib/cjs/state.d.ts.map +1 -1
- package/lib/cjs/state.js +43 -1
- package/lib/cjs/state.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 +11 -0
- package/lib/esm/Forms/form_responses.d.ts.map +1 -1
- package/lib/esm/Forms/form_responses.js +37 -2
- package/lib/esm/Forms/form_responses.js.map +1 -1
- package/lib/esm/Forms/forms.d.ts +6 -5
- package/lib/esm/Forms/forms.d.ts.map +1 -1
- package/lib/esm/Forms/forms.js +41 -15
- package/lib/esm/Forms/forms.js.map +1 -1
- package/lib/esm/Forms/forms.v2.d.ts +4 -4
- package/lib/esm/Forms/forms.v2.d.ts.map +1 -1
- package/lib/esm/Forms/forms.v2.js +8 -8
- package/lib/esm/Forms/forms.v2.js.map +1 -1
- package/lib/esm/Forms/hooks.d.ts +2 -0
- package/lib/esm/Forms/hooks.d.ts.map +1 -1
- package/lib/esm/Forms/hooks.js +58 -17
- 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 +6 -3
- package/lib/esm/Forms/inputs.js.map +1 -1
- package/lib/esm/Forms/inputs.native.d.ts +1 -0
- package/lib/esm/Forms/inputs.native.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.v2.d.ts +1 -2
- package/lib/esm/Forms/inputs.v2.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.v2.js +1 -44
- package/lib/esm/Forms/inputs.v2.js.map +1 -1
- package/lib/esm/Forms/types.d.ts +1 -0
- package/lib/esm/Forms/types.d.ts.map +1 -1
- package/lib/esm/TwilioVideo/hooks.d.ts +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 +336 -330
- package/lib/esm/state.d.ts.map +1 -1
- package/lib/esm/state.js +42 -1
- package/lib/esm/state.js.map +1 -1
- package/lib/esm/theme.native.d.ts +1 -0
- package/lib/esm/theme.native.d.ts.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/Forms/form_responses.tsx +133 -2
- package/src/Forms/forms.tsx +52 -20
- package/src/Forms/forms.v2.tsx +13 -7
- package/src/Forms/hooks.tsx +67 -14
- package/src/Forms/inputs.tsx +6 -3
- package/src/Forms/inputs.v2.tsx +1 -50
- package/src/Forms/types.ts +1 -0
- package/src/state.tsx +37 -0
package/src/Forms/hooks.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, use
|
|
|
2
2
|
import { Session } from "@tellescope/sdk"
|
|
3
3
|
import { ChangeHandler, FormFieldNode } from "./types"
|
|
4
4
|
import { DatabaseRecord, Enduser, Form, FormField, FormResponse } from "@tellescope/types-client"
|
|
5
|
-
import { phoneValidator } from "@tellescope/validation"
|
|
5
|
+
import { phoneValidator, is_valid_mm_dd_yyyy } from "@tellescope/validation"
|
|
6
6
|
import { FileBlob, Indexable } from "@tellescope/types-utilities"
|
|
7
7
|
import { CompoundFilter, EnduserRelationship, FormCustomization, FormFieldOptionDetails, FormResponseAnswerAddress, FormResponseAnswerFileValue, FormResponseValue, FormResponseValueAnswer, OrganizationTheme, PreviousFormCompoundLogic, PreviousFormFieldType, Timezone, TIMEZONES } from "@tellescope/types-models"
|
|
8
8
|
import { WithTheme, contact_is_valid, useAddGTMTag, useFileUpload, useFormFields, useFormResponses, useResolvedSession, value_is_loaded } from "../index"
|
|
@@ -425,6 +425,7 @@ export const useOrganizationTheme = () => {
|
|
|
425
425
|
return context?.theme ?? theme
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
+
/** @deprecated Use is_valid_mm_dd_yyyy from @tellescope/validation instead — it validates days-in-month and leap years */
|
|
428
429
|
export const isDateString = (_s='') => {
|
|
429
430
|
const s = _s.trim()
|
|
430
431
|
|
|
@@ -442,7 +443,7 @@ export const isDateString = (_s='') => {
|
|
|
442
443
|
// const [mm,dd,yyyy] = s.split('-').map(v => parseInt(v)) // don't shorthand, for radix argument of parseInt gets messed up
|
|
443
444
|
// const d = Date.parse(`${yyyy}-${mm}-${dd}`) // this format should be explicitly supported by all implementations
|
|
444
445
|
// if (isNaN(d)) return false
|
|
445
|
-
|
|
446
|
+
|
|
446
447
|
return true
|
|
447
448
|
}
|
|
448
449
|
const isZIPString = (s='') => /^\d{5}$/.test(s) || /^\d{5}-\d{4}$/.test(s)
|
|
@@ -500,6 +501,41 @@ const existing_response_if_compatible = (existingResponses: FormResponseValue[]
|
|
|
500
501
|
return undefined // no valid match, write off as data loss due to incompatible type of new question
|
|
501
502
|
}
|
|
502
503
|
|
|
504
|
+
// Filter stale multiple-choice/dropdown selections whose options are now hidden by showCondition
|
|
505
|
+
const filter_stale_choices = (
|
|
506
|
+
value: string[] | undefined,
|
|
507
|
+
field: FormField,
|
|
508
|
+
responseContext: FormResponseValue[],
|
|
509
|
+
enduser?: Partial<Enduser>,
|
|
510
|
+
form?: Form,
|
|
511
|
+
): string[] | undefined => {
|
|
512
|
+
if (!value || !Array.isArray(value)) return value
|
|
513
|
+
if (field.type !== 'multiple_choice' && field.type !== 'Dropdown') return value
|
|
514
|
+
|
|
515
|
+
const options = field.options as { choices?: string[], optionDetails?: FormFieldOptionDetails[] } | undefined
|
|
516
|
+
if (!options?.optionDetails?.length) return value // no option-level conditions to check
|
|
517
|
+
|
|
518
|
+
const choices = options.choices ?? []
|
|
519
|
+
|
|
520
|
+
return value.filter(v => {
|
|
521
|
+
// preserve values not in the choices array (e.g. "other" free-text)
|
|
522
|
+
if (!choices.includes(v)) return true
|
|
523
|
+
|
|
524
|
+
const optionDetail = options.optionDetails?.find(d => d.option === v)
|
|
525
|
+
if (!optionDetail?.showCondition || object_is_empty(optionDetail.showCondition)) {
|
|
526
|
+
return true // no condition means always visible
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return responses_satisfy_conditions(responseContext, optionDetail.showCondition, {
|
|
530
|
+
dateOfBirth: enduser?.dateOfBirth,
|
|
531
|
+
gender: enduser?.gender,
|
|
532
|
+
state: enduser?.state,
|
|
533
|
+
form,
|
|
534
|
+
activeResponses: responseContext,
|
|
535
|
+
})
|
|
536
|
+
})
|
|
537
|
+
}
|
|
538
|
+
|
|
503
539
|
const shouldCallout = (field: FormField | undefined, value: FormResponseValueAnswer['value']) => {
|
|
504
540
|
if (!field) return false
|
|
505
541
|
if (!value) return false
|
|
@@ -545,6 +581,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
545
581
|
const [currentPageIndex, setCurrentPageIndex] = useState(0)
|
|
546
582
|
const [uploadingFiles, setUploadingFiles] = useState<{ fieldId: string }[]>([])
|
|
547
583
|
const prevFieldStackRef = useRef<typeof root[]>([])
|
|
584
|
+
const lastNavigationDirectionRef = useRef<'forward' | 'backward' | null>(null)
|
|
548
585
|
|
|
549
586
|
// Auto-advance state for form continuation
|
|
550
587
|
const [isAutoAdvancing, setIsAutoAdvancing] = useState(false)
|
|
@@ -653,14 +690,22 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
653
690
|
// placeholders for initial fields, reset when fields prop changes, since questions are now different (e.g. different form selected)
|
|
654
691
|
const fieldInitRef = useRef('')
|
|
655
692
|
const initializeFields = useCallback(() => (
|
|
656
|
-
fields.map(f =>
|
|
693
|
+
fields.map(f => {
|
|
694
|
+
const existingValue = existing_response_if_compatible(existingResponses, f)
|
|
695
|
+
const filteredValue = (
|
|
696
|
+
existingValue != null && Array.isArray(existingValue) && (f.type === 'multiple_choice' || f.type === 'Dropdown')
|
|
697
|
+
? filter_stale_choices(existingValue as string[], f, existingResponses ?? [], enduser, form)
|
|
698
|
+
: existingValue
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
return {
|
|
657
702
|
fieldId: f.id,
|
|
658
703
|
fieldTitle: f.title,
|
|
659
704
|
fieldDescription: f.description,
|
|
660
705
|
fieldHtmlDescription: f.htmlDescription,
|
|
661
706
|
externalId: f.externalId,
|
|
662
707
|
intakeField: f.intakeField || undefined,
|
|
663
|
-
touched: false,
|
|
708
|
+
touched: false,
|
|
664
709
|
includeInSubmit: false,
|
|
665
710
|
sharedWithEnduser: f.sharedWithEnduser,
|
|
666
711
|
isCalledOut: existingResponses?.find(r => r.fieldId === f.id)?.isCalledOut,
|
|
@@ -680,11 +725,11 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
680
725
|
: f?.intakeField === 'Address' && existingResponses?.find(r => r.fieldId === f.id && r.answer.type === 'Address')
|
|
681
726
|
? 'State'
|
|
682
727
|
: undefined
|
|
683
|
-
) as any,
|
|
684
|
-
answer: {
|
|
728
|
+
) as any,
|
|
729
|
+
answer: {
|
|
685
730
|
type: f.type,
|
|
686
731
|
value: (
|
|
687
|
-
|
|
732
|
+
filteredValue ?? (
|
|
688
733
|
(f.type === 'Insurance' || f.type === 'Address' || f.type === 'file' || f.type === 'signature' || f.type === 'multiple_choice' || f.type === 'Dropdown' || f.type === 'Table Input' || f.type === 'Database Select' || f.type === 'Medications' || f.type === 'Pharmacy Search')
|
|
689
734
|
? undefined
|
|
690
735
|
: f.type === 'Question Group'
|
|
@@ -710,8 +755,8 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
710
755
|
),
|
|
711
756
|
} as FormResponseValueAnswer,
|
|
712
757
|
field: f,
|
|
713
|
-
})
|
|
714
|
-
), [fields, existingResponses])
|
|
758
|
+
}})
|
|
759
|
+
), [fields, existingResponses, enduser, form])
|
|
715
760
|
|
|
716
761
|
const [responses, setResponses] = useState<(Response)[]>(initializeFields())
|
|
717
762
|
useEffect(() => {
|
|
@@ -993,7 +1038,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
993
1038
|
}
|
|
994
1039
|
|
|
995
1040
|
if (value.answer.type === 'Insurance') {
|
|
996
|
-
if (value.answer.value?.relationshipDetails?.dateOfBirth && !
|
|
1041
|
+
if (value.answer.value?.relationshipDetails?.dateOfBirth && !is_valid_mm_dd_yyyy(value.answer.value.relationshipDetails.dateOfBirth)) {
|
|
997
1042
|
return "Enter date of birth in MM-DD-YYYY format"
|
|
998
1043
|
}
|
|
999
1044
|
if (field.isOptional) return null
|
|
@@ -1240,7 +1285,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1240
1285
|
}
|
|
1241
1286
|
}
|
|
1242
1287
|
} else if (value.answer.type === 'dateString') {
|
|
1243
|
-
if (!
|
|
1288
|
+
if (!is_valid_mm_dd_yyyy(value.answer.value)) {
|
|
1244
1289
|
return "Enter a date in MM-DD-YYYY format"
|
|
1245
1290
|
}
|
|
1246
1291
|
} else if (value.answer.type === 'multiple_choice' || value.answer.type === 'Dropdown') {
|
|
@@ -1291,7 +1336,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1291
1336
|
for (const row of value.answer.value ?? []) {
|
|
1292
1337
|
for (const cell of row) {
|
|
1293
1338
|
const type = field.options?.tableChoices?.find(t => t.label === cell.label)?.type
|
|
1294
|
-
if (type === 'Date' && !
|
|
1339
|
+
if (type === 'Date' && !is_valid_mm_dd_yyyy(cell.entry)) {
|
|
1295
1340
|
return `Enter a date in MM-DD-YYYY format for ${cell.label} in row ${(value.answer.value?.indexOf(row) ?? 0) + 1}`
|
|
1296
1341
|
}
|
|
1297
1342
|
}
|
|
@@ -1446,12 +1491,14 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1446
1491
|
responses: [
|
|
1447
1492
|
...responsesToSubmit,
|
|
1448
1493
|
// include existing responses in case previously saved as draft
|
|
1449
|
-
...(existingResponses ?? []).filter(r =>
|
|
1494
|
+
...(existingResponses ?? []).filter(r =>
|
|
1450
1495
|
!responsesToSubmit.find(_r => r.fieldId === _r.fieldId)
|
|
1451
1496
|
// but don't include responses which were populated from a patient field and not a prior response
|
|
1452
1497
|
// if these are edited, they would be included in responsesToSubmit
|
|
1453
1498
|
&& !r.isPrepopulatedFromEnduserField
|
|
1454
|
-
)
|
|
1499
|
+
)
|
|
1500
|
+
// initializeFields leverages filter_stale_choices to strip answers whose options are no longer visible in multiple choice type questions
|
|
1501
|
+
// existingResponses may still carry stale values for fields the user didn't interact with this session, but preserving them as-is avoids unexpected data loss
|
|
1455
1502
|
],
|
|
1456
1503
|
automationStepId,
|
|
1457
1504
|
customerId,
|
|
@@ -1548,6 +1595,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1548
1595
|
if (!currentValue) return
|
|
1549
1596
|
if (isNextDisabled() && currentValue?.answer.type !== 'Hidden Value') return
|
|
1550
1597
|
|
|
1598
|
+
lastNavigationDirectionRef.current = 'forward'
|
|
1551
1599
|
console.log('going to next field')
|
|
1552
1600
|
if (currentValue.answer.type === 'Question Group') {
|
|
1553
1601
|
const responsesToSave = (
|
|
@@ -1612,6 +1660,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1612
1660
|
const goToPreviousField = useCallback(() => {
|
|
1613
1661
|
if (isPreviousDisabled()) return
|
|
1614
1662
|
|
|
1663
|
+
lastNavigationDirectionRef.current = 'backward'
|
|
1615
1664
|
updateInclusion(false)
|
|
1616
1665
|
|
|
1617
1666
|
const previous = prevFieldStackRef.current.pop()
|
|
@@ -1634,6 +1683,9 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1634
1683
|
touched,
|
|
1635
1684
|
isCalledOut: shouldCallout(fields?.find(f => f?.id === fieldId), value),
|
|
1636
1685
|
isHighlightedOnTimeline: fields?.find(f => f?.id === fieldId)?.highlightOnTimeline,
|
|
1686
|
+
// description fields are never "active", so the normal updateInclusion effect won't fire for them
|
|
1687
|
+
// explicitly mark as included when they receive a non-empty string value (historical data snapshot)
|
|
1688
|
+
...(field?.type === 'description' && typeof value === 'string' && value ? { includeInSubmit: true } : {}),
|
|
1637
1689
|
answer: {
|
|
1638
1690
|
...r.answer,
|
|
1639
1691
|
value: value as any,
|
|
@@ -1745,6 +1797,7 @@ export const useTellescopeForm = ({ dontAutoadvance, isPublicForm, form, urlLogi
|
|
|
1745
1797
|
uploadingFiles, setUploadingFiles,
|
|
1746
1798
|
handleFileUpload,
|
|
1747
1799
|
isAutoAdvancing,
|
|
1800
|
+
lastNavigationDirectionRef,
|
|
1748
1801
|
}
|
|
1749
1802
|
}
|
|
1750
1803
|
|
package/src/Forms/inputs.tsx
CHANGED
|
@@ -4895,7 +4895,7 @@ export const RedirectInput = ({ enduserId, groupId, groupInsance, rootResponseId
|
|
|
4895
4895
|
return null
|
|
4896
4896
|
}
|
|
4897
4897
|
|
|
4898
|
-
export const HiddenValueInput = ({ goToNextField, goToPreviousField, field, value, onChange, isSinglePage, groupFields }: FormInputProps<'email'>) => {
|
|
4898
|
+
export const HiddenValueInput = ({ goToNextField, goToPreviousField, field, value, onChange, isSinglePage, groupFields, lastNavigationDirectionRef }: FormInputProps<'email'>) => {
|
|
4899
4899
|
let lastRef = useRef(0)
|
|
4900
4900
|
let lastIdRef = useRef('')
|
|
4901
4901
|
|
|
@@ -4925,7 +4925,10 @@ export const HiddenValueInput = ({ goToNextField, goToPreviousField, field, valu
|
|
|
4925
4925
|
lastRef.current = Date.now()
|
|
4926
4926
|
lastIdRef.current = field.id
|
|
4927
4927
|
|
|
4928
|
-
|
|
4928
|
+
// Only collapse backward through a chain of hidden fields when actually navigating backward.
|
|
4929
|
+
// On forward nav (including form resume where existingResponses pre-populate the value),
|
|
4930
|
+
// fall through to the else branch so we re-set and advance instead of bouncing back.
|
|
4931
|
+
if (value && lastNavigationDirectionRef?.current === 'backward') {
|
|
4929
4932
|
if (isSinglePage) return
|
|
4930
4933
|
onChange('', field.id)
|
|
4931
4934
|
|
|
@@ -4939,7 +4942,7 @@ export const HiddenValueInput = ({ goToNextField, goToPreviousField, field, valu
|
|
|
4939
4942
|
// pass value that is set after above onChange
|
|
4940
4943
|
goToNextField?.({ type: 'Hidden Value', value: valueToSet })
|
|
4941
4944
|
}
|
|
4942
|
-
}, [value, onChange, field.id, valueToSet, goToNextField, goToPreviousField, isSinglePage, dontNavigate])
|
|
4945
|
+
}, [value, onChange, field.id, valueToSet, goToNextField, goToPreviousField, isSinglePage, dontNavigate, lastNavigationDirectionRef])
|
|
4943
4946
|
|
|
4944
4947
|
return <></>
|
|
4945
4948
|
}
|
package/src/Forms/inputs.v2.tsx
CHANGED
|
@@ -2371,56 +2371,7 @@ export const HeightInput = ({ field, value={} as any, onChange, form, ...props }
|
|
|
2371
2371
|
)
|
|
2372
2372
|
|
|
2373
2373
|
// Re-export from V1 to follow DRY principles
|
|
2374
|
-
export { RedirectInput } from './inputs'
|
|
2375
|
-
|
|
2376
|
-
export const HiddenValueInput = ({ goToNextField, goToPreviousField, field, value, onChange, isSinglePage, groupFields }: FormInputProps<'email'>) => {
|
|
2377
|
-
let lastRef = useRef(0)
|
|
2378
|
-
let lastIdRef = useRef('')
|
|
2379
|
-
|
|
2380
|
-
// in a Question Group, only the first Hidden Value should navigate
|
|
2381
|
-
// AND, it should only navigate if the group only contains hidden values
|
|
2382
|
-
const firstHiddenValue = groupFields?.find(v => v.type === 'Hidden Value')
|
|
2383
|
-
const dontNavigate = (
|
|
2384
|
-
(firstHiddenValue && firstHiddenValue?.id !== field.id) // is in a group, but not the first hidden value
|
|
2385
|
-
|| !!(groupFields?.find(v => v.type !== 'Hidden Value')) // group contains at least 1 non-hidden value
|
|
2386
|
-
)
|
|
2387
|
-
|
|
2388
|
-
const publicIdentifier = useMemo(() => {
|
|
2389
|
-
try {
|
|
2390
|
-
return new URL(window.location.href).searchParams.get('publicIdentifier') || ''
|
|
2391
|
-
} catch(err) {
|
|
2392
|
-
return ''
|
|
2393
|
-
}
|
|
2394
|
-
}, [])
|
|
2395
|
-
|
|
2396
|
-
const valueToSet = useMemo(() => (
|
|
2397
|
-
(field.title === "{{PUBLIC_IDENTIFIER}}" && publicIdentifier) ? publicIdentifier
|
|
2398
|
-
: field.title
|
|
2399
|
-
), [field.title, publicIdentifier])
|
|
2400
|
-
|
|
2401
|
-
useEffect(() => {
|
|
2402
|
-
if (lastRef.current > Date.now() - 1000 && lastIdRef.current === field.id) return
|
|
2403
|
-
lastRef.current = Date.now()
|
|
2404
|
-
lastIdRef.current = field.id
|
|
2405
|
-
|
|
2406
|
-
if (value) {
|
|
2407
|
-
if (isSinglePage) return
|
|
2408
|
-
onChange('', field.id)
|
|
2409
|
-
|
|
2410
|
-
if (dontNavigate) return
|
|
2411
|
-
goToPreviousField?.()
|
|
2412
|
-
} else {
|
|
2413
|
-
onChange(valueToSet, field.id)
|
|
2414
|
-
|
|
2415
|
-
if (dontNavigate) return
|
|
2416
|
-
|
|
2417
|
-
// pass value that is set after above onChange
|
|
2418
|
-
goToNextField?.({ type: 'Hidden Value', value: valueToSet })
|
|
2419
|
-
}
|
|
2420
|
-
}, [value, onChange, field.id, valueToSet, goToNextField, goToPreviousField, isSinglePage, dontNavigate])
|
|
2421
|
-
|
|
2422
|
-
return <></>
|
|
2423
|
-
}
|
|
2374
|
+
export { RedirectInput, HiddenValueInput } from './inputs'
|
|
2424
2375
|
|
|
2425
2376
|
export const EmotiiInput = ({ goToNextField, goToPreviousField, field, value, onChange, form, formResponseId, ...props }: FormInputProps<'email'>) => {
|
|
2426
2377
|
const session = useResolvedSession()
|
package/src/Forms/types.ts
CHANGED
|
@@ -39,6 +39,7 @@ export interface FormInputProps<K extends keyof AnswerForType> {
|
|
|
39
39
|
setUploadingFiles?: React.Dispatch<React.SetStateAction<{ fieldId: string }[]>>,
|
|
40
40
|
groupFields?: FormField[],
|
|
41
41
|
inputProps?: { sx: SxProps },
|
|
42
|
+
lastNavigationDirectionRef?: React.MutableRefObject<'forward' | 'backward' | null>,
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export type FormInputs = {
|
package/src/state.tsx
CHANGED
|
@@ -2637,6 +2637,43 @@ export const useIntegrations = (options={} as HookOptions<Integration>) => {
|
|
|
2637
2637
|
)
|
|
2638
2638
|
}
|
|
2639
2639
|
|
|
2640
|
+
export const useRedactedIntegrations = () => {
|
|
2641
|
+
const session = useSession()
|
|
2642
|
+
const [loadingState, setLoadingState] = useState<LoadedData<Integration[]>>({ status: LoadingStatus.Fetching, value: [] as any })
|
|
2643
|
+
const fetchedRef = useRef(false)
|
|
2644
|
+
|
|
2645
|
+
useEffect(() => {
|
|
2646
|
+
if (fetchedRef.current) return
|
|
2647
|
+
fetchedRef.current = true
|
|
2648
|
+
|
|
2649
|
+
let cancelled = false
|
|
2650
|
+
session.api.integrations.load_redacted({})
|
|
2651
|
+
.then((result: any) => {
|
|
2652
|
+
if (!cancelled) setLoadingState({ status: LoadingStatus.Loaded, value: result.integrations })
|
|
2653
|
+
})
|
|
2654
|
+
.catch((err: any) => {
|
|
2655
|
+
if (!cancelled) setLoadingState({ status: LoadingStatus.Error, value: err })
|
|
2656
|
+
})
|
|
2657
|
+
return () => { cancelled = true }
|
|
2658
|
+
}, [session])
|
|
2659
|
+
|
|
2660
|
+
const updateIntegration = useCallback(async (id: string, updates: Partial<Integration>) => {
|
|
2661
|
+
const result: any = await session.api.integrations.update_settings({ id, updates })
|
|
2662
|
+
setLoadingState((prev: LoadedData<Integration[]>) => {
|
|
2663
|
+
if (prev.status !== LoadingStatus.Loaded) return prev
|
|
2664
|
+
return {
|
|
2665
|
+
...prev,
|
|
2666
|
+
value: prev.value.map((i: Integration) => i.id === id ? { ...i, ...result.integration } : i),
|
|
2667
|
+
}
|
|
2668
|
+
})
|
|
2669
|
+
}, [session])
|
|
2670
|
+
|
|
2671
|
+
return [
|
|
2672
|
+
loadingState,
|
|
2673
|
+
{ updateIntegration },
|
|
2674
|
+
] as const
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2640
2677
|
export const usePortalCustomizations = (options={} as HookOptions<PortalCustomization>) => {
|
|
2641
2678
|
const session = useResolvedSession()
|
|
2642
2679
|
return useListStateHook(
|