mirror of
https://github.com/valitydev/checkout.git
synced 2024-11-06 02:25:18 +00:00
Payment method selector refactoring (#320)
This commit is contained in:
parent
0ef7ba8985
commit
20fc041110
@ -1,21 +0,0 @@
|
||||
import { Center, Image, Square, useColorModeValue } from '@chakra-ui/react';
|
||||
import { HiOutlineCash } from 'react-icons/hi';
|
||||
|
||||
import { ServiceProviderIconMetadata } from 'checkout/backend/payments/serviceProviderMetadata';
|
||||
import { isNil } from 'checkout/utils';
|
||||
|
||||
export type MetadataLogoBoxProps = {
|
||||
logo?: ServiceProviderIconMetadata;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export function MetadataLogoBox({ logo, height }: MetadataLogoBoxProps) {
|
||||
const bgColor = useColorModeValue('white', 'gray.100');
|
||||
|
||||
return (
|
||||
<Center bgColor={bgColor} borderRadius="md" height={height} p={4} userSelect="none">
|
||||
{!isNil(logo) && <Image height={logo.height} src={logo.src} width={logo.width} />}
|
||||
{isNil(logo) && <Square as={HiOutlineCash} color="gray.600" size={12} />}
|
||||
</Center>
|
||||
);
|
||||
}
|
24
src/common/components/Pane/Pane.tsx
Normal file
24
src/common/components/Pane/Pane.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { StackProps, useColorModeValue, VStack } from '@chakra-ui/react';
|
||||
|
||||
export type PaneProps = StackProps;
|
||||
|
||||
export function Pane(props: PaneProps) {
|
||||
const { children, ...rest } = props;
|
||||
const hoverBorderColor = useColorModeValue('gray.300', 'whiteAlpha.400');
|
||||
|
||||
return (
|
||||
<VStack
|
||||
_hover={{ borderColor: hoverBorderColor }}
|
||||
align="stretch"
|
||||
border="1px"
|
||||
borderColor="chakra-border-color"
|
||||
borderRadius="lg"
|
||||
cursor="pointer"
|
||||
p={2}
|
||||
spacing={2}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</VStack>
|
||||
);
|
||||
}
|
5
src/common/components/Pane/PaneLogo.tsx
Normal file
5
src/common/components/Pane/PaneLogo.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { Square, SquareProps } from '@chakra-ui/react';
|
||||
|
||||
export function PaneLogo(props: SquareProps) {
|
||||
return <Square color="gray.600" size={12} {...props} />;
|
||||
}
|
12
src/common/components/Pane/PaneLogoBox.tsx
Normal file
12
src/common/components/Pane/PaneLogoBox.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Center, CenterProps, useColorModeValue } from '@chakra-ui/react';
|
||||
|
||||
export function PaneLogoBox(props: CenterProps) {
|
||||
const { children, ...rest } = props;
|
||||
const bgColor = useColorModeValue('white', 'gray.100');
|
||||
|
||||
return (
|
||||
<Center bgColor={bgColor} borderRadius="md" height={16} p={4} userSelect="none" {...rest}>
|
||||
{children}
|
||||
</Center>
|
||||
);
|
||||
}
|
10
src/common/components/Pane/PaneMetadataLogo.tsx
Normal file
10
src/common/components/Pane/PaneMetadataLogo.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { ImageProps, Image } from '@chakra-ui/react';
|
||||
|
||||
import { ServiceProviderIconMetadata } from 'checkout/backend/payments/serviceProviderMetadata';
|
||||
|
||||
export type PaneMetadataLogoProps = { logo: ServiceProviderIconMetadata } & ImageProps;
|
||||
|
||||
export function PaneMetadataLogo(props: PaneMetadataLogoProps) {
|
||||
const { logo, ...rest } = props;
|
||||
return <Image height={logo.height} src={logo.src} width={logo.width} {...rest} />;
|
||||
}
|
18
src/common/components/Pane/PaneText.tsx
Normal file
18
src/common/components/Pane/PaneText.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Text, TextProps } from '@chakra-ui/react';
|
||||
|
||||
export function PaneText(props: TextProps) {
|
||||
const { children, ...rest } = props;
|
||||
return (
|
||||
<Text
|
||||
fontWeight="medium"
|
||||
overflow="hidden"
|
||||
textAlign="center"
|
||||
textOverflow="ellipsis"
|
||||
userSelect="none"
|
||||
whiteSpace="nowrap"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
}
|
7
src/common/components/Pane/index.ts
Normal file
7
src/common/components/Pane/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export { Pane } from './Pane';
|
||||
export { PaneText } from './PaneText';
|
||||
export { PaneLogoBox } from './PaneLogoBox';
|
||||
export { PaneLogo } from './PaneLogo';
|
||||
export { PaneMetadataLogo } from './PaneMetadataLogo';
|
||||
|
||||
export type { PaneMetadataLogoProps } from './PaneMetadataLogo';
|
@ -1,4 +1,6 @@
|
||||
export { ErrorAlert } from './ErrorAlert';
|
||||
export { BackwardBox } from './BackwardBox';
|
||||
export { GlobalSpinner } from './GlobalSpinner';
|
||||
export { MetadataLogoBox } from './MetadataLogoBox';
|
||||
export { Pane, PaneLogo, PaneLogoBox, PaneText, PaneMetadataLogo } from './Pane';
|
||||
|
||||
export type { PaneMetadataLogoProps } from './Pane';
|
||||
|
@ -52,7 +52,7 @@ export function CardForm() {
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<VStack align="stretch" spacing={5}>
|
||||
<Flex alignItems="center" direction="row">
|
||||
<Flex alignItems="center">
|
||||
{hasBackward && <BackwardBox onClick={backward} />}
|
||||
<Text fontWeight="medium" textAlign="center" width="full">
|
||||
{l['form.header.pay.card.label']}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Button, Center, LightMode, Spacer, VStack } from '@chakra-ui/react';
|
||||
import { Button, LightMode, Spacer, VStack } from '@chakra-ui/react';
|
||||
import { useContext } from 'react';
|
||||
import { FieldError, SubmitHandler, useForm } from 'react-hook-form';
|
||||
|
||||
import { BackwardBox, MetadataLogoBox } from 'checkout/components';
|
||||
import { BackwardBox, PaneLogoBox, PaneMetadataLogo } from 'checkout/components';
|
||||
import { LocaleContext, PaymentContext, PaymentModelContext, ViewModelContext } from 'checkout/contexts';
|
||||
import { toDefaultFormValuesMetadata } from 'checkout/paymentCondition';
|
||||
import { TerminalValues } from 'checkout/paymentMgmt';
|
||||
@ -61,9 +61,9 @@ export function MetadataForm({ provider }: MetadataFormProps) {
|
||||
<VStack align="stretch" spacing={5}>
|
||||
{hasBackward ? <BackwardBox onClick={backward} /> : <Spacer />}
|
||||
{!isNil(logo) && (
|
||||
<Center>
|
||||
<MetadataLogoBox height={16} logo={logo} />
|
||||
</Center>
|
||||
<PaneLogoBox alignSelf="center" width="50%">
|
||||
<PaneMetadataLogo logo={logo} />
|
||||
</PaneLogoBox>
|
||||
)}
|
||||
{!isNil(form) &&
|
||||
form
|
||||
|
@ -1,29 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
import { HiCreditCard } from 'react-icons/hi';
|
||||
|
||||
import { LocaleContext, ViewModelContext } from '../../../common/contexts';
|
||||
import { Method, PaymentMethodIcon, PaymentMethodTitle } from '../../legacy';
|
||||
import { Pane, PaneLogo, PaneLogoBox, PaneText } from 'checkout/components';
|
||||
import { LocaleContext } from 'checkout/contexts';
|
||||
|
||||
export type BankCardPaneProps = {
|
||||
destinationViewId: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export function BankCardPane({ destinationViewId }: BankCardPaneProps) {
|
||||
export function BankCardPane({ onClick }: BankCardPaneProps) {
|
||||
const { l } = useContext(LocaleContext);
|
||||
const {
|
||||
viewModel: { views },
|
||||
forward,
|
||||
} = useContext(ViewModelContext);
|
||||
|
||||
const destination = views.get(destinationViewId);
|
||||
|
||||
if (destination.name !== 'PaymentFormView') {
|
||||
throw new Error(`Wrong View. Expected: PaymentFormView, actual: ${destination.name}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<Method onClick={() => forward(destinationViewId)}>
|
||||
<PaymentMethodIcon name="bank-card" />
|
||||
<PaymentMethodTitle>{l['form.payment.method.name.card.label']}</PaymentMethodTitle>
|
||||
</Method>
|
||||
<Pane onClick={onClick}>
|
||||
<PaneLogoBox>
|
||||
<PaneLogo as={HiCreditCard} />
|
||||
</PaneLogoBox>
|
||||
<PaneText>{l['form.payment.method.name.card.label']}</PaneText>
|
||||
</Pane>
|
||||
);
|
||||
}
|
||||
|
@ -1,21 +1,17 @@
|
||||
import { VStack, Text, SimpleGrid } from '@chakra-ui/react';
|
||||
import { useContext } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { LocaleContext, ViewModelContext } from 'checkout/contexts';
|
||||
|
||||
import { BankCardPane } from './BankCardPane';
|
||||
import { PaymentTerminalPane } from './PaymentTerminalPane';
|
||||
import { TerminalSelectorPane } from './TerminalSelectorPane';
|
||||
import { LocaleContext, ViewModelContext } from '../../../common/contexts';
|
||||
import { HeaderWrapper, Title } from '../../../components/legacy';
|
||||
|
||||
const PanesWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export function PaymentMethodSelectorView() {
|
||||
const { l } = useContext(LocaleContext);
|
||||
const {
|
||||
viewModel: { views, activeViewId },
|
||||
forward,
|
||||
} = useContext(ViewModelContext);
|
||||
const view = views.get(activeViewId);
|
||||
|
||||
@ -24,22 +20,26 @@ export function PaymentMethodSelectorView() {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderWrapper>
|
||||
<Title>{l['form.header.payment.methods.label']}</Title>
|
||||
</HeaderWrapper>
|
||||
<PanesWrapper>
|
||||
{view.items.map(({ name, viewId }, key) => {
|
||||
<VStack align="stretch" minH="sm" spacing={5}>
|
||||
<Text fontWeight="medium" textAlign="center">
|
||||
{l['form.header.payment.methods.label']}
|
||||
</Text>
|
||||
<SimpleGrid columns={[1, 2, 2]} spacing={5}>
|
||||
{view.items.map(({ name, viewId, provider, category }, key) => {
|
||||
switch (name) {
|
||||
case 'BankCard':
|
||||
return <BankCardPane key={key} destinationViewId={viewId} />;
|
||||
return <BankCardPane key={key} onClick={() => forward(viewId)} />;
|
||||
case 'PaymentTerminal':
|
||||
return <PaymentTerminalPane key={key} destinationViewId={viewId} />;
|
||||
return (
|
||||
<PaymentTerminalPane key={key} provider={provider} onClick={() => forward(viewId)} />
|
||||
);
|
||||
case 'TerminalSelector':
|
||||
return <TerminalSelectorPane key={key} destinationViewId={viewId} />;
|
||||
return (
|
||||
<TerminalSelectorPane key={key} category={category} onClick={() => forward(viewId)} />
|
||||
);
|
||||
}
|
||||
})}
|
||||
</PanesWrapper>
|
||||
</>
|
||||
</SimpleGrid>
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
@ -1,38 +1,29 @@
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import { useContext } from 'react';
|
||||
import { HiCash } from 'react-icons/hi';
|
||||
|
||||
import { PaymentModelContext, ViewModelContext } from '../../../common/contexts';
|
||||
import { isNil } from '../../../common/utils';
|
||||
import { findMetadata } from '../../../common/utils/findMetadata';
|
||||
import { MetadataLogo, PaymentMethodItemContainer } from '../../legacy';
|
||||
import { Pane, PaneLogoBox, PaneLogo, PaneText, PaneMetadataLogo } from 'checkout/components';
|
||||
import { PaymentModelContext } from 'checkout/contexts';
|
||||
import { isNil } from 'checkout/utils';
|
||||
import { findMetadata } from 'checkout/utils/findMetadata';
|
||||
|
||||
export type PaymentTerminalPaneProps = {
|
||||
destinationViewId: string;
|
||||
provider: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export function PaymentTerminalPane({ destinationViewId }: PaymentTerminalPaneProps) {
|
||||
export function PaymentTerminalPane({ onClick, provider }: PaymentTerminalPaneProps) {
|
||||
const {
|
||||
paymentModel: { serviceProviders },
|
||||
} = useContext(PaymentModelContext);
|
||||
const {
|
||||
forward,
|
||||
viewModel: { views },
|
||||
} = useContext(ViewModelContext);
|
||||
const destination = views.get(destinationViewId);
|
||||
|
||||
if (destination.name !== 'PaymentFormView') {
|
||||
throw new Error(`Wrong View. Expected: PaymentFormView, actual: ${destination.name}`);
|
||||
}
|
||||
|
||||
const { logo } = findMetadata(serviceProviders, destination.provider);
|
||||
const { logo } = findMetadata(serviceProviders, provider);
|
||||
const serviceProvider = serviceProviders.find(({ id }) => id === provider);
|
||||
|
||||
return (
|
||||
<PaymentMethodItemContainer
|
||||
id={`${Math.floor(Math.random() * 100)}-payment-method-item`}
|
||||
onClick={() => forward(destinationViewId)}
|
||||
>
|
||||
{!isNil(logo) && <MetadataLogo metadata={logo} />}
|
||||
{isNil(logo) && <Text>{destination.provider}</Text>}
|
||||
</PaymentMethodItemContainer>
|
||||
<Pane onClick={onClick}>
|
||||
<PaneLogoBox>
|
||||
{isNil(logo) && <PaneLogo as={HiCash} />} {!isNil(logo) && <PaneMetadataLogo logo={logo} />}
|
||||
</PaneLogoBox>
|
||||
<PaneText>{serviceProvider?.brandName}</PaneText>
|
||||
</Pane>
|
||||
);
|
||||
}
|
||||
|
@ -1,28 +1,19 @@
|
||||
import { useContext } from 'react';
|
||||
import { HiCash } from 'react-icons/hi';
|
||||
|
||||
import { ViewModelContext } from '../../../common/contexts';
|
||||
import { Method, PaymentMethodIcon, PaymentMethodTitle } from '../../legacy';
|
||||
import { Pane, PaneLogoBox, PaneLogo, PaneText } from 'checkout/components';
|
||||
|
||||
export type TerminalSelectorPaneProps = {
|
||||
destinationViewId: string;
|
||||
category: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export function TerminalSelectorPane({ destinationViewId }: TerminalSelectorPaneProps) {
|
||||
const {
|
||||
forward,
|
||||
viewModel: { views },
|
||||
} = useContext(ViewModelContext);
|
||||
|
||||
const destination = views.get(destinationViewId);
|
||||
|
||||
if (destination.name !== 'TerminalSelectorView') {
|
||||
throw new Error(`Wrong View. Expected: TerminalSelectorView, actual: ${destination.name}`);
|
||||
}
|
||||
|
||||
export function TerminalSelectorPane({ onClick, category }: TerminalSelectorPaneProps) {
|
||||
return (
|
||||
<Method onClick={() => forward(destinationViewId)}>
|
||||
<PaymentMethodIcon name="terminals" />
|
||||
<PaymentMethodTitle>{destination.category}</PaymentMethodTitle>
|
||||
</Method>
|
||||
<Pane onClick={onClick}>
|
||||
<PaneLogoBox>
|
||||
<PaneLogo as={HiCash} />
|
||||
</PaneLogoBox>
|
||||
<PaneText>{category}</PaneText>
|
||||
</Pane>
|
||||
);
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
import { VStack, Text, useColorModeValue } from '@chakra-ui/react';
|
||||
|
||||
import { ServiceProviderIconMetadata } from 'checkout/backend/payments/serviceProviderMetadata';
|
||||
import { MetadataLogoBox } from 'checkout/components';
|
||||
|
||||
type ServiceProviderPaneProps = {
|
||||
logo?: ServiceProviderIconMetadata;
|
||||
text: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export function ServiceProviderPane({ text, logo, onClick }: ServiceProviderPaneProps) {
|
||||
const hoverBorderColor = useColorModeValue('gray.300', 'whiteAlpha.400');
|
||||
|
||||
return (
|
||||
<VStack
|
||||
_hover={{ borderColor: hoverBorderColor }}
|
||||
align="stretch"
|
||||
border="1px"
|
||||
borderColor="chakra-border-color"
|
||||
borderRadius="lg"
|
||||
cursor="pointer"
|
||||
p={2}
|
||||
spacing={2}
|
||||
onClick={onClick}
|
||||
>
|
||||
<MetadataLogoBox height={16} logo={logo} />
|
||||
<Text
|
||||
fontWeight="medium"
|
||||
overflow="hidden"
|
||||
textAlign="center"
|
||||
textOverflow="ellipsis"
|
||||
userSelect="none"
|
||||
whiteSpace="nowrap"
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
</VStack>
|
||||
);
|
||||
}
|
@ -1,25 +1,26 @@
|
||||
import { VStack, SimpleGrid, Input } from '@chakra-ui/react';
|
||||
import { VStack, SimpleGrid, Input, Flex, Text } from '@chakra-ui/react';
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { HiCash } from 'react-icons/hi';
|
||||
|
||||
import { BackwardBox } from 'checkout/components';
|
||||
import { BackwardBox, Pane, PaneLogo, PaneLogoBox, PaneMetadataLogo, PaneText } from 'checkout/components';
|
||||
import { LocaleContext, PaymentModelContext, ViewModelContext } from 'checkout/contexts';
|
||||
import { isNil } from 'checkout/utils';
|
||||
|
||||
import { PageNavigation } from './PageNavigation';
|
||||
import { ServiceProviderPane } from './ServiceProviderPane';
|
||||
import { useGridPages } from './useGrigPages';
|
||||
|
||||
const ITEMS_ON_PAGE = 6;
|
||||
|
||||
export function TerminalSelectorView() {
|
||||
const { l } = useContext(LocaleContext);
|
||||
const {
|
||||
paymentModel: { serviceProviders },
|
||||
} = useContext(PaymentModelContext);
|
||||
const {
|
||||
viewModel: { views, activeViewId, hasBackward },
|
||||
backward,
|
||||
forward,
|
||||
} = useContext(ViewModelContext);
|
||||
const {
|
||||
paymentModel: { serviceProviders },
|
||||
} = useContext(PaymentModelContext);
|
||||
|
||||
const view = views.get(activeViewId);
|
||||
if (view.name !== 'TerminalSelectorView') {
|
||||
@ -39,12 +40,22 @@ export function TerminalSelectorView() {
|
||||
};
|
||||
|
||||
return (
|
||||
<VStack align="stretch" spacing={5}>
|
||||
{hasBackward && <BackwardBox onClick={backward} />}
|
||||
<VStack align="stretch" minH="sm" spacing={5}>
|
||||
<Flex alignItems="center">
|
||||
{hasBackward && <BackwardBox onClick={backward} />}
|
||||
<Text fontWeight="medium" textAlign="center" width="full">
|
||||
{l['form.header.payment.methods.label']}
|
||||
</Text>
|
||||
</Flex>
|
||||
{isSearchAvailable && <Input placeholder={l['form.serviceProvidersGrid.search']} onChange={onChange} />}
|
||||
<SimpleGrid columns={[1, 2, 2]} spacing={5}>
|
||||
{pageItems.map(({ logo, brandName, viewId }, i) => (
|
||||
<ServiceProviderPane key={i} logo={logo} text={brandName} onClick={() => forward(viewId)} />
|
||||
<Pane key={i} onClick={() => forward(viewId)}>
|
||||
<PaneLogoBox>
|
||||
{isNil(logo) && <PaneLogo as={HiCash} />} {!isNil(logo) && <PaneMetadataLogo logo={logo} />}
|
||||
</PaneLogoBox>
|
||||
<PaneText>{brandName}</PaneText>
|
||||
</Pane>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
{totalPages > 1 && (
|
||||
|
@ -31,6 +31,7 @@ export type TerminalSelectorView = {
|
||||
export type PaymentMethodSelectorItem = {
|
||||
name: PaymentMethodName | 'TerminalSelector';
|
||||
viewId: string;
|
||||
category?: string;
|
||||
provider?: string;
|
||||
};
|
||||
|
||||
|
@ -126,6 +126,7 @@ const toViews = (paymentMethods: PaymentMethod[]): [Map<string, View>, string] =
|
||||
const singleProviderSelectorItem: PaymentMethodSelectorItem = {
|
||||
name: methodName,
|
||||
viewId: singleProviderTerminalId,
|
||||
category,
|
||||
provider: providers[0],
|
||||
};
|
||||
return [
|
||||
@ -150,6 +151,7 @@ const toViews = (paymentMethods: PaymentMethod[]): [Map<string, View>, string] =
|
||||
};
|
||||
const terminalSelectorItem: PaymentMethodSelectorItem = {
|
||||
name: 'TerminalSelector',
|
||||
category,
|
||||
viewId: terminalSelectorId,
|
||||
};
|
||||
return [
|
||||
|
Loading…
Reference in New Issue
Block a user