feat: Control the payment method from invoice form

This commit is contained in:
Ahmed Bouhuolia
2024-09-22 21:23:02 +02:00
parent 9827a84857
commit eb5fdbf4ee
11 changed files with 295 additions and 20 deletions

View File

@@ -0,0 +1,84 @@
import { FCheckbox, Group } from '@/components';
import { useUncontrolled } from '@/hooks/useUncontrolled';
import { Checkbox, Text } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { get } from 'lodash';
import { useMemo } from 'react';
import styled from 'styled-components';
export interface PaymentMethodSelectProps {
label: string;
value?: boolean;
initialValue?: boolean;
onChange?: (value: boolean) => void;
}
export function PaymentMethodSelect({
value,
initialValue,
onChange,
label,
}: PaymentMethodSelectProps) {
const [_value, handleChange] = useUncontrolled<boolean>({
value,
initialValue,
finalValue: false,
onChange,
});
const handleClick = () => {
handleChange(!_value);
};
return (
<PaymentMethodSelectRoot onClick={handleClick}>
<PaymentMethodCheckbox
label={''}
checked={_value}
onClick={handleClick}
/>
<PaymentMethodText>{label}</PaymentMethodText>
</PaymentMethodSelectRoot>
);
}
export interface PaymentMethodSelectFieldProps
extends Partial<PaymentMethodSelectProps> {
label: string;
name: string;
}
export function PaymentMethodSelectField({
name,
...props
}: PaymentMethodSelectFieldProps) {
const { values, setFieldValue } = useFormikContext();
const value = useMemo(() => get(values, name), [values, name]);
const handleChange = (newValue: boolean) => {
setFieldValue(name, newValue);
};
return (
<PaymentMethodSelect value={value} onChange={handleChange} {...props} />
);
}
const PaymentMethodSelectRoot = styled(Group)`
border: 1px solid #d3d8de;
border-radius: 3px;
padding: 8px;
gap: 0;
min-width: 200px;
cursor: pointer;
`;
const PaymentMethodCheckbox = styled(Checkbox)`
margin: 0;
&.bp4-control .bp4-control-indicator {
box-shadow: 0 0 0 1px #c5cbd3;
}
`;
const PaymentMethodText = styled(Text)`
color: #404854;
`;

View File

@@ -0,0 +1,52 @@
import styled from 'styled-components';
import React from 'react';
import {
Classes,
Popover,
PopoverInteractionKind,
Position,
} from '@blueprintjs/core';
import { Stack } from '@/components';
import { PaymentMethodSelectField } from './PaymentMethodSelect';
interface PaymentOptionsButtonPopverProps {
paymentMethods: Array<any>;
children: React.ReactNode;
}
export function PaymentOptionsButtonPopver({
paymentMethods,
children,
}: PaymentOptionsButtonPopverProps) {
return (
<Popover
interactionKind={PopoverInteractionKind.HOVER}
position={Position.TOP_RIGHT}
popoverClassName={Classes.POPOVER_CONTENT_SIZING}
minimal={true}
content={
<Stack spacing={8}>
<PaymentMethodsTitle>Payment Options</PaymentMethodsTitle>
<Stack spacing={8}>
{paymentMethods?.map((service, key) => (
<PaymentMethodSelectField
name={`payment_methods.${service.id}.enable`}
label={'Card (Stripe)'}
key={key}
/>
))}
</Stack>
</Stack>
}
>
{children}
</Popover>
);
}
const PaymentMethodsTitle = styled('h6')`
font-size: 12px;
font-weight: 500;
margin: 0;
color: rgb(95, 107, 124);
`;

View File

