mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 07:10:33 +00:00
feat: Delete Stripe payment method
This commit is contained in:
@@ -27,7 +27,7 @@ export class PaymentServicesController extends BaseController {
|
|||||||
param('paymentMethodId').exists(),
|
param('paymentMethodId').exists(),
|
||||||
body('name').optional().isString(),
|
body('name').optional().isString(),
|
||||||
body('options.bankAccountId').optional().isNumeric(),
|
body('options.bankAccountId').optional().isNumeric(),
|
||||||
body('options.clearningAccountId').optional().isNumeric(),
|
body('options.clearingAccountId').optional().isNumeric(),
|
||||||
body('options.showVisa').optional().isBoolean(),
|
body('options.showVisa').optional().isBoolean(),
|
||||||
body('options.showMasterCard').optional().isBoolean(),
|
body('options.showMasterCard').optional().isBoolean(),
|
||||||
body('options.showDiscover').optional().isBoolean(),
|
body('options.showDiscover').optional().isBoolean(),
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export class GetPaymentMethodsStateService {
|
|||||||
const isStripeAccountCreated = !!stripePayment;
|
const isStripeAccountCreated = !!stripePayment;
|
||||||
const isStripePaymentActive = !!(stripePayment?.active || null);
|
const isStripePaymentActive = !!(stripePayment?.active || null);
|
||||||
|
|
||||||
|
const stripePaymentMethodId = stripePayment?.id || null;
|
||||||
const stripeAccountId = stripePayment?.accountId || null;
|
const stripeAccountId = stripePayment?.accountId || null;
|
||||||
const stripePublishableKey = config.stripePayment.publishableKey;
|
const stripePublishableKey = config.stripePayment.publishableKey;
|
||||||
const stripeCurrencies = ['USD', 'EUR'];
|
const stripeCurrencies = ['USD', 'EUR'];
|
||||||
@@ -36,6 +37,7 @@ export class GetPaymentMethodsStateService {
|
|||||||
isStripeAccountCreated,
|
isStripeAccountCreated,
|
||||||
isStripePaymentActive,
|
isStripePaymentActive,
|
||||||
stripeAccountId,
|
stripeAccountId,
|
||||||
|
stripePaymentMethodId,
|
||||||
stripePublishableKey,
|
stripePublishableKey,
|
||||||
stripeCurrencies,
|
stripeCurrencies,
|
||||||
stripeRedirectUrl,
|
stripeRedirectUrl,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export interface GetPaymentMethodsPOJO {
|
|||||||
isStripeAccountCreated: boolean;
|
isStripeAccountCreated: boolean;
|
||||||
isStripePaymentActive: boolean;
|
isStripePaymentActive: boolean;
|
||||||
stripeAccountId: string | null;
|
stripeAccountId: string | null;
|
||||||
|
stripePaymentMethodId: number | null;
|
||||||
stripePublishableKey: string | null;
|
stripePublishableKey: string | null;
|
||||||
stripeCurrencies: Array<string>;
|
stripeCurrencies: Array<string>;
|
||||||
stripeRedirectUrl: string | null;
|
stripeRedirectUrl: string | null;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { BankRulesAlerts } from '../Banking/Rules/RulesList/BankRulesAlerts';
|
|||||||
import { SubscriptionAlerts } from '../Subscriptions/alerts/alerts';
|
import { SubscriptionAlerts } from '../Subscriptions/alerts/alerts';
|
||||||
import { BankAccountAlerts } from '@/containers/CashFlow/AccountTransactions/alerts';
|
import { BankAccountAlerts } from '@/containers/CashFlow/AccountTransactions/alerts';
|
||||||
import { BrandingTemplatesAlerts } from '../BrandingTemplates/alerts/BrandingTemplatesAlerts';
|
import { BrandingTemplatesAlerts } from '../BrandingTemplates/alerts/BrandingTemplatesAlerts';
|
||||||
|
import { PaymentMethodsAlerts } from '../Preferences/PaymentMethods/alerts/PaymentMethodsAlerts';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
...AccountsAlerts,
|
...AccountsAlerts,
|
||||||
@@ -63,4 +64,5 @@ export default [
|
|||||||
...SubscriptionAlerts,
|
...SubscriptionAlerts,
|
||||||
...BankAccountAlerts,
|
...BankAccountAlerts,
|
||||||
...BrandingTemplatesAlerts,
|
...BrandingTemplatesAlerts,
|
||||||
|
...PaymentMethodsAlerts,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Button, Classes, Intent, Text } from '@blueprintjs/core';
|
import {
|
||||||
|
Button,
|
||||||
|
Classes,
|
||||||
|
Intent,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Popover,
|
||||||
|
Tag,
|
||||||
|
Text,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
import { AppToaster, Box, Card, Group, Stack } from '@/components';
|
import { AppToaster, Box, Card, Group, Stack } from '@/components';
|
||||||
import { StripeLogo } from '@/icons/StripeLogo';
|
import { StripeLogo } from '@/icons/StripeLogo';
|
||||||
import {
|
import {
|
||||||
@@ -9,10 +18,15 @@ import {
|
|||||||
} from './PreferencesPaymentMethodsBoot';
|
} from './PreferencesPaymentMethodsBoot';
|
||||||
import { StripePreSetupDialog } from './dialogs/StripePreSetupDialog/StripePreSetupDialog';
|
import { StripePreSetupDialog } from './dialogs/StripePreSetupDialog/StripePreSetupDialog';
|
||||||
import { DialogsName } from '@/constants/dialogs';
|
import { DialogsName } from '@/constants/dialogs';
|
||||||
import { useDialogActions, useDrawerActions } from '@/hooks/state';
|
import {
|
||||||
|
useAlertActions,
|
||||||
|
useDialogActions,
|
||||||
|
useDrawerActions,
|
||||||
|
} from '@/hooks/state';
|
||||||
import { useCreateStripeAccountLink } from '@/hooks/query/stripe-integration';
|
import { useCreateStripeAccountLink } from '@/hooks/query/stripe-integration';
|
||||||
import { StripeIntegrationEditDrawer } from './drawers/StripeIntegrationEditDrawer';
|
import { StripeIntegrationEditDrawer } from './drawers/StripeIntegrationEditDrawer';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
|
import { MoreIcon } from '@/icons/More';
|
||||||
|
|
||||||
export default function PreferencesPaymentMethodsPage() {
|
export default function PreferencesPaymentMethodsPage() {
|
||||||
return (
|
return (
|
||||||
@@ -26,12 +40,12 @@ export default function PreferencesPaymentMethodsPage() {
|
|||||||
<Stack>
|
<Stack>
|
||||||
<StripePaymentMethod />
|
<StripePaymentMethod />
|
||||||
</Stack>
|
</Stack>
|
||||||
</PaymentMethodsBoot>
|
|
||||||
|
|
||||||
<StripePreSetupDialog dialogName={DialogsName.StripeSetup} />
|
<StripePreSetupDialog dialogName={DialogsName.StripeSetup} />
|
||||||
<StripeIntegrationEditDrawer
|
<StripeIntegrationEditDrawer
|
||||||
name={DRAWERS.STRIPE_PAYMENT_INTEGRATION_EDIT}
|
name={DRAWERS.STRIPE_PAYMENT_INTEGRATION_EDIT}
|
||||||
/>
|
/>
|
||||||
|
</PaymentMethodsBoot>
|
||||||
</PaymentMethodsRoot>
|
</PaymentMethodsRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -39,12 +53,15 @@ export default function PreferencesPaymentMethodsPage() {
|
|||||||
function StripePaymentMethod() {
|
function StripePaymentMethod() {
|
||||||
const { openDialog } = useDialogActions();
|
const { openDialog } = useDialogActions();
|
||||||
const { openDrawer } = useDrawerActions();
|
const { openDrawer } = useDrawerActions();
|
||||||
|
const { openAlert } = useAlertActions();
|
||||||
|
|
||||||
const { paymentMethodsState } = usePaymentMethodsBoot();
|
const { paymentMethodsState } = usePaymentMethodsBoot();
|
||||||
const stripeState = paymentMethodsState?.stripe;
|
const stripeState = paymentMethodsState?.stripe;
|
||||||
|
|
||||||
const isAccountCreated = stripeState?.isStripeAccountCreated;
|
const isAccountCreated = stripeState?.isStripeAccountCreated;
|
||||||
const isAccountActive = stripeState?.isStripePaymentActive;
|
const isAccountActive = stripeState?.isStripePaymentActive;
|
||||||
const stripeAccountId = stripeState?.stripeAccountId;
|
const stripeAccountId = stripeState?.stripeAccountId;
|
||||||
|
const stripePaymentMethodId = stripeState?.stripePaymentMethodId;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mutateAsync: createStripeAccountLink,
|
mutateAsync: createStripeAccountLink,
|
||||||
@@ -78,10 +95,24 @@ function StripePaymentMethod() {
|
|||||||
openDrawer(DRAWERS.STRIPE_PAYMENT_INTEGRATION_EDIT);
|
openDrawer(DRAWERS.STRIPE_PAYMENT_INTEGRATION_EDIT);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeleteConnectionClick = () => {
|
||||||
|
openAlert('delete-stripe-payment-method', {
|
||||||
|
paymentMethodId: stripePaymentMethodId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card style={{ margin: 0 }}>
|
<Card style={{ margin: 0 }}>
|
||||||
<Group position="apart">
|
<Group position="apart">
|
||||||
|
<Group>
|
||||||
<StripeLogo />
|
<StripeLogo />
|
||||||
|
|
||||||
|
{isAccountActive && (
|
||||||
|
<Tag minimal intent={Intent.SUCCESS}>
|
||||||
|
Active
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
<Group spacing={10}>
|
<Group spacing={10}>
|
||||||
<Button small onClick={handleEditBtnClick}>
|
<Button small onClick={handleEditBtnClick}>
|
||||||
Edit
|
Edit
|
||||||
@@ -102,6 +133,22 @@ function StripePaymentMethod() {
|
|||||||
Complete Stripe Set Up
|
Complete Stripe Set Up
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isAccountCreated && (
|
||||||
|
<Popover
|
||||||
|
content={
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
text={'Delete Connection'}
|
||||||
|
onClick={handleDeleteConnectionClick}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button small icon={<MoreIcon size={16} />} />
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { AppToaster, FormattedMessage as T } from '@/components';
|
||||||
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
import { useDeletePaymentMethod } from '@/hooks/query/payment-methods';
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete Stripe connection alert.
|
||||||
|
*/
|
||||||
|
function DeleteStripeAccountAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { paymentMethodId },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { isLoading, mutateAsync: deletePaymentMethod } =
|
||||||
|
useDeletePaymentMethod();
|
||||||
|
|
||||||
|
// Handle cancel open bill alert.
|
||||||
|
const handleCancelOpenBill = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle confirm bill open.
|
||||||
|
const handleConfirmBillOpen = () => {
|
||||||
|
deletePaymentMethod({ paymentMethodId })
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'The Stripe payment account has been deleted.',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
closeAlert(name);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
closeAlert(name);
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'Something went wrong.',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={'Delete Account'}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelOpenBill}
|
||||||
|
onConfirm={handleConfirmBillOpen}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>Are you sure want to delete your Stripe account connection?</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(DeleteStripeAccountAlert);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const DeleteStripeConnectionAlert = React.lazy(
|
||||||
|
() => import('./DeleteStripeConnectionAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const PaymentMethodsAlerts = [
|
||||||
|
{
|
||||||
|
name: 'delete-stripe-payment-method',
|
||||||
|
component: DeleteStripeConnectionAlert,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { Formik, FormikHelpers } from 'formik';
|
import { Formik, FormikHelpers } from 'formik';
|
||||||
|
import { useUpdatePaymentMethod } from '@/hooks/query/payment-services';
|
||||||
|
import { AppToaster } from '@/components';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { usePaymentMethodsBoot } from '../PreferencesPaymentMethodsBoot';
|
||||||
|
import { useDrawerActions } from '@/hooks/state';
|
||||||
|
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
|
||||||
|
|
||||||
interface StripeIntegrationFormValues {
|
interface StripeIntegrationFormValues {
|
||||||
paymentAccountId: string;
|
paymentAccountId: string;
|
||||||
@@ -24,10 +30,34 @@ interface StripeIntegrationEditFormProps {
|
|||||||
export function StripeIntegrationEditForm({
|
export function StripeIntegrationEditForm({
|
||||||
children,
|
children,
|
||||||
}: StripeIntegrationEditFormProps) {
|
}: StripeIntegrationEditFormProps) {
|
||||||
|
const { closeDrawer } = useDrawerActions();
|
||||||
|
const { name } = useDrawerContext();
|
||||||
|
const { mutateAsync: updatePaymentMethod } = useUpdatePaymentMethod();
|
||||||
|
const { paymentMethodsState } = usePaymentMethodsBoot();
|
||||||
|
const stripePaymentState = paymentMethodsState?.stripe;
|
||||||
|
const paymentMethodId = stripePaymentState?.stripePaymentMethodId;
|
||||||
|
|
||||||
const onSubmit = (
|
const onSubmit = (
|
||||||
values: StripeIntegrationFormValues,
|
values: StripeIntegrationFormValues,
|
||||||
{ setSubmitting }: FormikHelpers<StripeIntegrationFormValues>,
|
{ setSubmitting }: FormikHelpers<StripeIntegrationFormValues>,
|
||||||
) => {
|
) => {
|
||||||
|
setSubmitting(true);
|
||||||
|
updatePaymentMethod({ paymentMethodId, values })
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'The Stripe settings have been updated.',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
setSubmitting(false);
|
||||||
|
closeDrawer(name);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setSubmitting(false);
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'Something went wrong.',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function StripeIntegrationEditFormContent() {
|
|||||||
export function StripeIntegrationEditFormFooter() {
|
export function StripeIntegrationEditFormFooter() {
|
||||||
const { name } = useDrawerContext();
|
const { name } = useDrawerContext();
|
||||||
const { closeDrawer } = useDrawerActions();
|
const { closeDrawer } = useDrawerActions();
|
||||||
const { submitForm } = useFormikContext();
|
const { submitForm, isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
const handleSubmitBtnClick = () => {
|
const handleSubmitBtnClick = () => {
|
||||||
submitForm();
|
submitForm();
|
||||||
@@ -56,7 +56,11 @@ export function StripeIntegrationEditFormFooter() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Group spacing={10}>
|
<Group spacing={10}>
|
||||||
<Button intent={Intent.PRIMARY} onClick={handleSubmitBtnClick}>
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
loading={isSubmitting}
|
||||||
|
onClick={handleSubmitBtnClick}
|
||||||
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleCancelBtnClick}>Cancel</Button>
|
<Button onClick={handleCancelBtnClick}>Cancel</Button>
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
|
import {
|
||||||
|
useMutation,
|
||||||
|
useQuery,
|
||||||
|
UseQueryOptions,
|
||||||
|
UseQueryResult,
|
||||||
|
} from 'react-query';
|
||||||
import useApiRequest from '../useRequest';
|
import useApiRequest from '../useRequest';
|
||||||
import { transformToCamelCase } from '@/utils';
|
import { transformToCamelCase, transfromToSnakeCase } from '@/utils';
|
||||||
|
|
||||||
const PaymentServicesQueryKey = 'PaymentServices';
|
const PaymentServicesQueryKey = 'PaymentServices';
|
||||||
|
|
||||||
@@ -37,7 +42,8 @@ export interface GetPaymentServicesStateResponse {
|
|||||||
stripe: {
|
stripe: {
|
||||||
isStripeAccountCreated: boolean;
|
isStripeAccountCreated: boolean;
|
||||||
isStripePaymentActive: boolean;
|
isStripePaymentActive: boolean;
|
||||||
stripeAccountId: string;
|
stripeAccountId: string | null;
|
||||||
|
stripePaymentMethodId: number | null;
|
||||||
stripeCurrencies: string[];
|
stripeCurrencies: string[];
|
||||||
stripePublishableKey: string;
|
stripePublishableKey: string;
|
||||||
stripeRedirectUrl: string;
|
stripeRedirectUrl: string;
|
||||||
@@ -69,3 +75,42 @@ export const useGetPaymentServicesState = (
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface UpdatePaymentMethodResponse {
|
||||||
|
id: number;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
interface UpdatePaymentMethodValues {
|
||||||
|
paymentMethodId: string | number;
|
||||||
|
values: {
|
||||||
|
name: string;
|
||||||
|
bankAccountId: number;
|
||||||
|
clearingAccountId: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Updates a payment method.
|
||||||
|
* @returns {UseMutationResult<UpdatePaymentMethodResponse, Error, UpdatePaymentMethodValues, unknown>}
|
||||||
|
*/
|
||||||
|
export const useUpdatePaymentMethod = (): UseMutationResult<
|
||||||
|
UpdatePaymentMethodResponse,
|
||||||
|
Error,
|
||||||
|
UpdatePaymentMethodValues,
|
||||||
|
unknown
|
||||||
|
> => {
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation<
|
||||||
|
UpdatePaymentMethodResponse,
|
||||||
|
Error,
|
||||||
|
UpdatePaymentMethodValues,
|
||||||
|
unknown
|
||||||
|
>((data: UpdatePaymentMethodValues) =>
|
||||||
|
apiRequest
|
||||||
|
.post(
|
||||||
|
`/payment-services/${data.paymentMethodId}`,
|
||||||
|
transfromToSnakeCase(data.values),
|
||||||
|
)
|
||||||
|
.then((response) => response.data),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
closeDialog,
|
closeDialog,
|
||||||
openDrawer,
|
openDrawer,
|
||||||
closeDrawer,
|
closeDrawer,
|
||||||
|
openAlert,
|
||||||
|
closeAlert,
|
||||||
} from '@/store/dashboard/dashboard.actions';
|
} from '@/store/dashboard/dashboard.actions';
|
||||||
|
|
||||||
export const useDispatchAction = (action) => {
|
export const useDispatchAction = (action) => {
|
||||||
@@ -86,3 +88,10 @@ export const useDrawerActions = () => {
|
|||||||
closeDrawer: useDispatchAction(closeDrawer),
|
closeDrawer: useDispatchAction(closeDrawer),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useAlertActions = () => {
|
||||||
|
return {
|
||||||
|
openAlert: useDispatchAction(openAlert),
|
||||||
|
closeAlert: useDispatchAction(closeAlert),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
26
packages/webapp/src/icons/More.tsx
Normal file
26
packages/webapp/src/icons/More.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface MoreIconProps extends React.SVGProps<SVGSVGElement> {
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MoreIcon: React.FC<MoreIconProps> = ({ size = 16, ...props }) => (
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Layer_1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox={`0 0 ${size} ${size}`}
|
||||||
|
enableBackground={`new 0 0 ${size} ${size}`}
|
||||||
|
xmlSpace="preserve"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<g id="more_3_">
|
||||||
|
<circle cx={size / 8} cy={size / 2.00625} r={size / 8} />
|
||||||
|
<circle cx={size - size / 8} cy={size / 2.00625} r={size / 8} />
|
||||||
|
<circle cx={size / 2} cy={size / 2.00625} r={size / 8} />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user