315 lines
9.5 KiB
TypeScript
Executable File
315 lines
9.5 KiB
TypeScript
Executable File
import React, {Fragment} from 'react';
|
|
import {DataTablePage} from '../../datatable/page/data-table-page';
|
|
import {IconButton} from '../../ui/buttons/icon-button';
|
|
import {EditIcon} from '../../icons/material/Edit';
|
|
import {ColumnConfig} from '../../datatable/column-config';
|
|
import {Trans} from '../../i18n/trans';
|
|
import {DeleteSelectedItemsAction} from '../../datatable/page/delete-selected-items-action';
|
|
import {DataTableEmptyStateMessage} from '../../datatable/page/data-table-emty-state-message';
|
|
import {SubscriptionIndexPageFilters} from './subscription-index-page-filters';
|
|
import {DialogTrigger} from '../../ui/overlays/dialog/dialog-trigger';
|
|
import {DataTableAddItemButton} from '../../datatable/data-table-add-item-button';
|
|
import subscriptionsSvg from './subscriptions.svg';
|
|
import {NameWithAvatar} from '../../datatable/column-templates/name-with-avatar';
|
|
import {Subscription} from '../../billing/subscription';
|
|
import {CloseIcon} from '../../icons/material/Close';
|
|
import {FormattedDate} from '../../i18n/formatted-date';
|
|
import {UpdateSubscriptionDialog} from './update-subscription-dialog';
|
|
import {CreateSubscriptionDialog} from './create-subscription-dialog';
|
|
import {useCancelSubscription} from '../../billing/billing-page/requests/use-cancel-subscription';
|
|
import {PauseIcon} from '../../icons/material/Pause';
|
|
import {queryClient} from '../../http/query-client';
|
|
import {DatatableDataQueryKey} from '../../datatable/requests/paginated-resources';
|
|
import {Tooltip} from '../../ui/tooltip/tooltip';
|
|
import {useResumeSubscription} from '../../billing/billing-page/requests/use-resume-subscription';
|
|
import {PlayArrowIcon} from '../../icons/material/PlayArrow';
|
|
import {ConfirmationDialog} from '../../ui/overlays/dialog/confirmation-dialog';
|
|
import {Chip} from '../../ui/forms/input-field/chip-field/chip';
|
|
|
|
const endpoint = 'billing/subscriptions';
|
|
|
|
const columnConfig: ColumnConfig<Subscription>[] = [
|
|
{
|
|
key: 'user_id',
|
|
allowsSorting: true,
|
|
width: 'flex-3 min-w-200',
|
|
visibleInMode: 'all',
|
|
header: () => <Trans message="Customer" />,
|
|
body: subscription =>
|
|
subscription.user && (
|
|
<NameWithAvatar
|
|
image={subscription.user.avatar}
|
|
label={subscription.user.display_name}
|
|
description={subscription.user.email}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
key: 'status',
|
|
width: 'w-100 flex-shrink-0',
|
|
header: () => <Trans message="Status" />,
|
|
body: subscription => (
|
|
<Chip
|
|
size="xs"
|
|
color={subscription.valid ? 'positive' : undefined}
|
|
radius="rounded"
|
|
className="w-max"
|
|
>
|
|
{subscription.gateway_status}
|
|
</Chip>
|
|
),
|
|
},
|
|
{
|
|
key: 'product_id',
|
|
allowsSorting: true,
|
|
header: () => <Trans message="Plan" />,
|
|
body: subscription => subscription.product?.name,
|
|
},
|
|
{
|
|
key: 'gateway_name',
|
|
allowsSorting: true,
|
|
header: () => <Trans message="Gateway" />,
|
|
body: subscription => (
|
|
<span className="capitalize">{subscription.gateway_name}</span>
|
|
),
|
|
},
|
|
{
|
|
key: 'renews_at',
|
|
allowsSorting: true,
|
|
header: () => <Trans message="Renews at" />,
|
|
body: subscription => <FormattedDate date={subscription.renews_at} />,
|
|
},
|
|
{
|
|
key: 'ends_at',
|
|
allowsSorting: true,
|
|
header: () => <Trans message="Ends at" />,
|
|
body: subscription => <FormattedDate date={subscription.ends_at} />,
|
|
},
|
|
{
|
|
key: 'created_at',
|
|
allowsSorting: true,
|
|
header: () => <Trans message="Created at" />,
|
|
body: subscription => <FormattedDate date={subscription.created_at} />,
|
|
},
|
|
{
|
|
key: 'actions',
|
|
header: () => <Trans message="Actions" />,
|
|
hideHeader: true,
|
|
align: 'end',
|
|
visibleInMode: 'all',
|
|
width: 'w-[168px] flex-shrink-0',
|
|
body: subscription => {
|
|
return <SubscriptionActions subscription={subscription} />;
|
|
},
|
|
},
|
|
];
|
|
|
|
export function SubscriptionsIndexPage() {
|
|
return (
|
|
<DataTablePage
|
|
endpoint={endpoint}
|
|
title={<Trans message="Subscriptions" />}
|
|
columns={columnConfig}
|
|
filters={SubscriptionIndexPageFilters}
|
|
actions={<PageActions />}
|
|
enableSelection={false}
|
|
selectedActions={<DeleteSelectedItemsAction />}
|
|
queryParams={{with: 'product'}}
|
|
emptyStateMessage={
|
|
<DataTableEmptyStateMessage
|
|
image={subscriptionsSvg}
|
|
title={<Trans message="No subscriptions have been created yet" />}
|
|
filteringTitle={<Trans message="No matching subscriptions" />}
|
|
/>
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function PageActions() {
|
|
return (
|
|
<>
|
|
<DialogTrigger type="modal">
|
|
<DataTableAddItemButton>
|
|
<Trans message="Add new subscription" />
|
|
</DataTableAddItemButton>
|
|
<CreateSubscriptionDialog />
|
|
</DialogTrigger>
|
|
</>
|
|
);
|
|
}
|
|
|
|
interface SubscriptionActionsProps {
|
|
subscription: Subscription;
|
|
}
|
|
function SubscriptionActions({subscription}: SubscriptionActionsProps) {
|
|
return (
|
|
<Fragment>
|
|
<DialogTrigger type="modal">
|
|
<IconButton size="md" className="text-muted">
|
|
<EditIcon />
|
|
</IconButton>
|
|
<UpdateSubscriptionDialog subscription={subscription} />
|
|
</DialogTrigger>
|
|
{subscription.cancelled && subscription.on_grace_period ? (
|
|
<ResumeSubscriptionButton subscription={subscription} />
|
|
) : null}
|
|
{subscription.active ? (
|
|
<SuspendSubscriptionButton subscription={subscription} />
|
|
) : null}
|
|
<CancelSubscriptionButton subscription={subscription} />
|
|
</Fragment>
|
|
);
|
|
}
|
|
|
|
function SuspendSubscriptionButton({subscription}: SubscriptionActionsProps) {
|
|
const cancelSubscription = useCancelSubscription();
|
|
|
|
const handleSuspendSubscription = () => {
|
|
cancelSubscription.mutate(
|
|
{subscriptionId: subscription.id},
|
|
{
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: DatatableDataQueryKey(endpoint),
|
|
});
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
return (
|
|
<DialogTrigger
|
|
type="modal"
|
|
onClose={confirmed => {
|
|
if (confirmed) {
|
|
handleSuspendSubscription();
|
|
}
|
|
}}
|
|
>
|
|
<Tooltip label={<Trans message="Cancel subscription" />}>
|
|
<IconButton
|
|
size="md"
|
|
className="text-muted"
|
|
disabled={cancelSubscription.isPending}
|
|
>
|
|
<PauseIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<ConfirmationDialog
|
|
title={<Trans message="Cancel subscription" />}
|
|
body={
|
|
<div>
|
|
<Trans message="Are you sure you want to cancel this subscription?" />
|
|
<div className="mt-10 text-sm font-semibold">
|
|
<Trans message="This will put user on grace period until their next scheduled renewal date. Subscription can be renewed until that date by user or from admin area." />
|
|
</div>
|
|
</div>
|
|
}
|
|
confirm={<Trans message="Confirm" />}
|
|
/>
|
|
</DialogTrigger>
|
|
);
|
|
}
|
|
|
|
function ResumeSubscriptionButton({subscription}: SubscriptionActionsProps) {
|
|
const resumeSubscription = useResumeSubscription();
|
|
const handleResumeSubscription = () => {
|
|
resumeSubscription.mutate(
|
|
{subscriptionId: subscription.id},
|
|
{
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: DatatableDataQueryKey(endpoint),
|
|
});
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
return (
|
|
<DialogTrigger
|
|
type="modal"
|
|
onClose={confirmed => {
|
|
if (confirmed) {
|
|
handleResumeSubscription();
|
|
}
|
|
}}
|
|
>
|
|
<Tooltip label={<Trans message="Renew subscription" />}>
|
|
<IconButton
|
|
size="md"
|
|
className="text-muted"
|
|
onClick={handleResumeSubscription}
|
|
disabled={resumeSubscription.isPending}
|
|
>
|
|
<PlayArrowIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<ConfirmationDialog
|
|
title={<Trans message="Resume subscription" />}
|
|
body={
|
|
<div>
|
|
<Trans message="Are you sure you want to resume this subscription?" />
|
|
<div className="mt-10 text-sm font-semibold">
|
|
<Trans message="This will put user on their original plan and billing cycle." />
|
|
</div>
|
|
</div>
|
|
}
|
|
confirm={<Trans message="Confirm" />}
|
|
/>
|
|
</DialogTrigger>
|
|
);
|
|
}
|
|
|
|
function CancelSubscriptionButton({subscription}: SubscriptionActionsProps) {
|
|
const cancelSubscription = useCancelSubscription();
|
|
|
|
const handleDeleteSubscription = () => {
|
|
cancelSubscription.mutate(
|
|
{subscriptionId: subscription.id, delete: true},
|
|
{
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: DatatableDataQueryKey(endpoint),
|
|
});
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
return (
|
|
<DialogTrigger
|
|
type="modal"
|
|
onClose={confirmed => {
|
|
if (confirmed) {
|
|
handleDeleteSubscription();
|
|
}
|
|
}}
|
|
>
|
|
<Tooltip label={<Trans message="Delete subscription" />}>
|
|
<IconButton
|
|
size="md"
|
|
className="text-muted"
|
|
disabled={cancelSubscription.isPending}
|
|
>
|
|
<CloseIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<ConfirmationDialog
|
|
isDanger
|
|
title={<Trans message="Delete subscription" />}
|
|
body={
|
|
<div>
|
|
<Trans message="Are you sure you want to delete this subscription?" />
|
|
<div className="mt-10 text-sm font-semibold">
|
|
<Trans message="This will permanently delete the subscription and immediately cancel it on billing gateway. Subscription will not be renewable anymore." />
|
|
</div>
|
|
</div>
|
|
}
|
|
confirm={<Trans message="Confirm" />}
|
|
/>
|
|
</DialogTrigger>
|
|
);
|
|
}
|