@tellescope/react-components 1.234.0 → 1.235.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 (39) hide show
  1. package/lib/cjs/Forms/forms.d.ts.map +1 -1
  2. package/lib/cjs/Forms/forms.js +37 -35
  3. package/lib/cjs/Forms/forms.js.map +1 -1
  4. package/lib/cjs/Forms/forms.v2.d.ts.map +1 -1
  5. package/lib/cjs/Forms/forms.v2.js +37 -35
  6. package/lib/cjs/Forms/forms.v2.js.map +1 -1
  7. package/lib/cjs/Forms/inputs.d.ts +17 -2
  8. package/lib/cjs/Forms/inputs.d.ts.map +1 -1
  9. package/lib/cjs/Forms/inputs.js +308 -36
  10. package/lib/cjs/Forms/inputs.js.map +1 -1
  11. package/lib/cjs/Forms/inputs.v2.d.ts +3 -2
  12. package/lib/cjs/Forms/inputs.v2.d.ts.map +1 -1
  13. package/lib/cjs/Forms/inputs.v2.js +20 -293
  14. package/lib/cjs/Forms/inputs.v2.js.map +1 -1
  15. package/lib/cjs/Forms/types.d.ts +4 -0
  16. package/lib/cjs/Forms/types.d.ts.map +1 -1
  17. package/lib/esm/Forms/forms.d.ts.map +1 -1
  18. package/lib/esm/Forms/forms.js +38 -36
  19. package/lib/esm/Forms/forms.js.map +1 -1
  20. package/lib/esm/Forms/forms.v2.d.ts.map +1 -1
  21. package/lib/esm/Forms/forms.v2.js +38 -36
  22. package/lib/esm/Forms/forms.v2.js.map +1 -1
  23. package/lib/esm/Forms/inputs.d.ts +17 -2
  24. package/lib/esm/Forms/inputs.d.ts.map +1 -1
  25. package/lib/esm/Forms/inputs.js +306 -37
  26. package/lib/esm/Forms/inputs.js.map +1 -1
  27. package/lib/esm/Forms/inputs.v2.d.ts +3 -2
  28. package/lib/esm/Forms/inputs.v2.d.ts.map +1 -1
  29. package/lib/esm/Forms/inputs.v2.js +15 -289
  30. package/lib/esm/Forms/inputs.v2.js.map +1 -1
  31. package/lib/esm/Forms/types.d.ts +4 -0
  32. package/lib/esm/Forms/types.d.ts.map +1 -1
  33. package/lib/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +9 -9
  35. package/src/Forms/forms.tsx +13 -6
  36. package/src/Forms/forms.v2.tsx +9 -2
  37. package/src/Forms/inputs.tsx +449 -65
  38. package/src/Forms/inputs.v2.tsx +12 -593
  39. package/src/Forms/types.ts +4 -2
@@ -544,368 +544,17 @@ export const NumberInput = ({ field, value, onChange, form, ...props }: FormInpu
544
544
  )
545
545
  }
546
546
 
