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 (
- - + + - { borderBottom="1px solid" borderColor="gray.100" > - - - + + + + + + + + + + + + + - - - - - - - - - - + + + - - + + - - - - - + + + + + + + + + - + 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 ? ( ) : ( - + - +