@@ -2,23 +2,25 @@
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { FFormGroup, FEditableText, FormattedMessage as T } from '@/components';
import { useDialogActions } from '@/hooks/state';
import { DialogsName } from '@/constants/dialogs';
import { Button, Intent } from '@blueprintjs/core';
import {
FFormGroup,
FEditableText,
FormattedMessage as T,
Box,
Group,
Stack,
} from '@/components';
import { VisaIcon } from '@/icons/Visa';
import { MastercardIcon } from '@/icons/Mastercard';
import { useInvoiceFormContext } from './InvoiceFormProvider';
import { PaymentOptionsButtonPopver } from '@/containers/PaymentMethods/SelectPaymentMethodPopover';
export function InvoiceFormFooterLeft() {
const { openDialog } = useDialogActions();
const handleSelectPaymentMethodsClick = () => {
openDialog(DialogsName.SelectPaymentMethod, {});
}
const { paymentServices } = useInvoiceFormContext();
return (
<React.Fragment>
<FFormGroup label={'Payment Options'} name={'payment_method_id'}>
<a href={'#'} onClick={handleSelectPaymentMethodsClick}>Payment Options</a>
</FFormGroup>
<Stack spacing={20}>
{/* --------- Invoice message --------- */}
<InvoiceMsgFormGroup
name={'invoice_message'}
@@ -46,14 +48,31 @@ export function InvoiceFormFooterLeft() {
fastField
/>
</TermsConditsFormGroup>
</React.Fragment>
{/* --------- Payment Options --------- */}
<PaymentOptionsFormGroup
label={'Payment Options'}
name={'payment_method_id'}
>
<PaymentOptionsText>
Select an online payment option to get paid faster{' '}
<Group spacing={6} style={{ marginLeft: 8 }}>
<VisaIcon />
<MastercardIcon />
</Group>
<PaymentOptionsButtonPopver paymentMethods={paymentServices}>
<PaymentOptionsButton intent={Intent.PRIMARY} small minimal>
Payment Options
</PaymentOptionsButton>
</PaymentOptionsButtonPopver>
</PaymentOptionsText>
</PaymentOptionsFormGroup>
</Stack>
);
}
const InvoiceMsgFormGroup = styled(FFormGroup)`
&.bp4-form-group {
margin-bottom: 40px;
.bp4-label {
font-size: 12px;
margin-bottom: 12px;
@@ -75,3 +94,29 @@ const TermsConditsFormGroup = styled(FFormGroup)`
}
}
`;
const PaymentOptionsFormGroup = styled(FFormGroup)`
&.bp4-form-group {
.bp4-label {
font-weight: 500;
font-size: 12px;
margin-bottom: 10px;
}
}
`;
const PaymentOptionsText = styled(Box)`
font-size: 13px;
display: inline-flex;
align-items: center;
color: #5f6b7c;
`;
const PaymentOptionsButton = styled(Button)`
font-size: 13px;
margin-left: 4px;
&.bp4-minimal.bp4-intent-primary {
color: #0052cc;
}
`;

View File

@@ -150,6 +150,10 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
editInvoiceMutate,
setSubmitPayload,
isNewMode,
// Payment Services
paymentServices,
isPaymentServicesLoading,
};
return (

View File

@@ -69,6 +69,7 @@ export const defaultInvoice = {
pdf_template_id: '',
entries: [...repeatValue(defaultInvoiceEntry, MIN_LINES_NUMBER)],
attachments: [],
payment_methods: {},
};
// Invoice entry request schema.
@@ -223,9 +224,19 @@ export function transformValueToRequest(values) {
entries: transformEntriesToRequest(values.entries),
delivered: false,
attachments: transformAttachmentsToRequest(values),
payment_methods: transformPaymentMethodsToRequest(values?.payment_methods),
};
}
const transformPaymentMethodsToRequest = (
paymentMethods: Record<string, { enable: boolean }>,
): Array<{ payment_integration_id: string; enable: boolean }> => {
return Object.entries(paymentMethods).map(([paymentMethodId, method]) => ({
payment_integration_id: paymentMethodId,
enable: method.enable,
}));
};
export const useSetPrimaryWarehouseToForm = () => {
const { setFieldValue } = useFormikContext();
const { warehouses, isWarehousesSuccess } = useInvoiceFormContext();