547
- export const InsuranceInput = ({ field, onDatabaseSelect, value, onChange, form, responses, enduser, ...props }: FormInputProps<'Insurance'>) => {
548
- const session = useResolvedSession()
549
-
550
- const [payers, setPayers] = useState<{ id: string, name: string, databaseRecord?: DatabaseRecord, type?: string, state?: string }[]>([])
551
- const [query, setQuery] = useState('')
552
-
553
- const addressQuestion = useMemo(() => responses?.find(r => {
554
- if (r.answer.type !== 'Address') return false
555
- if (r.field.intakeField !== 'Address') return false
556
-
557
- // make sure state is actually defined (in case of multiple address questions, where 1+ are blank)
558
- if (!r.answer.value?.state) return false
559
-
560
- return true
561
- }), [responses])
562
-
563
- const state = useMemo(() => (
564
- (addressQuestion?.answer?.type === 'Address' ? addressQuestion?.answer?.value?.state : undefined) || enduser?.state
565
- ), [enduser?.state, addressQuestion])
566
-
567
- const loadRef = useRef(false) // so session changes don't cause
568
- useEffect(() => {
569
- if (field?.options?.dataSource === CANVAS_TITLE) return // instead, look-up while typing against Canvas Search API
570
- if (loadRef.current) return
571
- loadRef.current = true
572
-
573
- // just load all at once, should be reasonably performant compared to paging
574
- session.api.form_fields.load_choices_from_database({ fieldId: field.id, limit: 10000 })
575
- .then(({ choices }) => setPayers(
576
- choices
577
- .map(c => ({
578
- id: c.values.find(v => v.label?.trim()?.toLowerCase() === 'id')?.value?.toString() || '',
579
- name: c.values.find(v => v.label?.trim()?.toLowerCase() === 'name')?.value?.toString() || '',
580
- state: c.values.find(v => v.label?.trim()?.toLowerCase() === 'state')?.value?.toString() || '',
581
- type: c.values.find(v => v.label?.trim()?.toLowerCase() === 'type')?.value?.toString() || '',
582
- databaseRecord: c,
583
- }))
584
- .filter(c => !c.state || !state || (c.state === state))
585
- ))
586
- .catch(console.error)
587
- }, [session, state, field?.options?.dataSource])
588
-
589
- const searchRef = useRef(query)
590
- useEffect(() => {
591
- if (field?.options?.dataSource !== CANVAS_TITLE) { return }
592
- if (!query) return
593
- if (searchRef.current === query) return
594
- searchRef.current = query
595
-
596
- session.api.integrations.proxy_read({
597
- integration: CANVAS_TITLE,
598
- query,
599
- type: 'organizations',
600
- })
601
- .then(({ data }) => {
602
- try {
603
- setPayers(data.map((d: any) => ({
604
- id: d.resource.id,
605
- name: d.resource.name,
606
- })))
607
- } catch(err) { console.error }
608
- })
609
- .catch(console.error)
610
- }, [session, field?.options?.dataSource, query])
611
-
612
- return (
613
- <Grid container spacing={2} sx={{ mt: '0' }}>
614
- <Grid item xs={12} sm={6}>
615
- <Autocomplete freeSolo={!field.options?.requirePredefinedInsurer} options={payers.map(p => p.name)}
616
- value={value?.payerName || ''}
617
- onChange={(e, v) => onChange({
618
- ...value,
619
- payerName: v || '',
620
- payerId: payers.find(p => p.name === v)?.id || '',
621
- payerType: payers.find(p => p.name === v)?.type || '',
622
- }, field.id)}
623
- onInputChange={
624
- field.options?.requirePredefinedInsurer
625
- ? (e, v) => { if (v) { setQuery(v) } }
626
- : (e, v) => {
627
- if (v) { setQuery(v) }
628
-
629
- const databaseRecord = payers.find(p => p.name === v)?.databaseRecord
630
- if (databaseRecord) {
631
- onDatabaseSelect?.([databaseRecord])
632
- }
633
-
634
- onChange({
635
- ...value,
636
- payerName: v || '',
637
- payerId: payers.find(p => p.name === v)?.id || '',
638
- payerType: payers.find(p => p.name === v)?.type || '',
639
- }, field.id)
640
- }
641
- }
642
- renderInput={(params) => (
643
- <TextField {...params} InputProps={{ ...params.InputProps, sx: defaultInputProps.sx }}
644
- required={!field.isOptional} size="small" label={"Insurer"}
645
- placeholder={field.options?.dataSource === CANVAS_TITLE ? "Search insurer..." : "Insurer"}
646
- />
647
- )}
648
- />
649
- </Grid>
650
- <Grid item xs={12} sm={6}>
651
- <TextField InputProps={defaultInputProps} required={!field.isOptional} fullWidth value={value?.memberId ?? ''}
652
- onChange={e => onChange({ ...value, memberId: e.target.value }, field.id)}
653
- label={form_display_text_for_language(form, "Member ID", '')}
654
- size="small"
655
- />
656
- </Grid>
657
-
658
- <Grid item xs={12} sm={6}>
659
- <TextField InputProps={defaultInputProps} required={false} fullWidth value={value?.planName ?? ''}
660
- onChange={e => onChange({ ...value, planName: e.target.value }, field.id)}
661
- label={form_display_text_for_language(form, "Plan Name", '')}
662
- size="small"
663
- />
664
- </Grid>
665
-
666
- <Grid item xs={12} sm={6}>
667
- <DateStringInput size="small" label="Plan Start Date"
668
- field={{
669
- ...field,
670
- isOptional: true, //field.isOptional || field.options?.billingProvider === 'Candid'
671
- }}
672
- value={value?.startDate || ''}
673
- onChange={startDate =>
674
- onChange({
675
- ...value,
676
- startDate,
677
- }, field.id)
678
- }
679
- />
680
- </Grid>
681
-
682
- {field.options?.includeGroupNumber &&
683
- <Grid item xs={12}>
684
- <TextField InputProps={defaultInputProps} fullWidth value={value?.groupNumber ?? ''}
685
- onChange={e => onChange({ ...value, groupNumber: e.target.value }, field.id)}
686
- label={form_display_text_for_language(form, "Group Number", '')}
687
- size="small"
688
- />
689
- </Grid>
690
- }
547
+ // InsuranceInput, BridgeEligibilityInput, and AppointmentBookingInput logic is shared with inputs.tsx to avoid duplication
548
+ import { InsuranceInput as SharedInsuranceInput, BridgeEligibilityInput as SharedBridgeEligibilityInput, AppointmentBookingInput as SharedAppointmentBookingInput } from './inputs'
691
549
 
692
- <Grid item xs={12}>
693
- <StringSelector size="small" label="Relationship to Policy Owner"
694
- options={
695
- (
696
- (field.options?.billingProvider === CANVAS_TITLE || field.options?.dataSource === CANVAS_TITLE )
697
- ? INSURANCE_RELATIONSHIPS_CANVAS
698
- : INSURANCE_RELATIONSHIPS
699
- )
700
- .sort((x, y) => x.localeCompare(y))
701
- }
702
- value={value?.relationship || 'Self'}
703
- onChange={relationship =>
704
- onChange({ ...value, relationship: relationship as InsuranceRelationship || 'Self' }, field.id)
705
- }
706
- />
707
- </Grid>
708
-
709
- {(value?.relationship || 'Self') !== 'Self' &&
710
- <>
711
- <Grid item xs={12}>
712
- <Typography sx={{ fontWeight: 'bold' }}>Policy Owner Details</Typography>
713
- </Grid>
714
-
715
- <Grid item xs={6}>
716
- <TextField label="First Name" size="small" InputProps={defaultInputProps} fullWidth
717
- value={value?.relationshipDetails?.fname || ''}
718
- required={!field.isOptional}
719
- onChange={e =>
720
- onChange({
721
- ...value,
722
- relationshipDetails: { ...value?.relationshipDetails, fname: e.target.value }
723
- }, field.id)
724
- }
725
- />
726
- </Grid>
727
- <Grid item xs={6}>
728
- <TextField label="Last Name" size="small" InputProps={defaultInputProps} fullWidth
729
- value={value?.relationshipDetails?.lname || ''}
730
- required={!field.isOptional}
731
- onChange={e =>
732
- onChange({
733
- ...value,
734
- relationshipDetails: { ...value?.relationshipDetails, lname: e.target.value }
735
- }, field.id)
736
- }
737
- />
738
- </Grid>
739
- <Grid item xs={6}>
740
- <StringSelector options={TELLESCOPE_GENDERS} size="small" label="Gender"
741
- value={value?.relationshipDetails?.gender || ''}
742
- required={!field.isOptional}
743
- onChange={v =>
744
- onChange({
745
- ...value,
746
- relationshipDetails: { ...value?.relationshipDetails, gender: v as TellescopeGender }
747
- }, field.id)
748
- }
749
- />
750
- </Grid>
751
- <Grid item xs={6}>
752
- <DateStringInput size="small" label="Date of Birth"
753
- field={{
754
- ...field,
755
- isOptional: field.isOptional || field.options?.billingProvider === 'Candid'
756
- }}
757
- value={value?.relationshipDetails?.dateOfBirth || ''}
758
- onChange={dateOfBirth =>
759
- onChange({
760
- ...value,
761
- relationshipDetails: { ...value?.relationshipDetails, dateOfBirth }
762
- }, field.id)
763
- }
764
- />
765
- </Grid>
766
-
767
-
768
- {/* <Grid item xs={6}>
769
- <TextField label="Email" type="email" size="small" InputProps={defaultInputProps} fullWidth
770
- value={value?.relationshipDetails?.email || ''}
771
- onChange={e =>
772
- onChange({
773
- ...value,
774
- relationshipDetails: { ...value?.relationshipDetails, email: e.target.value }
775
- }, field.id)
776
- }
777
- />
778
- </Grid>
779
- <Grid item xs={6}>
780
- <TextField label="Cell Phone" size="small" InputProps={defaultInputProps} fullWidth
781
- value={value?.relationshipDetails?.phone || ''}
782
- onChange={e =>
783
- onChange({
784
- ...value,
785
- relationshipDetails: { ...value?.relationshipDetails, phone: e.target.value }
786
- }, field.id)
787
- }
788
- />
789
- </Grid>
790
-
791
- <Grid item xs={12}>
792
- <AddressInput field={field} value={{
793
- addressLineOne: value?.relationshipDetails?.address?.lineOne || '',
794
- addressLineTwo: value?.relationshipDetails?.address?.lineTwo || '',
795
- city: value?.relationshipDetails?.address?.city || '',
796
- state: value?.relationshipDetails?.address?.state || '',
797
- zipCode: value?.relationshipDetails?.address?.zipCode || '',
798
- }}
799
- onChange={v => {
800
- const { addressLineOne='', addressLineTwo='', ...address } = v || {}
801
- onChange({
802
- ...value,
803
- relationshipDetails: {
804
- ...value?.relationshipDetails,
805
- address: {
806
- lineOne: addressLineOne,
807
- lineTwo: addressLineTwo,
808
- ...address,
809
- },
810
- }
811
- }, field.id)
812
- }}
813
- />
814
- </Grid> */}
815
-
816
- {/* <Grid item xs={6}>
817
- <TextField label="Address"
818
- value={value?.relationshipDetails?.address?.lineOne || ''}
819
- onChange={e =>
820
- onChange({
821
- ...value,
822
- relationshipDetails: {
823
- ...value?.relationshipDetails,
824
- address: {
825
- ...value?.relationshipDetails?.address,
826
- lineOne: e.target.value
827
- }
828
- }
829
- }, field.id)
830
- }
831
- />
832
- </Grid>
833
- <Grid item xs={6}>
834
- <TextField label="Line Two"
835
- value={value?.relationshipDetails?.address?.lineTwo || ''}
836
- onChange={e =>
837
- onChange({
838
- ...value,
839
- relationshipDetails: {
840
- ...value?.relationshipDetails,
841
- address: {
842
- ...value?.relationshipDetails?.address,
843
- lineTwo: e.target.value
844
- }
845
- }
846
- }, field.id)
847
- }
848
- />
849
- </Grid>
850
- <Grid item xs={6}>
851
- <TextField label="City"
852
- value={value?.relationshipDetails?.address?.city || ''}
853
- onChange={e =>
854
- onChange({
855
- ...value,
856
- relationshipDetails: {
857
- ...value?.relationshipDetails,
858
- address: {
859
- ...value?.relationshipDetails?.address,
860
- city: e.target.value
861
- }
862
- }
863
- }, field.id)
864
- }
865
- />
866
- </Grid>
867
- <Grid item xs={2}>
868
- <TextField label="State"
869
- value={value?.relationshipDetails?.address?.state || ''}
870
- onChange={e =>
871
- onChange({
872
- ...value,
873
- relationshipDetails: {
874
- ...value?.relationshipDetails,
875
- address: {
876
- ...value?.relationshipDetails?.address,
877
- state: e.target.value
878
- }
879
- }
880
- }, field.id)
881
- }
882
- />
883
- </Grid>
550
+ // Wrap the shared InsuranceInput component with v2-specific props
551
+ export const InsuranceInput = (props: FormInputProps<'Insurance'>) => {
552
+ return <SharedInsuranceInput {...props} inputProps={defaultInputProps} />
553
+ }
884
554
 
885
- <Grid item xs={4}>
886
- <Autocomplete value={value?.state}
887
- options={VALID_STATES}
888
- sx={{ width: 100 }}
889
- disablePortal
890
- onChange={(e, v) => v &&
891
- onChange({
892
- ...value as any,
893
- state: v ?? '',
894
- },
895
- field.id
896
- )}
897
- renderInput={(params) => (
898
- <TextField {...params} InputProps={{ ...params.InputProps, sx: defaultInputProps.sx }}
899
- size={'small'} label={"State"} required={!field.isOptional}
900
- />
901
- )}
902
- {...props}
903
- />
904
- </Grid> */}
905
- </>
906
- }
907
- </Grid>
908
- )
555
+ // Wrap the shared BridgeEligibilityInput component with v2-specific props
556
+ export const BridgeEligibilityInput = (props: FormInputProps<'Bridge Eligibility'>) => {
557
+ return <SharedBridgeEligibilityInput {...props} inputProps={defaultInputProps} />
909
558
  }
910
559
 
911
560
 
@@ -2667,239 +2316,9 @@ export const RelatedContactsInput = ({ field, value: _value, onChange, error: pa
2667
2316
  )
2668
2317
  }
2669
2318
 
2670
- export const AppointmentBookingInput = ({ formResponseId, field, value, onChange, form, responses, goToPreviousField, isPreviousDisabled, enduserId, ...props }: FormInputProps<'Appointment Booking'>) => {
2671
- const session = useResolvedSession()
2672
-
2673
- const [loaded, setLoaded] = useState<Awaited<ReturnType<typeof session['api']['form_fields']['booking_info']>>>()
2674
- const [error, setError] = useState('')
2675
- const [acknowledgedWarning, setAcknowledgedWarning] = useState(false)
2676
- const [height, setHeight] = useState(450)
2677
- const [confirming, setConfirming] = useState(false)
2678
-
2679
- const bookingPageId = field?.options?.bookingPageId
2680
-
2681
-
2682
- const downloadICS = useCallback(async (event : Pick<CalendarEvent, 'id'>) => {
2683
- try {
2684
- downloadFile(
2685
- await session.api.calendar_events.download_ics_file({ calendarEventId: event.id, excludeAttendee: true }) as any,
2686
- { name: "event.ics", dataIsURL: true, type: 'text/calendar'}
2687
- )
2688
- } catch(err) {
2689
- console.error(err)
2690
- }
2691
- }, [session])
2692
-
2693
- const addressQuestion = useMemo(() => responses?.find(r => {
2694
- if (r.answer.type !== 'Address') return false
2695
- if (r.field.intakeField !== 'Address') return false
2696
-
2697
- // make sure state is actually defined (in case of multiple address questions, where 1+ are blank)
2698
- if (!r.answer.value?.state) return false
2699
-
2700
- return true
2701
- }), [responses])
2702
- const state = useMemo(() => (
2703
- addressQuestion?.answer?.type === 'Address' ? addressQuestion?.answer?.value?.state : undefined
2704
- ), [addressQuestion])
2705
-
2706
- const loadBookingInfo = useCallback(() => {
2707
- if (!bookingPageId) return
2708
-
2709
- setError('')
2710
- session.api.form_fields.booking_info({
2711
- enduserId,
2712
- bookingPageId,
2713
- enduserFields: { state }
2714
- })
2715
- .then(setLoaded)
2716
- .catch(e => setError(e?.message || e?.toString() || 'Error loading appointment details'))
2717
- }, [enduserId, bookingPageId, session, state])
2718
-
2719
- const fetchRef = useRef(false)
2720
- useEffect(() => {
2721
- if (value) return
2722
- if (!bookingPageId) return
2723
- if (fetchRef.current) return
2724
- fetchRef.current = true
2725
-
2726
- loadBookingInfo()
2727
- }, [bookingPageId, loadBookingInfo, value])
2728
-
2729
- useEffect(() => {
2730
- const handleMessage = (m: MessageEvent) => {
2731
- // entropy to separate from other booking pages rendered on the same screen
2732
- if (
2733
- m?.data?.type === 'Booking Success'
2734
- && typeof m?.data?.bookedEventId === 'string'
2735
- && (!m?.data?.entropy || m?.data?.entropy === loaded?.entropy)
2736
- ) {
2737
- onChange(m.data.bookedEventId, field.id)
2738
- emit_gtm_event({ event: 'form_progress', fieldId: field.id, formId: field.formId, title: field.title, status: "Appointment Booked" })
2739
- }
2740
- if (m?.data?.type === 'CalendarPicker') {
2741
- setHeight(750)
2742
- }
2743
- if (m?.data?.type === 'UsersPicker') {
2744
- setHeight(450)
2745
- }
2746
- if (m?.data?.type === 'Confirmation') {
2747
- setConfirming(true)
2748
- }
2749
- if (m?.data?.type === 'Join Link' && m?.data?.link) {
2750
- update_local_storage('tellescope_last_booking_page_join_link', m.data.link)
2751
- }
2752
- else {
2753
- setConfirming(false)
2754
- }
2755
- }
2756
-
2757
- window.addEventListener('message', handleMessage)
2758
- return () => { window.removeEventListener('message', handleMessage) }
2759
- }, [field?.id, field?.formId, field?.title, onChange, acknowledgedWarning, value, loaded?.entropy])
2760
-
2761
- if (value) {
2762
- return (
2763
- <Grid container direction="column" spacing={1}>
2764
- <Grid item>
2765
- <Grid container alignItems="center" wrap="nowrap">
2766
- <CheckCircleOutline color="success" />
2767
-
2768
- <Typography sx={{ ml: 1, fontSize: 20 }}>
2769
- Your appointment has been booked
2770
- </Typography>
2771
- </Grid>
2772
- </Grid>
2773
-
2774
- <Grid item sx={{ maxWidth: 250 }}>
2775
- <LoadingButton variant="contained" style={{ maxWidth: 250 }}
2776
- submitText="Add to Calendar" submittingText="Downloading..."
2777
- onClick={() => downloadICS({ id: value })}
2778
- />
2779
- </Grid>
2780
- </Grid>
2781
- )
2782
- }
2783
- if (!bookingPageId) {
2784
- return <Typography>No booking page specified</Typography>
2785
- }
2786
- if (error) {
2787
- return (
2788
- <Grid container direction="column" spacing={1}>
2789
- <Grid item>
2790
- <Typography color="error">Error: {error}</Typography>
2791
- </Grid>
2792
-
2793
- <Grid item>
2794
- <LoadingButton disabled={!bookingPageId} style={{ maxWidth: 300 }}
2795
- variant="contained" onClick={loadBookingInfo}
2796
- submitText="Try Again" submittingText="Loading..."
2797
- />
2798
- </Grid>
2799
- </Grid>
2800
- )
2801
- }
2802
- if (!loaded?.bookingURL) {
2803
- return <LinearProgress />
2804
- }
2805
-
2806
- let bookingURL = loaded.bookingURL
2807
- if (field.options?.userTags?.length) {
2808
- bookingURL += `&userTags=${
2809
- field.options.userTags
2810
- .flatMap(t => {
2811
- // set dynamic tags if found
2812
- if (t === '{{logic}}') {
2813
- return new URL(window.location.href).searchParams.get('logic') || '{{logic}}'
2814
- }
2815
- if (t.startsWith("{{field.") && t.endsWith(".value}}")) {
2816
- const fieldId = t.replace('{{field.', '').replace(".value}}", '')
2817
-
2818
- const answer = responses?.find(r => r.fieldId === fieldId)?.answer
2819
- if (!answer?.value) return t
2820
-
2821
- if (answer.type === 'Insurance') {
2822
- return answer.value.payerName || ''
2823
- }
2824
- if (Array.isArray(answer.value) && typeof answer.value?.[0] === 'string') {
2825
- return answer.value as string[]
2826
- }
2827
- return form_response_value_to_string(answer.value)
2828
- }
2829
- return t
2830
- })
2831
- .join(',')
2832
- }`
2833
- }
2834
- if (field.options?.userFilterTags?.length) {
2835
- bookingURL += `&userFilterTags=${
2836
- field.options.userFilterTags
2837
- .flatMap(t => {
2838
- // set dynamic tags if found
2839
- if (t === '{{logic}}') {
2840
- return new URL(window.location.href).searchParams.get('logic') || '{{logic}}'
2841
- }
2842
- if (t.startsWith("{{field.") && t.endsWith(".value}}")) {
2843
- const fieldId = t.replace('{{field.', '').replace(".value}}", '')
2844
-
2845
- const answer = responses?.find(r => r.fieldId === fieldId)?.answer
2846
- if (!answer?.value) return t
2847
-
2848
- if (answer.type === 'Insurance') {
2849
- return answer.value.payerName || ''
2850
- }
2851
- if (Array.isArray(answer.value) && typeof answer.value?.[0] === 'string') {
2852
- return answer.value as string[]
2853
- }
2854
- return form_response_value_to_string(answer.value)
2855
- }
2856
- return t
2857
- })
2858
- .join(',')
2859
- }`
2860
- }
2861
- // need to use form?.id for internally-submitted forms because formResponseId isn't generated until initial submission or saved draft
2862
- if (field.options?.holdAppointmentMinutes && (formResponseId || field?.id)) {
2863
- bookingURL += `&formResponseId=${formResponseId || field?.id}`
2864
- bookingURL += `&holdAppointmentMinutes=${field.options.holdAppointmentMinutes}`
2865
- }
2866
-
2867
- return (
2868
- <Grid container direction="column" spacing={1} sx={{ mt: 1 }}>
2869
- {/* When skipping user selection, include a back button at the top for clearer navigation on mobile */}
2870
- {!!field.options?.userFilterTags?.length && !field.options.userTags?.length && !isPreviousDisabled?.() && !confirming &&
2871
- <Grid item alignSelf="flex-start" >
2872
- <Button variant="outlined" onClick={goToPreviousField} sx={{ height: 25, p: 0.5, px: 1 }}>
2873
- Back
2874
- </Button>
2875
- </Grid>
2876
- }
2877
-
2878
- {loaded.warningMessage &&
2879
- <Grid item>
2880
- <Typography color="error" sx={{ fontSize: 20, fontWeight: 'bold' }}>
2881
- {loaded.warningMessage}
2882
- </Typography>
2883
- </Grid>
2884
- }
2885
-
2886
- <Grid item>
2887
- {(!loaded.warningMessage || acknowledgedWarning)
2888
- ? (
2889
- <iframe title="Appointment Booking Embed"
2890
- src={bookingURL}
2891
- style={{ border: 'none', width: '100%', height }}
2892
- />
2893
- )
2894
- : (
2895
- <Button variant="outlined" onClick={() => setAcknowledgedWarning(true)}>
2896
- Show Booking Page Preview
2897
- </Button>
2898
- )
2899
- }
2900
- </Grid>
2901
- </Grid>
2902
- )
2319
+ // AppointmentBookingInput logic is shared with inputs.tsx to avoid duplication
2320
+ export const AppointmentBookingInput = (props: FormInputProps<'Appointment Booking'>) => {
2321
+ return <SharedAppointmentBookingInput {...props} />
2903
2322
  }
2904
2323
 
2905
2324
  export const HeightInput = ({ field, value={} as any, onChange, ...props }: FormInputProps<'Height'>) => (
@@ -2,6 +2,7 @@ import { AnswerForType, FormResponseValue } from "@tellescope/types-models";
2
2
  import { DatabaseRecord, Enduser, Form, FormField } from "@tellescope/types-client";
3
3
  import { FileBlob, TreeNode } from "@tellescope/types-utilities";
4
4
  import { JSXElementConstructor } from "react";
5
+ import { SxProps } from "@mui/material";
5
6
  import { Response } from "./hooks";
6
7
 
7
8
  export type FormFieldNode = TreeNode<FormField>
@@ -23,8 +24,8 @@ export interface FormInputProps<K extends keyof AnswerForType> {
23
24
  enduserId?: string,
24
25
  enduser?: Partial<Enduser>
25
26
  error?: boolean,
26
- goToPreviousField?: () => void,
27
- goToNextField?: (response?: FormResponseValue['answer']) => void,
27
+ goToPreviousField?: () => void,
28
+ goToNextField?: (response?: FormResponseValue['answer']) => void,
28
29
  isPreviousDisabled?: () => boolean,
29
30
  formResponseId?: string,
30
31
  rootResponseId?: string,
@@ -37,6 +38,7 @@ export interface FormInputProps<K extends keyof AnswerForType> {
37
38
  uploadingFiles?: { fieldId: string }[]
38
39
  setUploadingFiles?: React.Dispatch<React.SetStateAction<{ fieldId: string }[]>>,
39
40
  groupFields?: FormField[],
41
+ inputProps?: { sx: SxProps },
40
42
  }
41
43
 
42
44
  export type FormInputs = {