Files
mtdb_movie/common/resources/client/admin/subscriptions/subscriptions-index-page.tsx
maher 703f50a09d
Some checks failed
Build / run (push) Has been cancelled
first commit
2025-10-29 11:42:25 +01:00

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>
);
}