Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ const Field = ({
>
<PasswordIcon color="gray.500" boxSize={6} />
</IconButton>
) : undefined
) : (
_inputProps?.endElement
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In v3, the pattern is to use InputGroup with an endElement prop instead of InputRightElement.

)
}
>
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@
* 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'
import {AmexIcon, DiscoverIcon, MastercardIcon, VisaIcon, InfoIcon} from '../../components/icons'

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
Expand All @@ -42,31 +42,15 @@ const CreditCardFields = ({form, prefix = ''}) => {
description: 'Generic credit card security code help text'
})

const handleTooltipClose = () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tooltip responds to these events by default. So we don't need this

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 (
<Box>
<Stack spacing={5}>
<Stack gap={5}>
<Field
{...fields.number}
formLabel={
<Flex justify="space-between">
<FormLabel>{fields.number.label}</FormLabel>
<Stack direction="row" spacing={1}>
<Flex justify="space-between" align="center" w="full">
<Box>{fields.number.label}</Box>
<Stack direction="row" gap={1}>
<VisaIcon layerStyle="ccIcon" />
<MastercardIcon layerStyle="ccIcon" />
<AmexIcon layerStyle="ccIcon" />
Expand All @@ -84,19 +68,17 @@ const CreditCardFields = ({form, prefix = ''}) => {
: number
form.setValue('cardType', card?.type || '')
return onChange(formattedNumber)
}
},
endElement:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In v3, the pattern is to use InputGroup with an endElement prop instead of InputRightElement.

CardIcon && form.getValues().number?.length > 2 ? (
<CardIcon layerStyle="ccIcon" />
) : undefined
})}
>
{CardIcon && form.getValues().number?.length > 2 && (
<InputRightElement width="60px">
<CardIcon layerStyle="ccIcon" />
</InputRightElement>
)}
</Field>
/>

<Field {...fields.holder} />

