feat: sharable payment link dialog

This commit is contained in:
Ahmed Bouhuolia
2024-09-15 19:28:43 +02:00
parent 9517b4e279
commit 542e61dbfc
17 changed files with 476 additions and 19 deletions

View File

@@ -0,0 +1,16 @@
import { DialogBody } from '@blueprintjs/core';
import { SharePaymentLinkForm } from './SharePaymentLinkForm';
import { SharePaymentLinkFormContent } from './SharePaymentLinkFormContent';
import { SharePaymentLinkProvider } from './SharePaymentLinkProvider';
export function SharePaymentLinkContent() {
return (
<DialogBody>
<SharePaymentLinkProvider>
<SharePaymentLinkForm>
<SharePaymentLinkFormContent />
</SharePaymentLinkForm>
</SharePaymentLinkProvider>
</DialogBody>
);
}

View File

@@ -0,0 +1,38 @@
// @ts-nocheck
import React from 'react';
import { Dialog, DialogSuspense } from '@/components';
import withDialogRedux from '@/components/DialogReduxConnect';
import { compose } from '@/utils';
const SharePaymentLinkContent = React.lazy(() =>
import('./SharePaymentLinkContent').then((module) => ({
default: module.SharePaymentLinkContent,
})),
);
/**
*
*/
function SharePaymentLinkDialogRoot({ dialogName, payload, isOpen }) {
return (
<Dialog
name={dialogName}
isOpen={isOpen}
payload={payload}
title={'Share Link'}
canEscapeJeyClose={true}
autoFocus={true}
style={{ width: 400 }}
>
<DialogSuspense>
<SharePaymentLinkContent />
</DialogSuspense>
</Dialog>
);
}
export const SharePaymentLinkDialog = compose(withDialogRedux())(
SharePaymentLinkDialogRoot,
);
SharePaymentLinkDialog.displayName = 'SharePaymentLinkDialog';

View File

@@ -0,0 +1,15 @@
import * as Yup from 'yup';
export const SharePaymentLinkFormSchema = Yup.object().shape({
publicity: Yup.string()
.oneOf(['private', 'public'], 'Invalid publicity type')
.required('Publicity is required'),
expiryDate: Yup.date()
.nullable()
.required('Expiration date is required')
.min(new Date(), 'Expiration date must be in the future'),
transactionId: Yup.string()
.required('Transaction ID is required'),
transactionType: Yup.string()
.required('Transaction type is required'),
});

View File

@@ -0,0 +1,72 @@
import React from 'react';
import { Intent } from '@blueprintjs/core';
import { Formik, Form, FormikHelpers } from 'formik';
import { useCreatePaymentLink } from '@/hooks/query/payment-link';
import { AppToaster } from '@/components';
import { SharePaymentLinkFormSchema } from './SharePaymentLinkForm.schema';
import { useDialogContext } from '@/components/Dialog/DialogProvider';
import { useDialogActions } from '@/hooks/state';
import { useSharePaymentLink } from './SharePaymentLinkProvider';
interface SharePaymentLinkFormProps {
children: React.ReactNode;
}
interface SharePaymentLinkFormValues {
publicity: string;
expiryDate: string;
transactionId: string;
transactionType: string;
}
const initialValues = {
publicity: '',
expiryDate: '',
transactionId: '',
transactionType: '',
};
export const SharePaymentLinkForm = ({
children,
}: SharePaymentLinkFormProps) => {
const { mutateAsync: generateShareLink } = useCreatePaymentLink();
const { payload } = useDialogContext();
const { setUrl } = useSharePaymentLink();
const transactionId = payload?.transactionId;
const transactionType = payload?.transactionType;
const formInitialValues = {
...initialValues,
transactionType,
transactionId,
};
const handleFormSubmit = (
values: SharePaymentLinkFormValues,
{ setSubmitting }: FormikHelpers<SharePaymentLinkFormValues>,
) => {
setSubmitting(true);
generateShareLink(values)
.then((res) => {
setSubmitting(false);
setUrl(res.link?.link);
})
.catch(() => {
setSubmitting(false);
AppToaster.show({
message: 'Something went wrong.',
intent: Intent.DANGER,
});
});
};
return (
<Formik<SharePaymentLinkFormValues>
initialValues={formInitialValues}
validationSchema={SharePaymentLinkFormSchema}
onSubmit={handleFormSubmit}
>
<Form>{children}</Form>
</Formik>
);
};
SharePaymentLinkForm.displayName = 'SharePaymentLinkForm';

View File

@@ -0,0 +1,86 @@
// @ts-nocheck
import {
Button,
FormGroup,
InputGroup,
Intent,
Position,
} from '@blueprintjs/core';
import {
DialogFooter,
FDateInput,
FFormGroup,
FSelect,
Icon,
Stack,
} from '@/components';
import { useSharePaymentLink } from './SharePaymentLinkProvider';
import { useClipboard } from '@/hooks/utils/useClipboard';
export function SharePaymentLinkFormContent() {
const { url } = useSharePaymentLink();
const clipboard = useClipboard();
const handleCopyBtnClick = () => {
clipboard.copy(url);
};
return (
<>
<Stack>
<FSelect
name={'publicity'}
items={[
{ value: 'private', text: 'Private' },
{ value: 'public', text: 'Public' },
]}
fastField
/>
<FFormGroup name={'expiryDate'} label={'Expiration Date'} fastField>
<FDateInput
name={'expiryDate'}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
inputProps={{
fill: true,
leftElement: <Icon icon={'date-range'} />,
}}
fastField
/>
</FFormGroup>
{url && (
<FormGroup name={'link'} label={'Payment Link'}>
<InputGroup
name={'link'}
value={url}
disabled
leftElement={
<Button onClick={handleCopyBtnClick} minimal>
Copy
</Button>
}
/>
</FormGroup>
)}
</Stack>
<DialogFooter>
{url ? (
<Button intent={Intent.PRIMARY} onClick={handleCopyBtnClick}>
Copy Link
</Button>
) : (
<>
<Button type={'submit'} intent={Intent.PRIMARY}>
Generate
</Button>
<Button>Cancel</Button>
</>
)}
</DialogFooter>
</>
);
}

View File

@@ -0,0 +1,35 @@
import React, { createContext, useContext, useState, ReactNode } from 'react';
interface SharePaymentLinkContextType {
url: string;
setUrl: React.Dispatch<React.SetStateAction<string>>;
}
const SharePaymentLinkContext =
createContext<SharePaymentLinkContextType | null>(null);
interface SharePaymentLinkProviderProps {
children: ReactNode;
}
export const SharePaymentLinkProvider: React.FC<
SharePaymentLinkProviderProps
> = ({ children }) => {
const [url, setUrl] = useState<string>('');
return (
<SharePaymentLinkContext.Provider value={{ url, setUrl }}>
{children}
</SharePaymentLinkContext.Provider>
);
};
export const useSharePaymentLink = () => {
const context = useContext(SharePaymentLinkContext);
if (!context) {
throw new Error(
'useSharePaymentLink must be used within a SharePaymentLinkProvider',
);
}
return context;
};