@tellescope/react-components 1.230.2 → 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 +2 -2
- package/lib/cjs/Forms/forms.js.map +1 -1
- package/lib/cjs/Forms/forms.v2.js +4 -4
- package/lib/cjs/Forms/forms.v2.js.map +1 -1
- package/lib/cjs/Forms/hooks.d.ts +111 -3
- package/lib/cjs/Forms/hooks.d.ts.map +1 -1
- package/lib/cjs/Forms/hooks.js +30 -38
- package/lib/cjs/Forms/hooks.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 +13 -242
- package/lib/cjs/Forms/inputs.v2.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 +1 -0
- package/lib/esm/Forms/form_responses.d.ts.map +1 -1
- package/lib/esm/Forms/forms.d.ts +3 -3
- package/lib/esm/Forms/forms.js +2 -2
- package/lib/esm/Forms/forms.js.map +1 -1
- package/lib/esm/Forms/forms.v2.d.ts +3 -3
- package/lib/esm/Forms/forms.v2.js +4 -4
- package/lib/esm/Forms/forms.v2.js.map +1 -1
- package/lib/esm/Forms/hooks.d.ts +112 -3
- package/lib/esm/Forms/hooks.d.ts.map +1 -1
- package/lib/esm/Forms/hooks.js +31 -39
- 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 +72 -19
- package/lib/esm/Forms/inputs.js.map +1 -1
- package/lib/esm/Forms/inputs.v2.d.ts +3 -5
- package/lib/esm/Forms/inputs.v2.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.v2.js +15 -244
- package/lib/esm/Forms/inputs.v2.js.map +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 +315 -315
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/Forms/forms.tsx +2 -2
- package/src/Forms/forms.v2.tsx +12 -12
- package/src/Forms/hooks.tsx +49 -62
- package/src/Forms/inputs.tsx +73 -6
- package/src/Forms/inputs.v2.tsx +23 -404
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,
|
|
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,9 +21,7 @@ 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 {
|
|
25
|
-
import { loadStripe } from '@stripe/stripe-js';
|
|
26
|
-
import { CheckCircleOutline, Delete, Edit, ExpandMore, UploadFile } from "@mui/icons-material"
|
|
24
|
+
import { CheckCircleOutline, Delete, Edit, UploadFile } from "@mui/icons-material"
|
|
27
25
|
import { WYSIWYG } from "./wysiwyg"
|
|
28
26
|
|
|
29
27
|
export const LanguageSelect = ({ value, ...props }: { value: string, onChange: (s: string) => void}) => (
|
|
@@ -1511,7 +1509,6 @@ export const FilesInput = ({ value, onChange, field, existingFileName, uploading
|
|
|
1511
1509
|
export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: FormInputProps<'multiple_choice'>) => {
|
|
1512
1510
|
const value = typeof _value === 'string' ? [_value] : _value // if loading existingResponses, allows them to be a string
|
|
1513
1511
|
const { choices, radio, other, optionDetails } = field.options as MultipleChoiceOptions
|
|
1514
|
-
const [expandedDescriptions, setExpandedDescriptions] = useState<Record<number, boolean>>({})
|
|
1515
1512
|
|
|
1516
1513
|
// current other string
|
|
1517
1514
|
const enteringOtherStringRef = React.useRef('') // if typing otherString as prefix of a checkbox value, don't auto-select
|
|
@@ -1524,13 +1521,6 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
|
|
|
1524
1521
|
return optionDetails?.find(detail => detail.option === choice)?.description
|
|
1525
1522
|
}, [optionDetails])
|
|
1526
1523
|
|
|
1527
|
-
const toggleDescription = useCallback((index: number) => {
|
|
1528
|
-
setExpandedDescriptions(prev => ({
|
|
1529
|
-
...prev,
|
|
1530
|
-
[index]: !prev[index]
|
|
1531
|
-
}))
|
|
1532
|
-
}, [])
|
|
1533
|
-
|
|
1534
1524
|
return (
|
|
1535
1525
|
<Grid container alignItems="center" rowGap={1.5}>
|
|
1536
1526
|
{radio
|
|
@@ -1544,7 +1534,6 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
|
|
|
1544
1534
|
{(choices ?? []).map((c, i) => {
|
|
1545
1535
|
const description = getDescriptionForChoice(c)
|
|
1546
1536
|
const hasDescription = !!description
|
|
1547
|
-
const isExpanded = expandedDescriptions[i]
|
|
1548
1537
|
const isSelected = !!value?.includes(c) && c !== otherString
|
|
1549
1538
|
|
|
1550
1539
|
return (
|
|
@@ -1558,7 +1547,7 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
|
|
|
1558
1547
|
borderColor: 'primary.main',
|
|
1559
1548
|
borderRadius: 1,
|
|
1560
1549
|
padding: '16px 16px',
|
|
1561
|
-
marginBottom: '12px',
|
|
1550
|
+
marginBottom: hasDescription ? '8px' : '12px',
|
|
1562
1551
|
cursor: 'pointer',
|
|
1563
1552
|
backgroundColor: 'transparent',
|
|
1564
1553
|
boxSizing: 'border-box',
|
|
@@ -1569,32 +1558,13 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
|
|
|
1569
1558
|
onClick={() => onChange(value?.includes(c) ? [] : [c], field.id)}
|
|
1570
1559
|
>
|
|
1571
1560
|
<Typography component="span" sx={{ flex: 1, color: 'primary.main', fontSize: 13, fontWeight: 600 }}>{c}</Typography>
|
|
1572
|
-
{hasDescription && (
|
|
1573
|
-
<MuiIconButton
|
|
1574
|
-
className="expand-button"
|
|
1575
|
-
size="small"
|
|
1576
|
-
onClick={(e: React.MouseEvent) => {
|
|
1577
|
-
e.stopPropagation()
|
|
1578
|
-
toggleDescription(i)
|
|
1579
|
-
}}
|
|
1580
|
-
sx={{
|
|
1581
|
-
transform: isExpanded ? 'rotate(180deg)' : 'rotate(0deg)',
|
|
1582
|
-
transition: 'transform 0.2s',
|
|
1583
|
-
ml: 1
|
|
1584
|
-
}}
|
|
1585
|
-
>
|
|
1586
|
-
<ExpandMore fontSize="small" />
|
|
1587
|
-
</MuiIconButton>
|
|
1588
|
-
)}
|
|
1589
1561
|
</Box>
|
|
1590
1562
|
{hasDescription && (
|
|
1591
|
-
<
|
|
1592
|
-
<
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
</Box>
|
|
1597
|
-
</Collapse>
|
|
1563
|
+
<Box sx={{ pl: 2, pr: 2, pb: 1, mb: 1 }}>
|
|
1564
|
+
<Typography style={{ fontSize: 14, color: '#00000099' }}>
|
|
1565
|
+
{description}
|
|
1566
|
+
</Typography>
|
|
1567
|
+
</Box>
|
|
1598
1568
|
)}
|
|
1599
1569
|
</Box>
|
|
1600
1570
|
)
|
|
@@ -1605,7 +1575,6 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
|
|
|
1605
1575
|
(choices ?? []).map((c, i) => {
|
|
1606
1576
|
const description = getDescriptionForChoice(c)
|
|
1607
1577
|
const hasDescription = !!description
|
|
1608
|
-
const isExpanded = expandedDescriptions[i]
|
|
1609
1578
|
|
|
1610
1579
|
return (
|
|
1611
1580
|
<Grid xs={12} key={i}>
|
|
@@ -1619,10 +1588,6 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
|
|
|
1619
1588
|
boxSizing: 'border-box'
|
|
1620
1589
|
}}
|
|
1621
1590
|
onClick={(e) => {
|
|
1622
|
-
// Don't trigger selection if clicking on the expand button
|
|
1623
|
-
if ((e.target as HTMLElement).closest('.expand-button')) {
|
|
1624
|
-
return
|
|
1625
|
-
}
|
|
1626
1591
|
onChange(
|
|
1627
1592
|
(
|
|
1628
1593
|
value?.includes(c)
|
|
@@ -1647,32 +1612,13 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
|
|
|
1647
1612
|
inputProps={{ 'aria-label': 'primary checkbox' }}
|
|
1648
1613
|
/>
|
|
1649
1614
|
<Typography component="span" sx={{ flex: 1 }}>{c}</Typography>
|
|
1650
|
-
{hasDescription && (
|
|
1651
|
-
<MuiIconButton
|
|
1652
|
-
className="expand-button"
|
|
1653
|
-
size="small"
|
|
1654
|
-
onClick={(e: React.MouseEvent) => {
|
|
1655
|
-
e.stopPropagation()
|
|
1656
|
-
toggleDescription(i)
|
|
1657
|
-
}}
|
|
1658
|
-
sx={{
|
|
1659
|
-
transform: isExpanded ? 'rotate(180deg)' : 'rotate(0deg)',
|
|
1660
|
-
transition: 'transform 0.2s',
|
|
1661
|
-
ml: 1
|
|
1662
|
-
}}
|
|
1663
|
-
>
|
|
1664
|
-
<ExpandMore fontSize="small" />
|
|
1665
|
-
</MuiIconButton>
|
|
1666
|
-
)}
|
|
1667
1615
|
</Box>
|
|
1668
1616
|
{hasDescription && (
|
|
1669
|
-
<
|
|
1670
|
-
<
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
</Box>
|
|
1675
|
-
</Collapse>
|
|
1617
|
+
<Box sx={{ pl: '42px', pr: 2, pb: 1 }}>
|
|
1618
|
+
<Typography style={{ fontSize: 14, color: '#00000099' }}>
|
|
1619
|
+
{description}
|
|
1620
|
+
</Typography>
|
|
1621
|
+
</Box>
|
|
1676
1622
|
)}
|
|
1677
1623
|
</Box>
|
|
1678
1624
|
</Grid>
|
|
@@ -1718,339 +1664,12 @@ export const MultipleChoiceInput = ({ field, form, value: _value, onChange }: Fo
|
|
|
1718
1664
|
)
|
|
1719
1665
|
}
|
|
1720
1666
|
|
|
1721
|
-
export const StripeInput = ({ field, value, onChange, setCustomerId, enduserId }: FormInputProps<'Stripe'> & {
|
|
1722
|
-
setCustomerId: React.Dispatch<React.SetStateAction<string | undefined>>,
|
|
1723
|
-
}) => {
|
|
1724
|
-
const session = useResolvedSession()
|
|
1725
|
-
const [clientSecret, setClientSecret] = useState('')
|
|
1726
|
-
const [businessName, setBusinessName] = useState('')
|
|
1727
|
-
const [isCheckout, setIsCheckout] = useState(false)
|
|
1728
|
-
const [stripePromise, setStripePromise] = useState<ReturnType<typeof loadStripe>>()
|
|
1729
|
-
const [answertext, setAnswertext] = useState('')
|
|
1730
|
-
const [error, setError] = useState('')
|
|
1731
|
-
const [selectedProducts, setSelectedProducts] = useState<string[]>([])
|
|
1732
|
-
const [showProductSelection, setShowProductSelection] = useState(false)
|
|
1733
|
-
const [availableProducts, setAvailableProducts] = useState<any[]>([])
|
|
1734
|
-
const [loadingProducts, setLoadingProducts] = useState(false)
|
|
1735
|
-
|
|
1736
|
-
const fetchRef = useRef(false)
|
|
1737
|
-
useEffect(() => {
|
|
1738
|
-
if (fetchRef.current) return
|
|
1739
|
-
if (value && (session.userInfo as any)?.stripeCustomerId) {
|
|
1740
|
-
return setCustomerId(c => c ? c : (session.userInfo as any)?.stripeCustomerId) // already paid or saved card
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
// Check if product selection mode is enabled
|
|
1744
|
-
if (field.options?.stripeProductSelectionMode && (field.options?.productIds || []).length > 1) {
|
|
1745
|
-
setShowProductSelection(true)
|
|
1746
|
-
setLoadingProducts(true)
|
|
1747
|
-
|
|
1748
|
-
// Fetch product data with real-time Stripe pricing via proxy_read
|
|
1749
|
-
const productIds = (field.options.productIds || []).join(',')
|
|
1750
|
-
session.api.integrations.proxy_read({
|
|
1751
|
-
integration: 'Stripe',
|
|
1752
|
-
type: 'product-prices',
|
|
1753
|
-
id: productIds,
|
|
1754
|
-
query: field.options.stripeKey
|
|
1755
|
-
})
|
|
1756
|
-
.then(({ data }) => {
|
|
1757
|
-
setAvailableProducts(data.products || [])
|
|
1758
|
-
setLoadingProducts(false)
|
|
1759
|
-
})
|
|
1760
|
-
.catch((e: any) => {
|
|
1761
|
-
console.error('Error loading product data:', e)
|
|
1762
|
-
const errorMessage = e?.message?.includes?.('Stripe pricing error:')
|
|
1763
|
-
? e.message.replace('Stripe pricing error: ', '')
|
|
1764
|
-
: 'Failed to load product information from Stripe'
|
|
1765
|
-
setError(`Product configuration error: ${errorMessage}`)
|
|
1766
|
-
setLoadingProducts(false)
|
|
1767
|
-
})
|
|
1768
|
-
return
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
fetchRef.current = true
|
|
1772
|
-
|
|
1773
|
-
session.api.form_responses.stripe_details({ fieldId: field.id, enduserId })
|
|
1774
|
-
.then(({ clientSecret, publishableKey, stripeAccount, businessName, customerId, isCheckout, answerText }) => {
|
|
1775
|
-
setAnswertext(answerText || '')
|
|
1776
|
-
setIsCheckout(!!isCheckout)
|
|
1777
|
-
setClientSecret(clientSecret)
|
|
1778
|
-
setStripePromise(loadStripe(publishableKey, { stripeAccount }))
|
|
1779
|
-
setBusinessName(businessName)
|
|
1780
|
-
setCustomerId(customerId)
|
|
1781
|
-
})
|
|
1782
|
-
.catch((e: any) => {
|
|
1783
|
-
console.error(e)
|
|
1784
|
-
if (typeof e?.message === 'string') {
|
|
1785
|
-
setError(e.message)
|
|
1786
|
-
}
|
|
1787
|
-
})
|
|
1788
|
-
}, [session, value, field.id, enduserId])
|
|
1789
|
-
|
|
1790
|
-
const cost = (
|
|
1791
|
-
showProductSelection
|
|
1792
|
-
? selectedProducts.reduce((total, productId) => {
|
|
1793
|
-
const product = availableProducts.find(p => p._id === productId)
|
|
1794
|
-
if (product?.currentPrice) {
|
|
1795
|
-
return total + (product.currentPrice.amount || 0)
|
|
1796
|
-
}
|
|
1797
|
-
return total + (product?.cost?.amount || 0)
|
|
1798
|
-
}, 0)
|
|
1799
|
-
: 0 // Will be calculated by existing Stripe flow when not in selection mode
|
|
1800
|
-
)
|
|
1801
|
-
|
|
1802
|
-
// Handle product selection step
|
|
1803
|
-
if (showProductSelection) {
|
|
1804
|
-
if (error) {
|
|
1805
|
-
return (
|
|
1806
|
-
<Grid container direction="column" spacing={2} alignItems="center">
|
|
1807
|
-
<Grid item>
|
|
1808
|
-
<Typography color="error" variant="h6">
|
|
1809
|
-
Product Configuration Error
|
|
1810
|
-
</Typography>
|
|
1811
|
-
</Grid>
|
|
1812
|
-
<Grid item>
|
|
1813
|
-
<Typography color="error" sx={{ textAlign: 'center' }}>
|
|
1814
|
-
{error}
|
|
1815
|
-
</Typography>
|
|
1816
|
-
</Grid>
|
|
1817
|
-
</Grid>
|
|
1818
|
-
)
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
if (loadingProducts) {
|
|
1822
|
-
return (
|
|
1823
|
-
<Grid container direction="column" spacing={2} alignItems="center">
|
|
1824
|
-
<Grid item>
|
|
1825
|
-
<LinearProgress />
|
|
1826
|
-
</Grid>
|
|
1827
|
-
<Grid item>
|
|
1828
|
-
<Typography>Loading product information...</Typography>
|
|
1829
|
-
</Grid>
|
|
1830
|
-
</Grid>
|
|
1831
|
-
)
|
|
1832
|
-
}
|
|
1833
|
-
const isSingleSelection = field.options?.radio === true
|
|
1834
|
-
|
|
1835
|
-
const handleProductSelection = (productId: string) => {
|
|
1836
|
-
if (isSingleSelection) {
|
|
1837
|
-
setSelectedProducts([productId])
|
|
1838
|
-
} else {
|
|
1839
|
-
setSelectedProducts(prev =>
|
|
1840
|
-
prev.includes(productId)
|
|
1841
|
-
? prev.filter(id => id !== productId)
|
|
1842
|
-
: [...prev, productId]
|
|
1843
|
-
)
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
const handleContinueToPayment = () => {
|
|
1848
|
-
if (selectedProducts.length === 0) return
|
|
1849
|
-
setShowProductSelection(false)
|
|
1850
|
-
fetchRef.current = true
|
|
1851
|
-
|
|
1852
|
-
// Now fetch Stripe details with selected products
|
|
1853
|
-
session.api.form_responses.stripe_details({
|
|
1854
|
-
fieldId: field.id,
|
|
1855
|
-
enduserId,
|
|
1856
|
-
...(selectedProducts.length > 0 && { selectedProductIds: selectedProducts }) // Pass selected products to Stripe checkout
|
|
1857
|
-
} as any)
|
|
1858
|
-
.then(({ clientSecret, publishableKey, stripeAccount, businessName, customerId, isCheckout, answerText }) => {
|
|
1859
|
-
setAnswertext(answerText || '')
|
|
1860
|
-
setIsCheckout(!!isCheckout)
|
|
1861
|
-
setClientSecret(clientSecret)
|
|
1862
|
-
setStripePromise(loadStripe(publishableKey, { stripeAccount }))
|
|
1863
|
-
setBusinessName(businessName)
|
|
1864
|
-
setCustomerId(customerId)
|
|
1865
|
-
})
|
|
1866
|
-
.catch((e: any) => {
|
|
1867
|
-
console.error(e)
|
|
1868
|
-
if (typeof e?.message === 'string') {
|
|
1869
|
-
setError(e.message)
|
|
1870
|
-
}
|
|
1871
|
-
})
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
return (
|
|
1875
|
-
<Grid container direction="column" spacing={2}>
|
|
1876
|
-
<Grid item>
|
|
1877
|
-
<Typography variant="h6">Select Product{isSingleSelection ? '' : 's'}</Typography>
|
|
1878
|
-
</Grid>
|
|
1879
|
-
|
|
1880
|
-
{availableProducts.map((product) => {
|
|
1881
|
-
// Use real-time Stripe pricing if available, fallback to Tellescope pricing
|
|
1882
|
-
const price = product.currentPrice || product.cost
|
|
1883
|
-
const priceAmount = price?.amount || 0
|
|
1884
|
-
const priceCurrency = price?.currency || 'USD'
|
|
1885
|
-
|
|
1886
|
-
return (
|
|
1887
|
-
<Grid item key={product._id}>
|
|
1888
|
-
<FormControlLabel
|
|
1889
|
-
control={
|
|
1890
|
-
isSingleSelection ? (
|
|
1891
|
-
<Radio
|
|
1892
|
-
checked={selectedProducts.includes(product._id)}
|
|
1893
|
-
onChange={() => handleProductSelection(product._id)}
|
|
1894
|
-
/>
|
|
1895
|
-
) : (
|
|
1896
|
-
<Checkbox
|
|
1897
|
-
checked={selectedProducts.includes(product._id)}
|
|
1898
|
-
onChange={() => handleProductSelection(product._id)}
|
|
1899
|
-
/>
|
|
1900
|
-
)
|
|
1901
|
-
}
|
|
1902
|
-
label={
|
|
1903
|
-
<Box>
|
|
1904
|
-
<Typography variant="body1" fontWeight="bold">
|
|
1905
|
-
{product.title}
|
|
1906
|
-
</Typography>
|
|
1907
|
-
{product.description && (
|
|
1908
|
-
<Typography variant="body2" color="textSecondary">
|
|
1909
|
-
{product.description}
|
|
1910
|
-
</Typography>
|
|
1911
|
-
)}
|
|
1912
|
-
<Typography variant="body2" color="primary">
|
|
1913
|
-
${(priceAmount / 100).toFixed(2)} {priceCurrency.toUpperCase()}
|
|
1914
|
-
{product.currentPrice?.isSubscription && (
|
|
1915
|
-
<Typography component="span" variant="caption" sx={{ ml: 0.5 }}>
|
|
1916
|
-
{format_stripe_subscription_interval(product.currentPrice?.interval, product.currentPrice?.interval_count)}
|
|
1917
|
-
</Typography>
|
|
1918
|
-
)}
|
|
1919
|
-
</Typography>
|
|
1920
|
-
</Box>
|
|
1921
|
-
}
|
|
1922
|
-
/>
|
|
1923
|
-
</Grid>
|
|
1924
|
-
)
|
|
1925
|
-
})}
|
|
1926
|
-
|
|
1927
|
-
<Grid item>
|
|
1928
|
-
<Button
|
|
1929
|
-
variant="contained"
|
|
1930
|
-
onClick={handleContinueToPayment}
|
|
1931
|
-
disabled={selectedProducts.length === 0}
|
|
1932
|
-
sx={{ mt: 2 }}
|
|
1933
|
-
>
|
|
1934
|
-
Continue to Payment
|
|
1935
|
-
</Button>
|
|
1936
|
-
</Grid>
|
|
1937
|
-
</Grid>
|
|
1938
|
-
)
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
if (error) {
|
|
1942
|
-
return (
|
|
1943
|
-
<Typography color="error">
|
|
1944
|
-
{error}
|
|
1945
|
-
</Typography>
|
|
1946
|
-
)
|
|
1947
|
-
}
|
|
1948
|
-
if (value) {
|
|
1949
|
-
return (
|
|
1950
|
-
<Grid container alignItems="center" wrap="nowrap">
|
|
1951
|
-
<CheckCircleOutline color="success" />
|
|
1952
|
-
|
|
1953
|
-
<Typography sx={{ ml: 1, fontSize: 20 }}>
|
|
1954
|
-
{field.options?.chargeImmediately ? 'Your purchase was successful' : "Your payment details have been saved!"}
|
|
1955
|
-
</Typography>
|
|
1956
|
-
</Grid>
|
|
1957
|
-
)
|
|
1958
|
-
}
|
|
1959
|
-
if (!(clientSecret && stripePromise)) return <LinearProgress />
|
|
1960
|
-
if (isCheckout && stripePromise) return (
|
|
1961
|
-
<EmbeddedCheckoutProvider stripe={stripePromise}
|
|
1962
|
-
options={{
|
|
1963
|
-
clientSecret,
|
|
1964
|
-
onComplete: () => onChange(answertext || 'Completed checkout', field.id),
|
|
1965
|
-
}}
|
|
1966
|
-
>
|
|
1967
|
-
<EmbeddedCheckout />
|
|
1968
|
-
</EmbeddedCheckoutProvider>
|
|
1969
|
-
)
|
|
1970
|
-
return (
|
|
1971
|
-
<Elements stripe={stripePromise} options={{
|
|
1972
|
-
clientSecret,
|
|
1973
|
-
}}>
|
|
1974
|
-
<StripeForm businessName={businessName} onSuccess={() => onChange(answertext || 'Saved card details', field.id)}
|
|
1975
|
-
cost={cost}
|
|
1976
|
-
field={field}
|
|
1977
|
-
/>
|
|
1978
|
-
</Elements>
|
|
1979
|
-
)
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
const StripeForm = ({ businessName, onSuccess, field, cost } : { businessName: string, onSuccess: () => void, field: FormField, cost: number }) => {
|
|
1983
|
-
const stripe = useStripe();
|
|
1984
|
-
const elements = useElements()
|
|
1985
|
-
|
|
1986
|
-
const [ready, setReady] = useState(false)
|
|
1987
|
-
const [errorMessage, setErrorMessage] = useState('');
|
|
1988
1667
|
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
if (!stripe || !elements) {
|
|
1995
|
-
// Stripe.js hasn't yet loaded.
|
|
1996
|
-
// Make sure to disable form submission until Stripe.js has loaded.
|
|
1997
|
-
return null;
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
const {error} = await (field.options?.chargeImmediately ? stripe.confirmPayment : stripe.confirmSetup)({
|
|
2001
|
-
//`Elements` instance that was used to create the Payment Element
|
|
2002
|
-
elements,
|
|
2003
|
-
confirmParams: {
|
|
2004
|
-
return_url: window.location.href,
|
|
2005
|
-
},
|
|
2006
|
-
redirect: 'if_required', // ensures the redirect url won't be used, unless the Bank redirect payment type is enabled (it's not, just card)
|
|
2007
|
-
});
|
|
2008
|
-
|
|
2009
|
-
if (error) {
|
|
2010
|
-
// This point will only be reached if there is an immediate error when
|
|
2011
|
-
// confirming the payment. Show error to your customer (for example, payment
|
|
2012
|
-
// details incomplete)
|
|
2013
|
-
setErrorMessage(error?.message ?? '');
|
|
2014
|
-
} else {
|
|
2015
|
-
onSuccess()
|
|
2016
|
-
// Your customer will be redirected to your `return_url`. For some payment
|
|
2017
|
-
// methods like iDEAL, your customer will be redirected to an intermediate
|
|
2018
|
-
// site first to authorize the payment, then redirected to the `return_url`.
|
|
2019
|
-
}
|
|
2020
|
-
};
|
|
2021
|
-
|
|
2022
|
-
return (
|
|
2023
|
-
<form onSubmit={handleSubmit}>
|
|
2024
|
-
<PaymentElement onReady={() => setReady(true)}
|
|
2025
|
-
options={{
|
|
2026
|
-
business: { name: businessName },
|
|
2027
|
-
}}
|
|
2028
|
-
/>
|
|
2029
|
-
<Button variant="contained" color="primary" type="submit" sx={{ mt: 1 }}
|
|
2030
|
-
disabled={!(stripe && ready)}
|
|
2031
|
-
>
|
|
2032
|
-
{field.options?.chargeImmediately ? 'Make Payment' : 'Save Payment Details'}
|
|
2033
|
-
</Button>
|
|
2034
|
-
|
|
2035
|
-
{cost > 0 &&
|
|
2036
|
-
<Typography sx={{ mt: 0.5 }}>
|
|
2037
|
-
{
|
|
2038
|
-
field.options?.customPriceMessage
|
|
2039
|
-
? field.options.customPriceMessage.replaceAll('{{PRICE}}', `$${(cost / 100).toFixed(2)}`)
|
|
2040
|
-
: `You will be charged $${(cost / 100).toFixed(2)} ${field.options?.chargeImmediately ? '' : 'on form submission'}`
|
|
2041
|
-
}
|
|
2042
|
-
</Typography>
|
|
2043
|
-
}
|
|
2044
|
-
|
|
2045
|
-
{/* Show error message to your customers */}
|
|
2046
|
-
{errorMessage &&
|
|
2047
|
-
<Typography color="error" sx={{ mt: 0.5 }}>
|
|
2048
|
-
{errorMessage}
|
|
2049
|
-
</Typography>
|
|
2050
|
-
}
|
|
2051
|
-
</form>
|
|
2052
|
-
)
|
|
2053
|
-
}
|
|
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'
|
|
2054
1673
|
|
|
2055
1674
|
export const Progress = ({ numerator, denominator, style, color } : { numerator: number, denominator: number, color?: string } & Styled) => (
|
|
2056
1675
|
<Box sx={{ display: 'flex', alignItems: 'center', ...style }}>
|
|
@@ -3159,7 +2778,7 @@ export const contact_is_valid = (e: Partial<Enduser>) => {
|
|
|
3159
2778
|
}
|
|
3160
2779
|
}
|
|
3161
2780
|
|
|
3162
|
-
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'>) => {
|
|
3163
2782
|
// safeguard against any rogue values like empty string
|
|
3164
2783
|
const value = Array.isArray(_value) ? _value : []
|
|
3165
2784
|
|
|
@@ -3234,7 +2853,7 @@ export const RelatedContactsInput = ({ field, value: _value, onChange, ...props
|
|
|
3234
2853
|
<Grid item xs={4}>
|
|
3235
2854
|
<TextField label="Phone Number" size="small" fullWidth
|
|
3236
2855
|
InputProps={defaultInputProps}
|
|
3237
|
-
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)}
|
|
3238
2857
|
/>
|
|
3239
2858
|
</Grid>
|
|
3240
2859
|
}
|
|
@@ -3288,7 +2907,7 @@ export const RelatedContactsInput = ({ field, value: _value, onChange, ...props
|
|
|
3288
2907
|
}
|
|
3289
2908
|
|
|
3290
2909
|
<Grid item sx={{ my: 0.75 }}>
|
|
3291
|
-
<Button variant="outlined" onClick={() => setEditing(-1)} size="small">
|
|
2910
|
+
<Button variant="outlined" onClick={() => setEditing(-1)} size="small" disabled={!!errorMessage || !!parentError}>
|
|
3292
2911
|
Save Contact
|
|
3293
2912
|
</Button>
|
|
3294
2913
|
</Grid>
|