<SimpleGrid columns={[2, 2, 3]} spacing={5}>
<SimpleGrid columns={[2, 2, 3]} gap={5}>
<Field
{...fields.expiry}
inputProps={({onChange}) => ({
Expand Down Expand Up @@ -131,33 +113,31 @@ const CreditCardFields = ({form, prefix = ''}) => {
<Field
{...fields.securityCode}
formLabel={
<>
<FormLabel display="inline" mr={1}>
{fields.securityCode.label}
</FormLabel>
<Box
onMouseEnter={handleTooltipOpen}
onFocus={handleTooltipOpen}
as="span"
>
<ChakraField.Label>
<Flex align="center" justify="space-between">
<Box>{fields.securityCode.label}</Box>
<Tooltip
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For ease of use, created a closed component composition for the Tooltip component as suggest in the Chakra doc

hasArrow
placement="top"
label={securityCodeTooltipLabel}
shouldWrapChildren={true}
isOpen={isTooltipOpen}
content={securityCodeTooltipLabel}
contentProps={{
css: {'--tooltip-bg': 'colors.blue.800'}
}}
>
<InfoIcon
boxSize={5}
color="gray.700"
<Box
as="button"
aria-label={formatMessage({
id: 'credit_card_fields.tool_tip.security_code_aria_label',
defaultMessage: 'Security code info'
})}
/>
>
<InfoIcon
boxSize={4}
color="gray.700"
cursor="pointer"
/>
</Box>
</Tooltip>
</Box>
</>
</Flex>
</ChakraField.Label>
}
/>
</SimpleGrid>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For ease of use, created a closed component composition for the Tooltip component as suggest in the Chakra doc

* 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 (
<ChakraTooltip.Root
openDelay={openDelay}
closeDelay={closeDelay}
positioning={finalPositioning}
>
<ChakraTooltip.Trigger asChild ref={ref}>
{children}
</ChakraTooltip.Trigger>
<ChakraTooltip.Positioner>
<ChakraTooltip.Content {...contentProps}>
{showArrow && (
<ChakraTooltip.Arrow>
<ChakraTooltip.ArrowTip />
</ChakraTooltip.Arrow>
)}
{content}
</ChakraTooltip.Content>
</ChakraTooltip.Positioner>
</ChakraTooltip.Root>
)
}
)

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
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const Checkout = () => {
/>
<ShippingAddress />
<ShippingOptions />
{/* <Payment /> TODO: bring this back */}
<Payment />

{step === 4 && (
<Box pt={3} display={{base: 'none', lg: 'block'}}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -20,10 +21,10 @@ const PaymentForm = ({form, onSubmit}) => {

return (
<form onSubmit={form.handleSubmit(onSubmit)}>
<Stack spacing={8}>
<Stack spacing={5}>
<Stack gap={8}>
<Stack gap={5}>
<Box border="1px solid" borderColor="gray.100" rounded="base" overflow="hidden">
<RadioGroup
<RadioGroup.Root
value="cc"
aria-label={formatMessage({
defaultMessage: 'Payment',
Expand All @@ -38,54 +39,76 @@ const PaymentForm = ({form, onSubmit}) => {
borderBottom="1px solid"
borderColor="gray.100"
>
<Radio value="cc">
<Flex justify="space-between">
<Stack direction="row" align="center">
<RadioGroup.Item
value="cc"
w="full"
display="flex"
alignItems="center"
>
<RadioGroup.ItemHiddenInput />
<RadioGroup.ItemIndicator />
<RadioGroup.ItemText flex="1">
<Flex justify="space-between">
<Stack direction="row" align="center">
<Text fontWeight="bold">
<FormattedMessage
defaultMessage="Credit Card"
id="payment_selection.heading.credit_card"
/>
</Text>
<Tooltip
content={formatMessage({
defaultMessage:
'This is a secure SSL encrypted payment.',
id: 'payment_selection.tooltip.secure_payment'
})}
contentProps={{
css: {'--tooltip-bg': 'colors.blue.800'}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: what do you think we make a constant for tthis at the top of checkout? or do you think it is not worth it?

}}
positioning={{
strategy: 'fixed',
flip: false
}}
>
<LockIcon
color="gray.700"
boxSize={5}
cursor="pointer"
/>
</Tooltip>
</Stack>
<Text fontWeight="bold">
<FormattedMessage
defaultMessage="Credit Card"
id="payment_selection.heading.credit_card"
<FormattedNumber
value={basket?.orderTotal}
style="currency"
currency={currency}
/>
</Text>
<Tooltip
hasArrow
placement="top"
label={formatMessage({
defaultMessage:
'This is a secure SSL encrypted payment.',
id: 'payment_selection.tooltip.secure_payment'
})}
>
<LockIcon color="gray.700" boxSize={5} />
</Tooltip>
</Stack>
<Text fontWeight="bold">
<FormattedNumber
value={basket?.orderTotal}
style="currency"
currency={currency}
/>
</Text>
</Flex>
</Radio>
</Flex>
</RadioGroup.ItemText>
</RadioGroup.Item>
</Box>

<Box p={[4, 4, 6]} borderBottom="1px solid" borderColor="gray.100">
<Stack spacing={6}>
<Stack spacing={6}>
<Stack gap={6}>
<Stack gap={6}>
<CreditCardFields form={form} />
</Stack>
</Stack>
</Box>

<Box py={3} px={[4, 4, 6]} bg="gray.50" borderColor="gray.100">
<Radio value="paypal">
<Box py="2px">
<PaypalIcon width="auto" height="20px" />
</Box>
</Radio>
<RadioGroup.Item value="paypal">
<RadioGroup.ItemHiddenInput />
<RadioGroup.ItemIndicator />
<RadioGroup.ItemText>
<Box py="2px">
<PaypalIcon width="auto" height="20px" />
</Box>
</RadioGroup.ItemText>
</RadioGroup.Item>
</Box>
</RadioGroup>
</RadioGroup.Root>
</Box>
</Stack>
</Stack>
Expand Down
Loading
Loading