feat: Edit stripe payment integation drawer

This commit is contained in:
Ahmed Bouhuolia
2024-09-22 12:52:59 +02:00
parent e04f5d26a3
commit c0a4c965f0
13 changed files with 399 additions and 13 deletions

View File

@@ -1,9 +1,12 @@
import React, { createContext, ReactNode, useContext } from 'react';
import { useGetPaymentServicesState } from '@/hooks/query/payment-services';
import { createContext, ReactNode, useContext } from 'react';
import {
GetPaymentServicesStateResponse,
useGetPaymentServicesState,
} from '@/hooks/query/payment-services';
type PaymentMethodsContextType = {
isPaymentMethodsStateLoading: boolean;
paymentMethodsState: any;
paymentMethodsState: GetPaymentServicesStateResponse | undefined;
};
const PaymentMethodsContext = createContext<PaymentMethodsContextType>(

View File

@@ -1,9 +1,18 @@
// @ts-nocheck
import styled from 'styled-components';
import { Button, Classes, Intent, Text } from '@blueprintjs/core';
import { Box, Card, Group, Stack } from '@/components';
import { AppToaster, Box, Card, Group, Stack } from '@/components';
import { StripeLogo } from '@/icons/StripeLogo';
import { PaymentMethodsBoot } from './PreferencesPaymentMethodsBoot';
import {
PaymentMethodsBoot,
usePaymentMethodsBoot,
} from './PreferencesPaymentMethodsBoot';
import { StripePreSetupDialog } from './dialogs/StripePreSetupDialog/StripePreSetupDialog';
import { DialogsName } from '@/constants/dialogs';
import { useDialogActions, useDrawerActions } from '@/hooks/state';
import { useCreateStripeAccountLink } from '@/hooks/query/stripe-integration';
import { StripeIntegrationEditDrawer } from './drawers/StripeIntegrationEditDrawer';
import { DRAWERS } from '@/constants/drawers';
export default function PreferencesPaymentMethodsPage() {
return (
@@ -18,19 +27,81 @@ export default function PreferencesPaymentMethodsPage() {
<StripePaymentMethod />
</Stack>
</PaymentMethodsBoot>
<StripePreSetupDialog dialogName={DialogsName.StripeSetup} />
<StripeIntegrationEditDrawer
name={DRAWERS.STRIPE_PAYMENT_INTEGRATION_EDIT}
/>
</PaymentMethodsRoot>
);
}
function StripePaymentMethod() {
const { openDialog } = useDialogActions();
const { openDrawer } = useDrawerActions();
const { paymentMethodsState } = usePaymentMethodsBoot();
const stripeState = paymentMethodsState?.stripe;
const isAccountCreated = stripeState?.isStripeAccountCreated;
const isAccountActive = stripeState?.isStripePaymentActive;
const stripeAccountId = stripeState?.stripeAccountId;
const {
mutateAsync: createStripeAccountLink,
isLoading: isCreateStripeLinkLoading,
} = useCreateStripeAccountLink();
// Handle Stripe setup button click.
const handleSetUpBtnClick = () => {
openDialog(DialogsName.StripeSetup);
};
// Handle complete Stripe setup button click.
const handleCompleteSetUpBtnClick = () => {
createStripeAccountLink({ stripeAccountId })
.then((res) => {
const { clientSecret } = res;
if (clientSecret.url) {
window.open(clientSecret.url, '_blank');
}
})
.catch(() => {
AppToaster.show({
message: 'Something went wrong.',
intent: Intent.DANGER,
});
});
};
const handleEditBtnClick = () => {
openDrawer(DRAWERS.STRIPE_PAYMENT_INTEGRATION_EDIT);
};
return (
<Card style={{ margin: 0 }}>
<Group position="apart">
<StripeLogo />
<Group>
<Button intent={Intent.PRIMARY} small>
Set it Up
<Group spacing={10}>
<Button small onClick={handleEditBtnClick}>
Edit
</Button>
{!isAccountCreated && (
<Button intent={Intent.PRIMARY} small onClick={handleSetUpBtnClick}>
Set it Up
</Button>
)}
{isAccountCreated && !isAccountActive && (
<Button
intent={Intent.PRIMARY}
small
onClick={handleCompleteSetUpBtnClick}
loading={isCreateStripeLinkLoading}
>
Complete Stripe Set Up
</Button>
)}
</Group>
</Group>

View File

@@ -0,0 +1,33 @@
// @ts-nocheck
import React from 'react';
import { Dialog, DialogSuspense } from '@/components';
import { compose } from '@/utils';
import withDialogRedux from '@/components/DialogReduxConnect';
import { StripePreSetupDialogContent } from './StripePreSetupDialogContent';
/**
* Select payment methods dialogs.
*/
function StripePreSetupDialogRoot({ dialogName, payload, isOpen }) {
return (
<Dialog
name={dialogName}
isOpen={isOpen}
payload={payload}
title={'Share Link'}
canEscapeJeyClose={true}
autoFocus={true}
style={{ width: 570 }}
>
<DialogSuspense>
<StripePreSetupDialogContent />
</DialogSuspense>
</Dialog>
);
}
export const StripePreSetupDialog = compose(withDialogRedux())(
StripePreSetupDialogRoot,
);
StripePreSetupDialogRoot.displayName = 'StripePreSetupDialog';

View File

@@ -0,0 +1,55 @@
import {
useCreateStripeAccount,
useCreateStripeAccountLink,
} from '@/hooks/query/stripe-integration';
import { Button, DialogBody, DialogFooter, Intent } from '@blueprintjs/core';
export function StripePreSetupDialogContent() {
const {
mutateAsync: createStripeAccount,
isLoading: isCreateStripeAccountLoading,
} = useCreateStripeAccount();
const {
mutateAsync: createStripeAccountLink,
isLoading: isCreateStripeLinkLoading,
} = useCreateStripeAccountLink();
const handleSetUpBtnClick = () => {
createStripeAccount({})
.then((response) => {
const { account_id: accountId } = response;
return createStripeAccountLink({ stripeAccountId: accountId });
})
.then((res) => {
const { clientSecret } = res;
if (clientSecret.url) {
window.location.href = clientSecret.url;
}
});
};
const isLoading = isCreateStripeAccountLoading || isCreateStripeLinkLoading;
return (
<>
<DialogBody>
</DialogBody>
<DialogFooter
actions={
<Button
intent={Intent.PRIMARY}
onClick={handleSetUpBtnClick}
loading={isLoading}
>
Set It Up
</Button>
}
></DialogFooter>
</>
);
}

View File

@@ -0,0 +1,41 @@
import React, { createContext, useContext } from 'react';
import { Spinner } from '@blueprintjs/core';
import { useAccounts } from '@/hooks/query';
interface StripeIntegrationEditContextType {
accounts: any;
isAccountsLoading: boolean;
}
const StripeIntegrationEditContext =
createContext<StripeIntegrationEditContextType>(
{} as StripeIntegrationEditContextType,
);
export const useStripeIntegrationEditBoot = () => {
const context = useContext<StripeIntegrationEditContextType>(
StripeIntegrationEditContext,
);
if (!context) {
throw new Error(
'useStripeIntegrationEditContext must be used within a StripeIntegrationEditProvider',
);
}
return context;
};
export const StripeIntegrationEditBoot: React.FC = ({ children }) => {
const { data: accounts, isLoading: isAccountsLoading } = useAccounts({}, {});
const value = { accounts, isAccountsLoading };
const isLoading = isAccountsLoading;
if (isLoading) {
return <Spinner size={20} />;
}
return (
<StripeIntegrationEditContext.Provider value={value}>
{children}
</StripeIntegrationEditContext.Provider>
);
};

View File

@@ -0,0 +1,28 @@
// @ts-nocheck
import { Classes } from '@blueprintjs/core';
import { DrawerBody, DrawerHeaderContent } from '@/components';
import { StripeIntegrationEditForm } from './StripeIntegrationEditForm';
import { StripeIntegrationEditBoot } from './StripeIntegrationEditBoot';
import {
StripeIntegrationEditFormContent,
StripeIntegrationEditFormFooter,
} from './StripeIntegrationEditFormContent';
export function StripeIntegrationEditContent() {
return (
<StripeIntegrationEditBoot>
<DrawerHeaderContent title={'Edit Stripe Integration'} />
<StripeIntegrationEditBoot>
<StripeIntegrationEditForm>
<DrawerBody>
<StripeIntegrationEditFormContent />
</DrawerBody>
<div className={Classes.DRAWER_FOOTER}>
<StripeIntegrationEditFormFooter />
</div>
</StripeIntegrationEditForm>
</StripeIntegrationEditBoot>
</StripeIntegrationEditBoot>
);
}

View File

@@ -0,0 +1,35 @@
// @ts-nocheck
import React from 'react';
import * as R from 'ramda';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
const StripeIntegrationEditContent = React.lazy(() =>
import('./StripeIntegrationEditContent').then((module) => ({
default: module.StripeIntegrationEditContent,
})),
);
/**
* Stripe integration edit drawer.
* @returns {React.ReactNode}
*/
function StripeIntegrationEditDrawerRoot({
name,
// #withDrawer
isOpen,
payload,
}) {
return (
<Drawer isOpen={isOpen} name={name} payload={payload} size={'600px'}>
<DrawerSuspense>
<StripeIntegrationEditContent />
</DrawerSuspense>
</Drawer>
);
}
export const StripeIntegrationEditDrawer = R.compose(withDrawers())(
StripeIntegrationEditDrawerRoot,
);

View File

@@ -0,0 +1,42 @@
import React from 'react';
import * as Yup from 'yup';
import { Formik, FormikHelpers } from 'formik';
interface StripeIntegrationFormValues {
paymentAccountId: string;
clearingAccountId: string;
}
const initialValues = {
paymentAccountId: '',
clearingAccountId: '',
};
const validationSchema = Yup.object().shape({
paymentAccountId: Yup.string().required('Payment Account is required'),
clearingAccountId: Yup.string().required('Clearing Account is required'),
});
interface StripeIntegrationEditFormProps {
children: React.ReactNode;
}
export function StripeIntegrationEditForm({
children,
}: StripeIntegrationEditFormProps) {
const onSubmit = (
values: StripeIntegrationFormValues,
{ setSubmitting }: FormikHelpers<StripeIntegrationFormValues>,
) => {
};
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
>
<>{children}</>
</Formik>
);
}

View File

@@ -0,0 +1,66 @@
import { AccountsSelect, FFormGroup, Group, Stack } from '@/components';
import { useStripeIntegrationEditBoot } from './StripeIntegrationEditBoot';
import { Button, Intent } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useDrawerActions } from '@/hooks/state';
export function StripeIntegrationEditFormContent() {
const { accounts } = useStripeIntegrationEditBoot();
return (
<Stack spacing={0} style={{ padding: 20 }}>
<FFormGroup
name={'paymentAccountId'}
label={'Payment Account'}
style={{ maxWidth: 300 }}
>
<AccountsSelect
name={'paymentAccountId'}
items={accounts}
fastField
fill
allowCreate
/>
</FFormGroup>
<FFormGroup
name={'clearingAccountId'}
label={'Clearing Account'}
style={{ maxWidth: 300 }}
>
<AccountsSelect
name={'clearingAccountId'}
items={accounts}
fastField
fill
allowCreate
/>
</FFormGroup>
</Stack>
);
}
export function StripeIntegrationEditFormFooter() {
const { name } = useDrawerContext();
const { closeDrawer } = useDrawerActions();
const { submitForm } = useFormikContext();
const handleSubmitBtnClick = () => {
submitForm();
};
const handleCancelBtnClick = () => {
closeDrawer(name);
};
return (
<>
<Group spacing={10}>
<Button intent={Intent.PRIMARY} onClick={handleSubmitBtnClick}>
Save
</Button>
<Button onClick={handleCancelBtnClick}>Cancel</Button>
</Group>
</>
);
}