diff --git a/packages/backend/src/graphql/queries/get-billing-and-usage.ee.js b/packages/backend/src/graphql/queries/get-billing-and-usage.ee.js deleted file mode 100644 index d23a44e5..00000000 --- a/packages/backend/src/graphql/queries/get-billing-and-usage.ee.js +++ /dev/null @@ -1,101 +0,0 @@ -import { DateTime } from 'luxon'; -import Billing from '../../helpers/billing/index.ee.js'; -import ExecutionStep from '../../models/execution-step.js'; - -const getBillingAndUsage = async (_parent, _params, context) => { - const persistedSubscription = await context.currentUser.$relatedQuery( - 'currentSubscription' - ); - - const subscription = persistedSubscription - ? paidSubscription(persistedSubscription) - : freeTrialSubscription(); - - return { - subscription, - usage: { - task: executionStepCount(context), - }, - }; -}; - -const paidSubscription = (subscription) => { - const currentPlan = Billing.paddlePlans.find( - (plan) => plan.productId === subscription.paddlePlanId - ); - - return { - status: subscription.status, - monthlyQuota: { - title: currentPlan.limit, - action: { - type: 'link', - text: 'Cancel plan', - src: subscription.cancelUrl, - }, - }, - nextBillAmount: { - title: subscription.nextBillAmount - ? '€' + subscription.nextBillAmount - : '---', - action: { - type: 'link', - text: 'Update payment method', - src: subscription.updateUrl, - }, - }, - nextBillDate: { - title: subscription.nextBillDate ? subscription.nextBillDate : '---', - action: { - type: 'text', - text: '(monthly payment)', - }, - }, - }; -}; - -const freeTrialSubscription = () => { - return { - status: null, - monthlyQuota: { - title: 'Free Trial', - action: { - type: 'link', - text: 'Upgrade plan', - src: '/settings/billing/upgrade', - }, - }, - nextBillAmount: { - title: '---', - action: null, - }, - nextBillDate: { - title: '---', - action: null, - }, - }; -}; - -const executionIds = async (context) => { - return ( - await context.currentUser - .$relatedQuery('executions') - .select('executions.id') - ).map((execution) => execution.id); -}; - -const executionStepCount = async (context) => { - const executionStepCount = await ExecutionStep.query() - .whereIn('execution_id', await executionIds(context)) - .andWhere( - 'created_at', - '>=', - DateTime.now().minus({ days: 30 }).toISODate() - ) - .count() - .first(); - - return executionStepCount.count; -}; - -export default getBillingAndUsage; diff --git a/packages/backend/src/graphql/query-resolvers.js b/packages/backend/src/graphql/query-resolvers.js index abde6d6b..68308897 100644 --- a/packages/backend/src/graphql/query-resolvers.js +++ b/packages/backend/src/graphql/query-resolvers.js @@ -1,5 +1,4 @@ import getAppAuthClient from './queries/get-app-auth-client.ee.js'; -import getBillingAndUsage from './queries/get-billing-and-usage.ee.js'; import getConnectedApps from './queries/get-connected-apps.js'; import getDynamicData from './queries/get-dynamic-data.js'; import getStepWithTestExecutions from './queries/get-step-with-test-executions.js'; @@ -7,7 +6,6 @@ import testConnection from './queries/test-connection.js'; const queryResolvers = { getAppAuthClient, - getBillingAndUsage, getConnectedApps, getDynamicData, getStepWithTestExecutions, diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 9714bde8..17e35206 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -8,7 +8,6 @@ type Query { key: String! parameters: JSONObject ): JSONObject - getBillingAndUsage: GetBillingAndUsage } type Mutation { @@ -560,43 +559,6 @@ type License { verified: Boolean } -type GetBillingAndUsage { - subscription: Subscription - usage: Usage -} - -type MonthlyQuota { - title: String - action: BillingCardAction -} - -type NextBillAmount { - title: String - action: BillingCardAction -} - -type NextBillDate { - title: String - action: BillingCardAction -} - -type BillingCardAction { - type: String - text: String - src: String -} - -type Subscription { - status: String - monthlyQuota: MonthlyQuota - nextBillAmount: NextBillAmount - nextBillDate: NextBillDate -} - -type Usage { - task: Int -} - type Permission { id: String action: String diff --git a/packages/web/src/components/CheckoutCompletedAlert/index.ee.jsx b/packages/web/src/components/CheckoutCompletedAlert/index.ee.jsx index 2bc213e8..d236987a 100644 --- a/packages/web/src/components/CheckoutCompletedAlert/index.ee.jsx +++ b/packages/web/src/components/CheckoutCompletedAlert/index.ee.jsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { useLocation } from 'react-router-dom'; import Alert from '@mui/material/Alert'; import Typography from '@mui/material/Typography'; + import useFormatMessage from 'hooks/useFormatMessage'; export default function CheckoutCompletedAlert() { @@ -9,7 +10,9 @@ export default function CheckoutCompletedAlert() { const location = useLocation(); const state = location.state; const checkoutCompleted = state?.checkoutCompleted; + if (!checkoutCompleted) return ; + return ( ; + if (subscription?.data?.status === 'active' || trial.hasTrial) + return ; + + const cancellationEffectiveDateObject = DateTime.fromISO( + subscription?.data?.cancellationEffectiveDate, + ); return ( - {subscription.message} + {formatMessage('subscriptionCancelledAlert.text', { + date: cancellationEffectiveDateObject.toFormat('DDD'), + })} ); diff --git a/packages/web/src/components/TrialStatusBadge/index.ee.jsx b/packages/web/src/components/TrialStatusBadge/index.ee.jsx index f4ab263f..d31d9e66 100644 --- a/packages/web/src/components/TrialStatusBadge/index.ee.jsx +++ b/packages/web/src/components/TrialStatusBadge/index.ee.jsx @@ -8,7 +8,7 @@ import useUserTrial from 'hooks/useUserTrial.ee'; export default function TrialStatusBadge() { const data = useUserTrial(); - if (!data) return ; + if (!data.hasTrial) return ; const { message, status } = data; diff --git a/packages/web/src/components/UsageDataInformation/index.ee.jsx b/packages/web/src/components/UsageDataInformation/index.ee.jsx index bbc5995f..cfacc841 100644 --- a/packages/web/src/components/UsageDataInformation/index.ee.jsx +++ b/packages/web/src/components/UsageDataInformation/index.ee.jsx @@ -10,15 +10,23 @@ import CardContent from '@mui/material/CardContent'; import Divider from '@mui/material/Divider'; import Grid from '@mui/material/Grid'; import Typography from '@mui/material/Typography'; + 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'; import useFormatMessage from 'hooks/useFormatMessage'; +import usePlanAndUsage from 'hooks/usePlanAndUsage'; +import useSubscription from 'hooks/useSubscription.ee'; +import useUserTrial from 'hooks/useUserTrial.ee'; +import { useQueryClient } from '@tanstack/react-query'; +import useCurrentUser from 'hooks/useCurrentUser'; + const capitalize = (str) => str[0].toUpperCase() + str.slice(1, str.length); + function BillingCard(props) { - const { name, title = '', action } = props; + const { name, title = '', action, text } = props; + return ( - + ); } + function Action(props) { - const { action } = props; + const { action, text } = props; + if (!action) return ; - const { text, type } = action; - if (type === 'link') { - if (action.src.startsWith('http')) { - return ( - - ); - } else { - return ( - - ); - } - } - if (type === 'text') { + + if (action.startsWith('http')) { return ( - + + ); + } else if (action.startsWith('/')) { + return ( + ); } - return ; + + return ( + + {text} + + ); } + export default function UsageDataInformation() { const formatMessage = useFormatMessage(); - const billingAndUsageData = useBillingAndUsageData(); + const queryClient = useQueryClient(); + const { data } = usePlanAndUsage(); + const planAndUsage = data?.data; + const { data: currentUser } = useCurrentUser(); + const currentUserId = currentUser?.data?.id; + const trial = useUserTrial(); + const subscriptionData = useSubscription(); + const subscription = subscriptionData?.data; + let billingInfo; + + React.useEffect(() => { + queryClient.invalidateQueries({ + queryKey: ['planAndUsage', currentUserId], + }); + }, [subscription, queryClient, currentUserId]); + + if (trial.hasTrial) { + billingInfo = { + monthlyQuota: { + title: formatMessage('usageDataInformation.freeTrial'), + action: URLS.SETTINGS_PLAN_UPGRADE, + text: 'Upgrade plan', + }, + nextBillAmount: { + title: '---', + action: null, + text: null, + }, + nextBillDate: { + title: '---', + action: null, + text: null, + }, + }; + } else { + billingInfo = { + monthlyQuota: { + title: planAndUsage?.plan?.limit, + action: subscription?.cancelUrl, + text: formatMessage('usageDataInformation.cancelPlan'), + }, + nextBillAmount: { + title: `€${subscription?.nextBillAmount}`, + action: subscription?.updateUrl, + text: formatMessage('usageDataInformation.updatePaymentMethod'), + }, + nextBillDate: { + title: subscription?.nextBillDate, + action: formatMessage('usageDataInformation.monthlyPayment'), + text: formatMessage('usageDataInformation.monthlyPayment'), + }, + }; + } + return ( @@ -92,11 +152,8 @@ export default function UsageDataInformation() { {formatMessage('usageDataInformation.subscriptionPlan')} - {billingAndUsageData?.subscription?.status && ( - + {subscription?.status && ( + )} @@ -113,26 +170,27 @@ export default function UsageDataInformation() { @@ -171,7 +229,7 @@ export default function UsageDataInformation() { variant="subtitle2" sx={{ color: 'text.secondary', mt: 2, fontWeight: 500 }} > - {billingAndUsageData?.usage.task} + {planAndUsage?.usage.task} @@ -179,7 +237,7 @@ export default function UsageDataInformation() { {/* free plan has `null` status so that we can show the upgrade button */} - {billingAndUsageData?.subscription?.status === null && ( + {subscription?.status === undefined && (