diff --git a/packages/extension-chakra-storefront/jest.config.js b/packages/extension-chakra-storefront/jest.config.js
index a25684983a..26ca5f1028 100644
--- a/packages/extension-chakra-storefront/jest.config.js
+++ b/packages/extension-chakra-storefront/jest.config.js
@@ -94,6 +94,7 @@ module.exports = {
},
setupFilesAfterEnv: [path.join(__dirname, 'jest-setup.js')],
collectCoverageFrom: [
+ 'src/**/*.{js,jsx}',
'app/**/*.{js,jsx}',
'non-pwa/**/*.{js,jsx}',
'worker/**/*.{js,jsx}',
diff --git a/packages/extension-chakra-storefront/src/components/field/index.jsx b/packages/extension-chakra-storefront/src/components/field/index.jsx
index f7c87893b0..5ed9e7a350 100644
--- a/packages/extension-chakra-storefront/src/components/field/index.jsx
+++ b/packages/extension-chakra-storefront/src/components/field/index.jsx
@@ -76,7 +76,9 @@ const Field = ({
>
- ) : undefined
+ ) : (
+ _inputProps?.endElement
+ )
}
>
<>
diff --git a/packages/extension-chakra-storefront/src/components/field/index.test.js b/packages/extension-chakra-storefront/src/components/field/index.test.js
index 8b32870a73..54506211c3 100644
--- a/packages/extension-chakra-storefront/src/components/field/index.test.js
+++ b/packages/extension-chakra-storefront/src/components/field/index.test.js
@@ -69,3 +69,64 @@ test('renders Field component without ref and works correctly', () => {
fireEvent.change(emailInput, {target: {value: 'testuser@example.com'}})
expect(emailInput.value).toBe('testuser@example.com')
})
+
+test('renders Field component with endElement', () => {
+ const EndElementComponent = () => USD
+
+ renderWithProviders(
+
+ {({control}) => (
+
+ }}
+ />
+ )}
+
+ )
+
+ const amountInput = screen.getByPlaceholderText('Enter amount')
+ const endElement = screen.getByTestId('end-element')
+
+ expect(amountInput).toBeInTheDocument()
+ expect(endElement).toBeInTheDocument()
+ expect(endElement).toHaveTextContent('USD')
+})
+
+test('renders Field component with password type shows password toggle endElement', () => {
+ renderWithProviders(
+
+ {({control}) => (
+
+ )}
+
+ )
+
+ const passwordInput = screen.getByPlaceholderText('Enter your password')
+ const toggleButton = screen.getByLabelText('Show password')
+
+ expect(passwordInput).toBeInTheDocument()
+ expect(passwordInput).toHaveAttribute('type', 'password')
+ expect(toggleButton).toBeInTheDocument()
+
+ // Click toggle to show password
+ fireEvent.click(toggleButton)
+ expect(passwordInput).toHaveAttribute('type', 'text')
+ expect(screen.getByLabelText('Hide password')).toBeInTheDocument()
+
+ // Click toggle again to hide password
+ fireEvent.click(screen.getByLabelText('Hide password'))
+ expect(passwordInput).toHaveAttribute('type', 'password')
+ expect(screen.getByLabelText('Show password')).toBeInTheDocument()
+})
diff --git a/packages/extension-chakra-storefront/src/components/forms/credit-card-fields.jsx b/packages/extension-chakra-storefront/src/components/forms/credit-card-fields.jsx
index 2fc527dc0d..6b15544569 100644
--- a/packages/extension-chakra-storefront/src/components/forms/credit-card-fields.jsx
+++ b/packages/extension-chakra-storefront/src/components/forms/credit-card-fields.jsx
@@ -4,11 +4,12 @@
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
-import React, {useState} from 'react'
+import React from 'react'
import PropTypes from 'prop-types'
import ccValidator from 'card-validator'
import {useIntl} from 'react-intl'
-import {Box, Flex, FormLabel, InputRightElement, SimpleGrid, Stack, Tooltip} from '@chakra-ui/react'
+import {Box, Flex, SimpleGrid, Stack, Field as ChakraField} from '@chakra-ui/react'
+import Tooltip from '../../components/tooltip'
import {formatCreditCardNumber, getCreditCardIcon} from '../../utils/cc-utils'
import useCreditCardFields from '../../components/forms/useCreditCardFields'
import Field from '../../components/field'
@@ -16,7 +17,6 @@ import {AmexIcon, DiscoverIcon, MastercardIcon, VisaIcon, InfoIcon} from '../../
const CreditCardFields = ({form, prefix = ''}) => {
const {formatMessage} = useIntl()
- const [isTooltipOpen, setIsTooltipOpen] = useState(false)
const fields = useCreditCardFields({form, prefix})
// Rerender the fields when we `cardType` changes so the detected
@@ -42,31 +42,15 @@ const CreditCardFields = ({form, prefix = ''}) => {
description: 'Generic credit card security code help text'
})
- const handleTooltipClose = () => {
- setIsTooltipOpen(false)
- if (document) {
- document.removeEventListener('click', handleTooltipClose)
- document.removeEventListener('keydown', handleTooltipClose)
- }
- }
-
- const handleTooltipOpen = () => {
- setIsTooltipOpen(true)
- if (document) {
- document.addEventListener('click', handleTooltipClose)
- document.addEventListener('keydown', handleTooltipClose)
- }
- }
-
return (
-
+
- {fields.number.label}
-
+
+ {fields.number.label}
+
@@ -84,19 +68,17 @@ const CreditCardFields = ({form, prefix = ''}) => {
: number
form.setValue('cardType', card?.type || '')
return onChange(formattedNumber)
- }
+ },
+ endElement:
+ CardIcon && form.getValues().number?.length > 2 ? (
+
+ ) : undefined
})}
- >
- {CardIcon && form.getValues().number?.length > 2 && (
-
-
-
- )}
-
+ />
-
+
({
@@ -131,33 +113,31 @@ const CreditCardFields = ({form, prefix = ''}) => {
-
- {fields.securityCode.label}
-
-
+
+
+ {fields.securityCode.label}
-
+ >
+
+
-
- >
+
+
}
/>
diff --git a/packages/extension-chakra-storefront/src/components/tooltip/index.jsx b/packages/extension-chakra-storefront/src/components/tooltip/index.jsx
new file mode 100644
index 0000000000..bab917470c
--- /dev/null
+++ b/packages/extension-chakra-storefront/src/components/tooltip/index.jsx
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2025, salesforce.com, inc.
+ * All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
+ */
+import React from 'react'
+import PropTypes from 'prop-types'
+import {Tooltip as ChakraTooltip} from '@chakra-ui/react'
+
+const Tooltip = React.forwardRef(
+ (
+ {
+ children,
+ content,
+ placement = 'top',
+ showArrow = true,
+ disabled = false,
+ openDelay = 500,
+ closeDelay = 500,
+ contentProps,
+ positioning
+ },
+ ref
+ ) => {
+ if (disabled || !content) {
+ return children
+ }
+
+ const positioningConfig = positioning || {}
+ const finalPositioning = {
+ placement,
+ ...positioningConfig
+ }
+
+ return (
+
+
+ {children}
+
+
+
+ {showArrow && (
+
+
+
+ )}
+ {content}
+
+
+
+ )
+ }
+)
+
+Tooltip.displayName = 'Tooltip'
+
+Tooltip.propTypes = {
+ children: PropTypes.node.isRequired,
+ content: PropTypes.node,
+ placement: PropTypes.oneOf([
+ 'top',
+ 'top-start',
+ 'top-end',
+ 'bottom',
+ 'bottom-start',
+ 'bottom-end',
+ 'left',
+ 'left-start',
+ 'left-end',
+ 'right',
+ 'right-start',
+ 'right-end'
+ ]),
+ showArrow: PropTypes.bool,
+ disabled: PropTypes.bool,
+ openDelay: PropTypes.number,
+ closeDelay: PropTypes.number,
+ contentProps: PropTypes.object,
+ positioning: PropTypes.object
+}
+
+export default Tooltip
diff --git a/packages/extension-chakra-storefront/src/pages/checkout/index.jsx b/packages/extension-chakra-storefront/src/pages/checkout/index.jsx
index 0758aed3f7..c8767478a3 100644
--- a/packages/extension-chakra-storefront/src/pages/checkout/index.jsx
+++ b/packages/extension-chakra-storefront/src/pages/checkout/index.jsx
@@ -86,7 +86,7 @@ const Checkout = () => {
/>
- {/* TODO: bring this back */}
+
{step === 4 && (
diff --git a/packages/extension-chakra-storefront/src/pages/checkout/partials/payment-form.jsx b/packages/extension-chakra-storefront/src/pages/checkout/partials/payment-form.jsx
index 7d006c14bf..e806bf4640 100644
--- a/packages/extension-chakra-storefront/src/pages/checkout/partials/payment-form.jsx
+++ b/packages/extension-chakra-storefront/src/pages/checkout/partials/payment-form.jsx
@@ -7,7 +7,8 @@
import React from 'react'
import {FormattedMessage, FormattedNumber, useIntl} from 'react-intl'
import PropTypes from 'prop-types'
-import {Box, Flex, Radio, RadioGroup, Stack, Text, Tooltip} from '@chakra-ui/react'
+import {Box, Flex, RadioGroup, Stack, Text} from '@chakra-ui/react'
+import Tooltip from '../../../components/tooltip'
import {useCurrentBasket} from '../../../hooks/use-current-basket'
import {LockIcon, PaypalIcon} from '../../../components/icons'
import CreditCardFields from '../../../components/forms/credit-card-fields'
@@ -20,10 +21,10 @@ const PaymentForm = ({form, onSubmit}) => {
return (
diff --git a/packages/extension-chakra-storefront/src/pages/checkout/partials/payment.jsx b/packages/extension-chakra-storefront/src/pages/checkout/partials/payment.jsx
index 9970cfae43..28fe5daeb2 100644
--- a/packages/extension-chakra-storefront/src/pages/checkout/partials/payment.jsx
+++ b/packages/extension-chakra-storefront/src/pages/checkout/partials/payment.jsx
@@ -7,7 +7,7 @@
import React, {useState} from 'react'
import PropTypes from 'prop-types'
import {defineMessage, FormattedMessage, useIntl} from 'react-intl'
-import {Box, Button, Checkbox, Container, Heading, Stack, Text, Divider} from '@chakra-ui/react'
+import {Box, Button, Checkbox, Container, Heading, Stack, Text, Separator} from '@chakra-ui/react'
import {useForm} from 'react-hook-form'
import useToast from '../../../hooks/use-toast'
import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react'
@@ -29,8 +29,18 @@ const Payment = () => {
const {formatMessage} = useIntl()
const {data: basket} = useCurrentBasket()
const selectedShippingAddress = basket?.shipments && basket?.shipments[0]?.shippingAddress
- const selectedBillingAddress = basket?.billingAddress
- const appliedPayment = basket?.paymentInstruments && basket?.paymentInstruments[0]
+ //TODO: Change to const after deleting the test/mocked data
+ let selectedBillingAddress = basket?.billingAddress
+ let appliedPayment = basket?.paymentInstruments && basket?.paymentInstruments[0]
+
+ // TODO: Testing/Mock data - remove this section after shipping and billing address components are migrated
+ selectedBillingAddress = {
+ address1: '123 Test Street',
+ city: 'Test City',
+ stateCode: 'CA',
+ postalCode: '12345',
+ countryCode: 'US'
+ }
const [billingSameAsShipping, setBillingSameAsShipping] = useState(true) // By default, have billing addr to be the same as shipping
const {mutateAsync: addPaymentInstrumentToBasket} = useShopperBasketsMutation(
'addPaymentInstrumentToBasket'
@@ -51,6 +61,9 @@ const Payment = () => {
const {step, STEPS, goToStep, goToNextStep} = useCheckout()
+ // TODO: This is added for testing, remove after shipping and billing address components are migrated
+ const [isEditing, setIsEditing] = useState(true)
+
const billingAddressForm = useForm({
mode: 'onChange',
shouldUnregister: false,
@@ -90,6 +103,11 @@ const Payment = () => {
if (!isFormValid) {
return
}
+
+ // TODO: This is added for testing, remove after billing address is migrated
+ return Promise.resolve({success: true}) // Mock successful response
+
+ /* Original code:
const billingAddress = billingSameAsShipping
? selectedShippingAddress
: billingAddressForm.getValues()
@@ -100,6 +118,7 @@ const Payment = () => {
body: address,
parameters: {basketId: basket.basketId}
})
+ */
}
const onPaymentRemoval = async () => {
try {
@@ -124,6 +143,8 @@ const Payment = () => {
const updatedBasket = await onBillingSubmit()
if (updatedBasket) {
+ // TODO: This is added for testing, remove after shipping and billing address components are migrated
+ setIsEditing(false)
goToNextStep()
}
})
@@ -137,13 +158,17 @@ const Payment = () => {
goToStep(STEPS.PAYMENT)}
+ onEdit={() => {
+ setIsEditing(true) // TODO: This is added for testing, remove after shipping address is migrated
+ goToStep(STEPS.PAYMENT)
+ }}
editLabel={formatMessage({
defaultMessage: 'Edit Payment Info',
id: 'toggle_card.action.editPaymentInfo'
@@ -154,23 +179,23 @@ const Payment = () => {
-
+
{!appliedPayment?.paymentCard ? (
) : (
-
+
-
+
)}
-
+
-
+
{
/>
- setBillingSameAsShipping(e.target.checked)}
+ checked={billingSameAsShipping}
+ onCheckedChange={(e) => setBillingSameAsShipping(e.checked)}
>
-
-
-
-
+
+
+
+
+
+
+
+
{billingSameAsShipping && selectedShippingAddress && (
@@ -236,9 +265,9 @@ const Payment = () => {
-
+
{appliedPayment && (
-
+
{
)}
-
+
{selectedBillingAddress && (
-
+
{
const PaymentCardSummary = ({payment}) => {
const CardIcon = getCreditCardIcon(payment?.paymentCard?.cardType)
return (
-
+
{CardIcon && }