diff --git a/packages/backend/src/models/subscription.ee.ts b/packages/backend/src/models/subscription.ee.ts
index a8d9de60..cf7f1939 100644
--- a/packages/backend/src/models/subscription.ee.ts
+++ b/packages/backend/src/models/subscription.ee.ts
@@ -1,6 +1,7 @@
import Base from './base';
import User from './user';
import UsageData from './usage-data.ee';
+import { DateTime } from 'luxon';
import { getPlanById } from '../helpers/billing/plans.ee';
class Subscription extends Base {
@@ -79,8 +80,20 @@ class Subscription extends Base {
return getPlanById(this.paddlePlanId);
}
- get isActive() {
- return this.status === 'active' || this.status === 'past_due';
+ get isCancelledAndValid() {
+ return (
+ this.status === 'deleted' &&
+ Number(this.cancellationEffectiveDate) >
+ DateTime.now().startOf('day').toMillis()
+ );
+ }
+
+ get isValid() {
+ if (this.status === 'active') return true;
+ if (this.status === 'past_due') return true;
+ if (this.isCancelledAndValid) return true;
+
+ return false;
}
}
diff --git a/packages/backend/src/models/user.ts b/packages/backend/src/models/user.ts
index a13402e3..d5b4f0d3 100644
--- a/packages/backend/src/models/user.ts
+++ b/packages/backend/src/models/user.ts
@@ -209,7 +209,7 @@ class User extends Base {
const subscription = await this.$relatedQuery('currentSubscription');
- return subscription?.isActive;
+ return subscription?.isValid;
}
async withinLimits() {
diff --git a/packages/web/src/components/SubscriptionCancelledAlert/index.ee.tsx b/packages/web/src/components/SubscriptionCancelledAlert/index.ee.tsx
new file mode 100644
index 00000000..e65da282
--- /dev/null
+++ b/packages/web/src/components/SubscriptionCancelledAlert/index.ee.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react';
+import Alert from '@mui/material/Alert';
+import Typography from '@mui/material/Typography';
+
+import useSubscriptionStatus from 'hooks/useSubscriptionStatus.ee';
+
+export default function SubscriptionCancelledAlert() {
+ const subscriptionStatus = useSubscriptionStatus();
+
+ if (!subscriptionStatus) return ;
+
+ return (
+
+
+ {subscriptionStatus.message}
+
+
+ );
+}
diff --git a/packages/web/src/components/UsageDataInformation/index.ee.tsx b/packages/web/src/components/UsageDataInformation/index.ee.tsx
index 275c8cc6..2d247fb7 100644
--- a/packages/web/src/components/UsageDataInformation/index.ee.tsx
+++ b/packages/web/src/components/UsageDataInformation/index.ee.tsx
@@ -13,6 +13,7 @@ import Typography from '@mui/material/Typography';
import { TBillingCardAction } from '@automatisch/types';
import TrialOverAlert from 'components/TrialOverAlert/index.ee';
+import SubscriptionCancelledAlert from 'components/SubscriptionCancelledAlert/index.ee';
import CheckoutCompletedAlert from 'components/CheckoutCompletedAlert/index.ee';
import * as URLS from 'config/urls';
import useBillingAndUsageData from 'hooks/useBillingAndUsageData.ee';
@@ -97,6 +98,8 @@ export default function UsageDataInformation() {
return (
+
+
diff --git a/packages/web/src/graphql/queries/get-subscription-status.ee.ts b/packages/web/src/graphql/queries/get-subscription-status.ee.ts
new file mode 100644
index 00000000..9b799c8f
--- /dev/null
+++ b/packages/web/src/graphql/queries/get-subscription-status.ee.ts
@@ -0,0 +1,9 @@
+import { gql } from '@apollo/client';
+
+export const GET_SUBSCRIPTION_STATUS = gql`
+ query GetSubscriptionStatus {
+ getSubscriptionStatus {
+ cancellationEffectiveDate
+ }
+ }
+`;
diff --git a/packages/web/src/hooks/useSubscriptionStatus.ee.ts b/packages/web/src/hooks/useSubscriptionStatus.ee.ts
new file mode 100644
index 00000000..853c6424
--- /dev/null
+++ b/packages/web/src/hooks/useSubscriptionStatus.ee.ts
@@ -0,0 +1,31 @@
+import { useQuery } from '@apollo/client';
+import { DateTime } from 'luxon';
+
+import { GET_SUBSCRIPTION_STATUS } from 'graphql/queries/get-subscription-status.ee';
+import useFormatMessage from './useFormatMessage';
+
+type UseSubscriptionStatusReturn = {
+ cancellationEffectiveDate: DateTime;
+ message: string;
+} | null;
+
+export default function useSubscriptionStatus(): UseSubscriptionStatusReturn {
+ const formatMessage = useFormatMessage();
+ const { data, loading, } = useQuery(GET_SUBSCRIPTION_STATUS);
+ const cancellationEffectiveDate = data?.getSubscriptionStatus?.cancellationEffectiveDate as string;
+ const hasCancelled = !!cancellationEffectiveDate;
+
+ if (loading || !hasCancelled) return null;
+
+ const cancellationEffectiveDateObject = DateTime.fromMillis(Number(cancellationEffectiveDate)).startOf('day');
+
+ return {
+ message: formatMessage(
+ 'subscriptionCancelledAlert.text',
+ {
+ date: cancellationEffectiveDateObject.toFormat('DDD')
+ }
+ ),
+ cancellationEffectiveDate: cancellationEffectiveDateObject,
+ };
+}
diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json
index 9eb2f990..e9804974 100644
--- a/packages/web/src/locales/en.json
+++ b/packages/web/src/locales/en.json
@@ -158,5 +158,6 @@
"trialBadge.endsToday": "Trial ends today",
"trialBadge.over": "Trial is over",
"trialOverAlert.text": "Your free trial is over. Please upgrade your plan to continue using Automatisch.",
- "checkoutCompletedAlert.text": "Thank you for upgrading your subscription and supporting our self-funded business!"
+ "checkoutCompletedAlert.text": "Thank you for upgrading your subscription and supporting our self-funded business!",
+ "subscriptionCancelledAlert.text": "Your subscription is cancelled, but you can continue using Automatisch until {date}."
}