@tellescope/react-components 1.231.0 → 1.232.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/Forms/forms.js +1 -1
- package/lib/cjs/Forms/forms.js.map +1 -1
- package/lib/cjs/Forms/forms.v2.js +1 -1
- package/lib/cjs/Forms/forms.v2.js.map +1 -1
- package/lib/cjs/Forms/inputs.d.ts +2 -2
- package/lib/cjs/Forms/inputs.d.ts.map +1 -1
- package/lib/cjs/Forms/inputs.js +71 -18
- package/lib/cjs/Forms/inputs.js.map +1 -1
- package/lib/cjs/Forms/inputs.v2.d.ts +2 -4
- package/lib/cjs/Forms/inputs.v2.d.ts.map +1 -1
- package/lib/cjs/Forms/inputs.v2.js +9 -211
- package/lib/cjs/Forms/inputs.v2.js.map +1 -1
- package/lib/esm/Forms/forms.js +1 -1
- package/lib/esm/Forms/forms.js.map +1 -1
- package/lib/esm/Forms/forms.v2.js +1 -1
- package/lib/esm/Forms/forms.v2.js.map +1 -1
- package/lib/esm/Forms/inputs.d.ts +2 -2
- package/lib/esm/Forms/inputs.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.js +72 -19
- package/lib/esm/Forms/inputs.js.map +1 -1
- package/lib/esm/Forms/inputs.v2.d.ts +2 -4
- package/lib/esm/Forms/inputs.v2.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.v2.js +10 -212
- package/lib/esm/Forms/inputs.v2.js.map +1 -1
- package/lib/esm/controls.d.ts +2 -2
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/Forms/forms.tsx +1 -1
- package/src/Forms/forms.v2.tsx +1 -1
- package/src/Forms/inputs.tsx +73 -6
- package/src/Forms/inputs.v2.tsx +11 -340
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellescope/react-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.232.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/cjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"@reduxjs/toolkit": "^1.6.2",
|
|
48
48
|
"@stripe/react-stripe-js": "^2.9.0",
|
|
49
49
|
"@stripe/stripe-js": "^1.52.1",
|
|
50
|
-
"@tellescope/constants": "1.
|
|
51
|
-
"@tellescope/sdk": "1.
|
|
52
|
-
"@tellescope/types-client": "1.
|
|
53
|
-
"@tellescope/types-models": "1.
|
|
54
|
-
"@tellescope/types-utilities": "1.
|
|
55
|
-
"@tellescope/utilities": "1.
|
|
56
|
-
"@tellescope/validation": "1.
|
|
50
|
+
"@tellescope/constants": "1.232.0",
|
|
51
|
+
"@tellescope/sdk": "1.232.0",
|
|
52
|
+
"@tellescope/types-client": "1.232.0",
|
|
53
|
+
"@tellescope/types-models": "1.232.0",
|
|
54
|
+
"@tellescope/types-utilities": "1.232.0",
|
|
55
|
+
"@tellescope/utilities": "1.232.0",
|
|
56
|
+
"@tellescope/validation": "1.232.0",
|
|
57
57
|
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
|
58
58
|
"@typescript-eslint/parser": "^4.33.0",
|
|
59
59
|
"css-to-react-native": "^3.0.0",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
84
84
|
"react-native": "^0.65.0 || ^0.66.0 || ^0.67.0 || ^0.68.0 || ^0.71.0"
|
|
85
85
|
},
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "b79c7d50da5ff767345e58331f483cc541abef20",
|
|
87
87
|
"publishConfig": {
|
|
88
88
|
"access": "public"
|
|
89
89
|
}
|
package/src/Forms/forms.tsx
CHANGED
|
@@ -316,7 +316,7 @@ export const QuestionForField = ({
|
|
|
316
316
|
<AppointmentBooking formResponseId={formResponseId} enduserId={enduserId} goToPreviousField={goToPreviousField} isPreviousDisabled={isPreviousDisabled} responses={responses} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Appointment Booking'>} form={form} />
|
|
317
317
|
)
|
|
318
318
|
: field.type === 'Stripe' ? (
|
|
319
|
-
<Stripe enduserId={enduserId} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} setCustomerId={setCustomerId} form={form} />
|
|
319
|
+
<Stripe enduserId={enduserId} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} setCustomerId={setCustomerId} form={form} responses={responses} enduser={enduser} />
|
|
320
320
|
)
|
|
321
321
|
: field.type === 'Chargebee' ? (
|
|
322
322
|
<Chargebee field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'Chargebee'>} setCustomerId={setCustomerId} form={form} />
|
package/src/Forms/forms.v2.tsx
CHANGED
|
@@ -310,7 +310,7 @@ export const QuestionForField = ({
|
|
|
310
310
|
<AppointmentBooking formResponseId={formResponseId} enduserId={enduserId} goToPreviousField={goToPreviousField} isPreviousDisabled={isPreviousDisabled} responses={responses} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Appointment Booking'>} form={form} />
|
|
311
311
|
)
|
|
312
312
|
: field.type === 'Stripe' ? (
|
|
313
|
-
<Stripe enduserId={enduserId} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} setCustomerId={setCustomerId} form={form} />
|
|
313
|
+
<Stripe enduserId={enduserId} field={field} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<any>} setCustomerId={setCustomerId} form={form} responses={responses} enduser={enduser} />
|
|
314
314
|
)
|
|
315
315
|
: field.type === 'Chargebee' ? (
|
|
316
316
|
<Chargebee field={field} value={value.answer.value as any} onChange={onFieldChange as ChangeHandler<'Chargebee'>} setCustomerId={setCustomerId} form={form} />
|
package/src/Forms/inputs.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { Autocomplete, Box, Button, Checkbox, Chip, Collapse, Divider, FormContr
|
|
|
4
4
|
import { FormInputProps } from "./types"
|
|
5
5
|
import { useDropzone } from "react-dropzone"
|
|
6
6
|
import { CANVAS_TITLE, EMOTII_TITLE, INSURANCE_RELATIONSHIPS, INSURANCE_RELATIONSHIPS_CANVAS, PRIMARY_HEX, RELATIONSHIP_TYPES, TELLESCOPE_GENDERS } from "@tellescope/constants"
|
|
7
|
-
import { MM_DD_YYYY_to_YYYY_MM_DD, capture_is_supported, downloadFile, emit_gtm_event, first_letter_capitalized, form_response_value_to_string, format_stripe_subscription_interval, getLocalTimezone, getPublicFileURL, mm_dd_yyyy, replace_enduser_template_values, truncate_string, update_local_storage, user_display_name } from "@tellescope/utilities"
|
|
7
|
+
import { MM_DD_YYYY_to_YYYY_MM_DD, capture_is_supported, downloadFile, emit_gtm_event, first_letter_capitalized, form_response_value_to_string, format_stripe_subscription_interval, getLocalTimezone, getPublicFileURL, mm_dd_yyyy, object_is_empty, replace_enduser_template_values, responses_satisfy_conditions, truncate_string, update_local_storage, user_display_name } from "@tellescope/utilities"
|
|
8
8
|
import { Address, DatabaseSelectResponse, Enduser, EnduserRelationship, FormResponseValue, InsuranceRelationship, MedicationResponse, MultipleChoiceOptions, FormFieldOptionDetails, TellescopeGender, TIMEZONES_USA } from "@tellescope/types-models"
|
|
9
9
|
import { VALID_STATES, emailValidator, phoneValidator } from "@tellescope/validation"
|
|
10
10
|
import Slider from '@mui/material/Slider';
|
|
@@ -1703,7 +1703,18 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
|
|
|
1703
1703
|
)
|
|
1704
1704
|
}
|
|
1705
1705
|
|
|
1706
|
-
|
|
1706
|
+
// Helper to emit GTM purchase event for Stripe payments (single source of truth)
|
|
1707
|
+
const emitStripePurchaseEvent = (field: FormField, cost: number) => {
|
|
1708
|
+
emit_gtm_event({
|
|
1709
|
+
event: 'form_purchase',
|
|
1710
|
+
productIds: field.options?.productIds || [],
|
|
1711
|
+
fieldId: field.id,
|
|
1712
|
+
value: cost / 100, // Convert cents to dollars
|
|
1713
|
+
currency: 'USD',
|
|
1714
|
+
})
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
export const StripeInput = ({ field, value, onChange, setCustomerId, enduserId, form, responses, enduser }: FormInputProps<'Stripe'> & {
|
|
1707
1718
|
setCustomerId: React.Dispatch<React.SetStateAction<string | undefined>>,
|
|
1708
1719
|
}) => {
|
|
1709
1720
|
const session = useResolvedSession()
|
|
@@ -1718,6 +1729,38 @@ export const StripeInput = ({ field, value, onChange, setCustomerId, enduserId }
|
|
|
1718
1729
|
const [availableProducts, setAvailableProducts] = useState<any[]>([])
|
|
1719
1730
|
const [loadingProducts, setLoadingProducts] = useState(false)
|
|
1720
1731
|
|
|
1732
|
+
// Compute visible products based on conditional logic
|
|
1733
|
+
const visibleProducts = useMemo(() => {
|
|
1734
|
+
if (!showProductSelection || availableProducts.length === 0) {
|
|
1735
|
+
return availableProducts
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
return availableProducts.filter(product => {
|
|
1739
|
+
// Find condition for this product
|
|
1740
|
+
const productCondition = field.options?.productConditions?.find(c => c.productId === product._id)
|
|
1741
|
+
|
|
1742
|
+
// If no condition defined, show by default
|
|
1743
|
+
if (!productCondition?.showCondition || object_is_empty(productCondition.showCondition)) {
|
|
1744
|
+
return true
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// Evaluate condition against current form responses
|
|
1748
|
+
return responses_satisfy_conditions(responses || [], productCondition.showCondition, {
|
|
1749
|
+
dateOfBirth: enduser?.dateOfBirth,
|
|
1750
|
+
gender: enduser?.gender,
|
|
1751
|
+
state: enduser?.state,
|
|
1752
|
+
form,
|
|
1753
|
+
activeResponses: responses,
|
|
1754
|
+
})
|
|
1755
|
+
})
|
|
1756
|
+
}, [availableProducts, field.options?.productConditions, responses, showProductSelection, enduser, form])
|
|
1757
|
+
|
|
1758
|
+
// Automatically deselect products that become hidden
|
|
1759
|
+
useEffect(() => {
|
|
1760
|
+
const visibleProductIds = visibleProducts.map(p => p._id)
|
|
1761
|
+
setSelectedProducts(prev => prev.filter(id => visibleProductIds.includes(id)))
|
|
1762
|
+
}, [visibleProducts])
|
|
1763
|
+
|
|
1721
1764
|
const fetchRef = useRef(false)
|
|
1722
1765
|
useEffect(() => {
|
|
1723
1766
|
if (fetchRef.current) return
|
|
@@ -1784,6 +1827,16 @@ export const StripeInput = ({ field, value, onChange, setCustomerId, enduserId }
|
|
|
1784
1827
|
: 0 // Will be calculated by existing Stripe flow when not in selection mode
|
|
1785
1828
|
)
|
|
1786
1829
|
|
|
1830
|
+
// Emit GTM purchase event once when success screen is displayed
|
|
1831
|
+
const purchaseEmittedRef = useRef(false)
|
|
1832
|
+
useEffect(() => {
|
|
1833
|
+
// Only emit for actual purchases (chargeImmediately), not for saving card details
|
|
1834
|
+
if (value && field.options?.chargeImmediately && !purchaseEmittedRef.current) {
|
|
1835
|
+
emitStripePurchaseEvent(field, cost)
|
|
1836
|
+
purchaseEmittedRef.current = true
|
|
1837
|
+
}
|
|
1838
|
+
}, [value, field, cost])
|
|
1839
|
+
|
|
1787
1840
|
// Handle product selection step
|
|
1788
1841
|
if (showProductSelection) {
|
|
1789
1842
|
if (error) {
|
|
@@ -1815,6 +1868,20 @@ export const StripeInput = ({ field, value, onChange, setCustomerId, enduserId }
|
|
|
1815
1868
|
</Grid>
|
|
1816
1869
|
)
|
|
1817
1870
|
}
|
|
1871
|
+
|
|
1872
|
+
// Check if all products are filtered out by conditional logic
|
|
1873
|
+
if (visibleProducts.length === 0) {
|
|
1874
|
+
return (
|
|
1875
|
+
<Grid container direction="column" spacing={2} alignItems="center">
|
|
1876
|
+
<Grid item>
|
|
1877
|
+
<Typography color="textSecondary">
|
|
1878
|
+
No products are available based on your previous answers.
|
|
1879
|
+
</Typography>
|
|
1880
|
+
</Grid>
|
|
1881
|
+
</Grid>
|
|
1882
|
+
)
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1818
1885
|
const isSingleSelection = field.options?.radio === true
|
|
1819
1886
|
|
|
1820
1887
|
const handleProductSelection = (productId: string) => {
|
|
@@ -1862,7 +1929,7 @@ export const StripeInput = ({ field, value, onChange, setCustomerId, enduserId }
|
|
|
1862
1929
|
<Typography variant="h6">Select Product{isSingleSelection ? '' : 's'}</Typography>
|
|
1863
1930
|
</Grid>
|
|
1864
1931
|
|
|
1865
|
-
{
|
|
1932
|
+
{visibleProducts.map((product) => {
|
|
1866
1933
|
// Use real-time Stripe pricing if available, fallback to Tellescope pricing
|
|
1867
1934
|
const price = product.currentPrice || product.cost
|
|
1868
1935
|
const priceAmount = price?.amount || 0
|
|
@@ -3126,7 +3193,7 @@ export const contact_is_valid = (e: Partial<Enduser>) => {
|
|
|
3126
3193
|
}
|
|
3127
3194
|
}
|
|
3128
3195
|
|
|
3129
|
-
export const RelatedContactsInput = ({ field, value: _value, onChange, ...props }: FormInputProps<'Related Contacts'>) => {
|
|
3196
|
+
export const RelatedContactsInput = ({ field, value: _value, onChange, error: parentError, ...props }: FormInputProps<'Related Contacts'>) => {
|
|
3130
3197
|
// safeguard against any rogue values like empty string
|
|
3131
3198
|
const value = Array.isArray(_value) ? _value : []
|
|
3132
3199
|
|
|
@@ -3201,7 +3268,7 @@ export const RelatedContactsInput = ({ field, value: _value, onChange, ...props
|
|
|
3201
3268
|
<Grid item xs={4}>
|
|
3202
3269
|
<TextField label="Phone Number" size="small" fullWidth
|
|
3203
3270
|
InputProps={defaultInputProps}
|
|
3204
|
-
value={phone} onChange={e => onChange(value.map((v, i) => i === editing ? { ...v, phone: e.target.value } : v), field.id)}
|
|
3271
|
+
value={phone} onChange={e => onChange(value.map((v, i) => i === editing ? { ...v, phone: e.target.value.trim() } : v), field.id)}
|
|
3205
3272
|
/>
|
|
3206
3273
|
</Grid>
|
|
3207
3274
|
}
|
|
@@ -3255,7 +3322,7 @@ export const RelatedContactsInput = ({ field, value: _value, onChange, ...props
|
|
|
3255
3322
|
}
|
|
3256
3323
|
|
|
3257
3324
|
<Grid item sx={{ my: 0.75 }}>
|
|
3258
|
-
<Button variant="outlined" onClick={() => setEditing(-1)} size="small">
|
|
3325
|
+
<Button variant="outlined" onClick={() => setEditing(-1)} size="small" disabled={!!errorMessage || !!parentError}>
|
|
3259
3326
|
Save Contact
|
|
3260
3327
|
</Button>
|
|
3261
3328
|
</Grid>
|
package/src/Forms/inputs.v2.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|
2
2
|
import axios from "axios"
|
|
3
|
-
import { Autocomplete, Box, Button, Checkbox, Chip, Divider, FormControl,
|
|
3
|
+
import { Autocomplete, Box, Button, Checkbox, Chip, Divider, FormControl, Grid, InputLabel, MenuItem, RadioGroup, Select, SxProps, TextField, TextFieldProps, Typography } from "@mui/material"
|
|
4
4
|
import { FormInputProps } from "./types"
|
|
5
5
|
import { useDropzone } from "react-dropzone"
|
|
6
6
|
import { CANVAS_TITLE, EMOTII_TITLE, INSURANCE_RELATIONSHIPS, INSURANCE_RELATIONSHIPS_CANVAS, PRIMARY_HEX, RELATIONSHIP_TYPES, TELLESCOPE_GENDERS } from "@tellescope/constants"
|
|
7
|
-
import { MM_DD_YYYY_to_YYYY_MM_DD, capture_is_supported, downloadFile, emit_gtm_event, first_letter_capitalized, form_response_value_to_string,
|
|
8
|
-
import { Address, DatabaseSelectResponse, Enduser, EnduserRelationship, FormResponseValue, InsuranceRelationship, MedicationResponse, MultipleChoiceOptions,
|
|
7
|
+
import { MM_DD_YYYY_to_YYYY_MM_DD, capture_is_supported, downloadFile, emit_gtm_event, first_letter_capitalized, form_response_value_to_string, getLocalTimezone, getPublicFileURL, mm_dd_yyyy, replace_enduser_template_values, responses_satisfy_conditions, truncate_string, update_local_storage, user_display_name } from "@tellescope/utilities"
|
|
8
|
+
import { Address, DatabaseSelectResponse, Enduser, EnduserRelationship, FormResponseValue, InsuranceRelationship, MedicationResponse, MultipleChoiceOptions, TellescopeGender, TIMEZONES_USA } from "@tellescope/types-models"
|
|
9
9
|
import { VALID_STATES, emailValidator, phoneValidator } from "@tellescope/validation"
|
|
10
10
|
import Slider from '@mui/material/Slider';
|
|
11
11
|
import LinearProgress from '@mui/material/LinearProgress';
|
|
@@ -21,8 +21,6 @@ import heic2any from "heic2any"
|
|
|
21
21
|
import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate';
|
|
22
22
|
import LanguageIcon from '@mui/icons-material/Language';
|
|
23
23
|
|
|
24
|
-
import { Elements, PaymentElement, useStripe, useElements, EmbeddedCheckout, EmbeddedCheckoutProvider } from '@stripe/react-stripe-js';
|
|
25
|
-
import { loadStripe } from '@stripe/stripe-js';
|
|
26
24
|
import { CheckCircleOutline, Delete, Edit, UploadFile } from "@mui/icons-material"
|
|
27
25
|
import { WYSIWYG } from "./wysiwyg"
|
|
28
26
|
|
|
@@ -1666,339 +1664,12 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
|
|
|
1666
1664
|
)
|
|
1667
1665
|
}
|
|
1668
1666
|
|
|
1669
|
-
export const StripeInput = ({ field, value, onChange, setCustomerId, enduserId }: FormInputProps<'Stripe'> & {
|
|
1670
|
-
setCustomerId: React.Dispatch<React.SetStateAction<string | undefined>>,
|
|
1671
|
-
}) => {
|
|
1672
|
-
const session = useResolvedSession()
|
|
1673
|
-
const [clientSecret, setClientSecret] = useState('')
|
|
1674
|
-
const [businessName, setBusinessName] = useState('')
|
|
1675
|
-
const [isCheckout, setIsCheckout] = useState(false)
|
|
1676
|
-
const [stripePromise, setStripePromise] = useState<ReturnType<typeof loadStripe>>()
|
|
1677
|
-
const [answertext, setAnswertext] = useState('')
|
|
1678
|
-
const [error, setError] = useState('')
|
|
1679
|
-
const [selectedProducts, setSelectedProducts] = useState<string[]>([])
|
|
1680
|
-
const [showProductSelection, setShowProductSelection] = useState(false)
|
|
1681
|
-
const [availableProducts, setAvailableProducts] = useState<any[]>([])
|
|
1682
|
-
const [loadingProducts, setLoadingProducts] = useState(false)
|
|
1683
|
-
|
|
1684
|
-
const fetchRef = useRef(false)
|
|
1685
|
-
useEffect(() => {
|
|
1686
|
-
if (fetchRef.current) return
|
|
1687
|
-
if (value && (session.userInfo as any)?.stripeCustomerId) {
|
|
1688
|
-
return setCustomerId(c => c ? c : (session.userInfo as any)?.stripeCustomerId) // already paid or saved card
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
// Check if product selection mode is enabled
|
|
1692
|
-
if (field.options?.stripeProductSelectionMode && (field.options?.productIds || []).length > 1) {
|
|
1693
|
-
setShowProductSelection(true)
|
|
1694
|
-
setLoadingProducts(true)
|
|
1695
|
-
|
|
1696
|
-
// Fetch product data with real-time Stripe pricing via proxy_read
|
|
1697
|
-
const productIds = (field.options.productIds || []).join(',')
|
|
1698
|
-
session.api.integrations.proxy_read({
|
|
1699
|
-
integration: 'Stripe',
|
|
1700
|
-
type: 'product-prices',
|
|
1701
|
-
id: productIds,
|
|
1702
|
-
query: field.options.stripeKey
|
|
1703
|
-
})
|
|
1704
|
-
.then(({ data }) => {
|
|
1705
|
-
setAvailableProducts(data.products || [])
|
|
1706
|
-
setLoadingProducts(false)
|
|
1707
|
-
})
|
|
1708
|
-
.catch((e: any) => {
|
|
1709
|
-
console.error('Error loading product data:', e)
|
|
1710
|
-
const errorMessage = e?.message?.includes?.('Stripe pricing error:')
|
|
1711
|
-
? e.message.replace('Stripe pricing error: ', '')
|
|
1712
|
-
: 'Failed to load product information from Stripe'
|
|
1713
|
-
setError(`Product configuration error: ${errorMessage}`)
|
|
1714
|
-
setLoadingProducts(false)
|
|
1715
|
-
})
|
|
1716
|
-
return
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
fetchRef.current = true
|
|
1720
|
-
|
|
1721
|
-
session.api.form_responses.stripe_details({ fieldId: field.id, enduserId })
|
|
1722
|
-
.then(({ clientSecret, publishableKey, stripeAccount, businessName, customerId, isCheckout, answerText }) => {
|
|
1723
|
-
setAnswertext(answerText || '')
|
|
1724
|
-
setIsCheckout(!!isCheckout)
|
|
1725
|
-
setClientSecret(clientSecret)
|
|
1726
|
-
setStripePromise(loadStripe(publishableKey, { stripeAccount }))
|
|
1727
|
-
setBusinessName(businessName)
|
|
1728
|
-
setCustomerId(customerId)
|
|
1729
|
-
})
|
|
1730
|
-
.catch((e: any) => {
|
|
1731
|
-
console.error(e)
|
|
1732
|
-
if (typeof e?.message === 'string') {
|
|
1733
|
-
setError(e.message)
|
|
1734
|
-
}
|
|
1735
|
-
})
|
|
1736
|
-
}, [session, value, field.id, enduserId])
|
|
1737
|
-
|
|
1738
|
-
const cost = (
|
|
1739
|
-
showProductSelection
|
|
1740
|
-
? selectedProducts.reduce((total, productId) => {
|
|
1741
|
-
const product = availableProducts.find(p => p._id === productId)
|
|
1742
|
-
if (product?.currentPrice) {
|
|
1743
|
-
return total + (product.currentPrice.amount || 0)
|
|
1744
|
-
}
|
|
1745
|
-
return total + (product?.cost?.amount || 0)
|
|
1746
|
-
}, 0)
|
|
1747
|
-
: 0 // Will be calculated by existing Stripe flow when not in selection mode
|
|
1748
|
-
)
|
|
1749
|
-
|
|
1750
|
-
// Handle product selection step
|
|
1751
|
-
if (showProductSelection) {
|
|
1752
|
-
if (error) {
|
|
1753
|
-
return (
|
|
1754
|
-
<Grid container direction="column" spacing={2} alignItems="center">
|
|
1755
|
-
<Grid item>
|
|
1756
|
-
<Typography color="error" variant="h6">
|
|
1757
|
-
Product Configuration Error
|
|
1758
|
-
</Typography>
|
|
1759
|
-
</Grid>
|
|
1760
|
-
<Grid item>
|
|
1761
|
-
<Typography color="error" sx={{ textAlign: 'center' }}>
|
|
1762
|
-
{error}
|
|
1763
|
-
</Typography>
|
|
1764
|
-
</Grid>
|
|
1765
|
-
</Grid>
|
|
1766
|
-
)
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
if (loadingProducts) {
|
|
1770
|
-
return (
|
|
1771
|
-
<Grid container direction="column" spacing={2} alignItems="center">
|
|
1772
|
-
<Grid item>
|
|
1773
|
-
<LinearProgress />
|
|
1774
|
-
</Grid>
|
|
1775
|
-
<Grid item>
|
|
1776
|
-
<Typography>Loading product information...</Typography>
|
|
1777
|
-
</Grid>
|
|
1778
|
-
</Grid>
|
|
1779
|
-
)
|
|
1780
|
-
}
|
|
1781
|
-
const isSingleSelection = field.options?.radio === true
|
|
1782
|
-
|
|
1783
|
-
const handleProductSelection = (productId: string) => {
|
|
1784
|
-
if (isSingleSelection) {
|
|
1785
|
-
setSelectedProducts([productId])
|
|
1786
|
-
} else {
|
|
1787
|
-
setSelectedProducts(prev =>
|
|
1788
|
-
prev.includes(productId)
|
|
1789
|
-
? prev.filter(id => id !== productId)
|
|
1790
|
-
: [...prev, productId]
|
|
1791
|
-
)
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
const handleContinueToPayment = () => {
|
|
1796
|
-
if (selectedProducts.length === 0) return
|
|
1797
|
-
setShowProductSelection(false)
|
|
1798
|
-
fetchRef.current = true
|
|
1799
|
-
|
|
1800
|
-
// Now fetch Stripe details with selected products
|
|
1801
|
-
session.api.form_responses.stripe_details({
|
|
1802
|
-
fieldId: field.id,
|
|
1803
|
-
enduserId,
|
|
1804
|
-
...(selectedProducts.length > 0 && { selectedProductIds: selectedProducts }) // Pass selected products to Stripe checkout
|
|
1805
|
-
} as any)
|
|
1806
|
-
.then(({ clientSecret, publishableKey, stripeAccount, businessName, customerId, isCheckout, answerText }) => {
|
|
1807
|
-
setAnswertext(answerText || '')
|
|
1808
|
-
setIsCheckout(!!isCheckout)
|
|
1809
|
-
setClientSecret(clientSecret)
|
|
1810
|
-
setStripePromise(loadStripe(publishableKey, { stripeAccount }))
|
|
1811
|
-
setBusinessName(businessName)
|
|
1812
|
-
setCustomerId(customerId)
|
|
1813
|
-
})
|
|
1814
|
-
.catch((e: any) => {
|
|
1815
|
-
console.error(e)
|
|
1816
|
-
if (typeof e?.message === 'string') {
|
|
1817
|
-
setError(e.message)
|
|
1818
|
-
}
|
|
1819
|
-
})
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
return (
|
|
1823
|
-
<Grid container direction="column" spacing={2}>
|
|
1824
|
-
<Grid item>
|
|
1825
|
-
<Typography variant="h6">Select Product{isSingleSelection ? '' : 's'}</Typography>
|
|
1826
|
-
</Grid>
|
|
1827
|
-
|
|
1828
|
-
{availableProducts.map((product) => {
|
|
1829
|
-
// Use real-time Stripe pricing if available, fallback to Tellescope pricing
|
|
1830
|
-
const price = product.currentPrice || product.cost
|
|
1831
|
-
const priceAmount = price?.amount || 0
|
|
1832
|
-
const priceCurrency = price?.currency || 'USD'
|
|
1833
|
-
|
|
1834
|
-
return (
|
|
1835
|
-
<Grid item key={product._id}>
|
|
1836
|
-
<FormControlLabel
|
|
1837
|
-
control={
|
|
1838
|
-
isSingleSelection ? (
|
|
1839
|
-
<Radio
|
|
1840
|
-
checked={selectedProducts.includes(product._id)}
|
|
1841
|
-
onChange={() => handleProductSelection(product._id)}
|
|
1842
|
-
/>
|
|
1843
|
-
) : (
|
|
1844
|
-
<Checkbox
|
|
1845
|
-
checked={selectedProducts.includes(product._id)}
|
|
1846
|
-
onChange={() => handleProductSelection(product._id)}
|
|
1847
|
-
/>
|
|
1848
|
-
)
|
|
1849
|
-
}
|
|
1850
|
-
label={
|
|
1851
|
-
<Box>
|
|
1852
|
-
<Typography variant="body1" fontWeight="bold">
|
|
1853
|
-
{product.title}
|
|
1854
|
-
</Typography>
|
|
1855
|
-
{product.description && (
|
|
1856
|
-
<Typography variant="body2" color="textSecondary">
|
|
1857
|
-
{product.description}
|
|
1858
|
-
</Typography>
|
|
1859
|
-
)}
|
|
1860
|
-
<Typography variant="body2" color="primary">
|
|
1861
|
-
${(priceAmount / 100).toFixed(2)} {priceCurrency.toUpperCase()}
|
|
1862
|
-
{product.currentPrice?.isSubscription && (
|
|
1863
|
-
<Typography component="span" variant="caption" sx={{ ml: 0.5 }}>
|
|
1864
|
-
{format_stripe_subscription_interval(product.currentPrice?.interval, product.currentPrice?.interval_count)}
|
|
1865
|
-
</Typography>
|
|
1866
|
-
)}
|
|
1867
|
-
</Typography>
|
|
1868
|
-
</Box>
|
|
1869
|
-
}
|
|
1870
|
-
/>
|
|
1871
|
-
</Grid>
|
|
1872
|
-
)
|
|
1873
|
-
})}
|
|
1874
|
-
|
|
1875
|
-
<Grid item>
|
|
1876
|
-
<Button
|
|
1877
|
-
variant="contained"
|
|
1878
|
-
onClick={handleContinueToPayment}
|
|
1879
|
-
disabled={selectedProducts.length === 0}
|
|
1880
|
-
sx={{ mt: 2 }}
|
|
1881
|
-
>
|
|
1882
|
-
Continue to Payment
|
|
1883
|
-
</Button>
|
|
1884
|
-
</Grid>
|
|
1885
|
-
</Grid>
|
|
1886
|
-
)
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
if (error) {
|
|
1890
|
-
return (
|
|
1891
|
-
<Typography color="error">
|
|
1892
|
-
{error}
|
|
1893
|
-
</Typography>
|
|
1894
|
-
)
|
|
1895
|
-
}
|
|
1896
|
-
if (value) {
|
|
1897
|
-
return (
|
|
1898
|
-
<Grid container alignItems="center" wrap="nowrap">
|
|
1899
|
-
<CheckCircleOutline color="success" />
|
|
1900
1667
|
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
}
|
|
1907
|
-
if (!(clientSecret && stripePromise)) return <LinearProgress />
|
|
1908
|
-
if (isCheckout && stripePromise) return (
|
|
1909
|
-
<EmbeddedCheckoutProvider stripe={stripePromise}
|
|
1910
|
-
options={{
|
|
1911
|
-
clientSecret,
|
|
1912
|
-
onComplete: () => onChange(answertext || 'Completed checkout', field.id),
|
|
1913
|
-
}}
|
|
1914
|
-
>
|
|
1915
|
-
<EmbeddedCheckout />
|
|
1916
|
-
</EmbeddedCheckoutProvider>
|
|
1917
|
-
)
|
|
1918
|
-
return (
|
|
1919
|
-
<Elements stripe={stripePromise} options={{
|
|
1920
|
-
clientSecret,
|
|
1921
|
-
}}>
|
|
1922
|
-
<StripeForm businessName={businessName} onSuccess={() => onChange(answertext || 'Saved card details', field.id)}
|
|
1923
|
-
cost={cost}
|
|
1924
|
-
field={field}
|
|
1925
|
-
/>
|
|
1926
|
-
</Elements>
|
|
1927
|
-
)
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
const StripeForm = ({ businessName, onSuccess, field, cost } : { businessName: string, onSuccess: () => void, field: FormField, cost: number }) => {
|
|
1931
|
-
const stripe = useStripe();
|
|
1932
|
-
const elements = useElements()
|
|
1933
|
-
|
|
1934
|
-
const [ready, setReady] = useState(false)
|
|
1935
|
-
const [errorMessage, setErrorMessage] = useState('');
|
|
1936
|
-
|
|
1937
|
-
const handleSubmit = async (event: any) => {
|
|
1938
|
-
// We don't want to let default form submission happen here,
|
|
1939
|
-
// which would refresh the page.
|
|
1940
|
-
event?.preventDefault();
|
|
1941
|
-
|
|
1942
|
-
if (!stripe || !elements) {
|
|
1943
|
-
// Stripe.js hasn't yet loaded.
|
|
1944
|
-
// Make sure to disable form submission until Stripe.js has loaded.
|
|
1945
|
-
return null;
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
const {error} = await (field.options?.chargeImmediately ? stripe.confirmPayment : stripe.confirmSetup)({
|
|
1949
|
-
//`Elements` instance that was used to create the Payment Element
|
|
1950
|
-
elements,
|
|
1951
|
-
confirmParams: {
|
|
1952
|
-
return_url: window.location.href,
|
|
1953
|
-
},
|
|
1954
|
-
redirect: 'if_required', // ensures the redirect url won't be used, unless the Bank redirect payment type is enabled (it's not, just card)
|
|
1955
|
-
});
|
|
1956
|
-
|
|
1957
|
-
if (error) {
|
|
1958
|
-
// This point will only be reached if there is an immediate error when
|
|
1959
|
-
// confirming the payment. Show error to your customer (for example, payment
|
|
1960
|
-
// details incomplete)
|
|
1961
|
-
setErrorMessage(error?.message ?? '');
|
|
1962
|
-
} else {
|
|
1963
|
-
onSuccess()
|
|
1964
|
-
// Your customer will be redirected to your `return_url`. For some payment
|
|
1965
|
-
// methods like iDEAL, your customer will be redirected to an intermediate
|
|
1966
|
-
// site first to authorize the payment, then redirected to the `return_url`.
|
|
1967
|
-
}
|
|
1968
|
-
};
|
|
1969
|
-
|
|
1970
|
-
return (
|
|
1971
|
-
<form onSubmit={handleSubmit}>
|
|
1972
|
-
<PaymentElement onReady={() => setReady(true)}
|
|
1973
|
-
options={{
|
|
1974
|
-
business: { name: businessName },
|
|
1975
|
-
}}
|
|
1976
|
-
/>
|
|
1977
|
-
<Button variant="contained" color="primary" type="submit" sx={{ mt: 1 }}
|
|
1978
|
-
disabled={!(stripe && ready)}
|
|
1979
|
-
>
|
|
1980
|
-
{field.options?.chargeImmediately ? 'Make Payment' : 'Save Payment Details'}
|
|
1981
|
-
</Button>
|
|
1982
|
-
|
|
1983
|
-
{cost > 0 &&
|
|
1984
|
-
<Typography sx={{ mt: 0.5 }}>
|
|
1985
|
-
{
|
|
1986
|
-
field.options?.customPriceMessage
|
|
1987
|
-
? field.options.customPriceMessage.replaceAll('{{PRICE}}', `$${(cost / 100).toFixed(2)}`)
|
|
1988
|
-
: `You will be charged $${(cost / 100).toFixed(2)} ${field.options?.chargeImmediately ? '' : 'on form submission'}`
|
|
1989
|
-
}
|
|
1990
|
-
</Typography>
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
{/* Show error message to your customers */}
|
|
1994
|
-
{errorMessage &&
|
|
1995
|
-
<Typography color="error" sx={{ mt: 0.5 }}>
|
|
1996
|
-
{errorMessage}
|
|
1997
|
-
</Typography>
|
|
1998
|
-
}
|
|
1999
|
-
</form>
|
|
2000
|
-
)
|
|
2001
|
-
}
|
|
1668
|
+
// StripeInput is shared between v1 and v2 forms
|
|
1669
|
+
// Both versions use the same implementation from inputs.tsx to ensure consistent behavior
|
|
1670
|
+
// and avoid code duplication. Re-exporting here maintains the pattern where forms.v2.tsx
|
|
1671
|
+
// only imports from inputs.v2.tsx
|
|
1672
|
+
export { StripeInput } from './inputs'
|
|
2002
1673
|
|
|
2003
1674
|
export const Progress = ({ numerator, denominator, style, color } : { numerator: number, denominator: number, color?: string } & Styled) => (
|
|
2004
1675
|
<Box sx={{ display: 'flex', alignItems: 'center', ...style }}>
|
|
@@ -3107,7 +2778,7 @@ export const contact_is_valid = (e: Partial<Enduser>) => {
|
|
|
3107
2778
|
}
|
|
3108
2779
|
}
|
|
3109
2780
|
|
|
3110
|
-
export const RelatedContactsInput = ({ field, value: _value, onChange, ...props }: FormInputProps<'Related Contacts'>) => {
|
|
2781
|
+
export const RelatedContactsInput = ({ field, value: _value, onChange, error: parentError, ...props }: FormInputProps<'Related Contacts'>) => {
|
|
3111
2782
|
// safeguard against any rogue values like empty string
|
|
3112
2783
|
const value = Array.isArray(_value) ? _value : []
|
|
3113
2784
|
|
|
@@ -3182,7 +2853,7 @@ export const RelatedContactsInput = ({ field, value: _value, onChange, ...props
|
|
|
3182
2853
|
<Grid item xs={4}>
|
|
3183
2854
|
<TextField label="Phone Number" size="small" fullWidth
|
|
3184
2855
|
InputProps={defaultInputProps}
|
|
3185
|
-
value={phone} onChange={e => onChange(value.map((v, i) => i === editing ? { ...v, phone: e.target.value } : v), field.id)}
|
|
2856
|
+
value={phone} onChange={e => onChange(value.map((v, i) => i === editing ? { ...v, phone: e.target.value.trim() } : v), field.id)}
|
|
3186
2857
|
/>
|
|
3187
2858
|
</Grid>
|
|
3188
2859
|
}
|
|
@@ -3236,7 +2907,7 @@ export const RelatedContactsInput = ({ field, value: _value, onChange, ...props
|
|
|
3236
2907
|
}
|
|
3237
2908
|
|
|
3238
2909
|
<Grid item sx={{ my: 0.75 }}>
|
|
3239
|
-
<Button variant="outlined" onClick={() => setEditing(-1)} size="small">
|
|
2910
|
+
<Button variant="outlined" onClick={() => setEditing(-1)} size="small" disabled={!!errorMessage || !!parentError}>
|
|
3240
2911
|
Save Contact
|
|
3241
2912
|
</Button>
|
|
3242
2913
|
</Grid>
|