-
Notifications
You must be signed in to change notification settings - Fork 214
Migrate Payment and PaymentForm to Chakra V3 #2661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 19 commits
0d46191
22206c3
4b2934a
3a03428
efdda70
5727397
fcd531f
569f9fc
6c4ec78
4956786
2e60852
eaf1175
71d7bc0
2b49056
b0c4b25
6fec742
cf6f250
47fc3ed
76cec75
07c67e6
9100f21
1832ab8
dc4be52
e21ea41
8608a2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -42,31 +42,15 @@ const CreditCardFields = ({form, prefix = ''}) => { | |
| description: 'Generic credit card security code help text' | ||
| }) | ||
|
|
||
| const handleTooltipClose = () => { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" /> | ||
|
|
@@ -84,19 +68,17 @@ const CreditCardFields = ({form, prefix = ''}) => { | |
| : number | ||
| form.setValue('cardType', card?.type || '') | ||
| return onChange(formattedNumber) | ||
| } | ||
| }, | ||
| endElement: | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In v3, the pattern is to use |
||
| 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}) => ({ | ||
|
|
@@ -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 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| /* | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
|---|---|---|
|
|
@@ -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 ( | ||
| <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', | ||
|
|
@@ -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'} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
|
|
||
There was a problem hiding this comment.
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
InputGroupwith anendElementprop instead ofInputRightElement.