From d4c542168c932fe5373c9419f7bdd41fe0985be0 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Sat, 8 Apr 2023 10:10:51 +0000 Subject: [PATCH 1/3] feat: add trial status badge in appbar --- .../graphql/queries/get-trial-status.ee.ts | 4 +- packages/backend/src/models/user.ts | 14 +++- packages/web/src/components/AppBar/index.tsx | 4 +- .../components/TrialStatusBadge/index.ee.tsx | 24 +++++++ .../components/TrialStatusBadge/style.ee.tsx | 13 ++++ .../graphql/queries/get-trial-status.ee.ts | 9 +++ .../src/hooks/useBillingAndUsageData.ee.ts | 2 +- packages/web/src/hooks/useTrialStatus.ee.ts | 64 +++++++++++++++++++ packages/web/src/locales/en.json | 5 +- 9 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 packages/web/src/components/TrialStatusBadge/index.ee.tsx create mode 100644 packages/web/src/components/TrialStatusBadge/style.ee.tsx create mode 100644 packages/web/src/graphql/queries/get-trial-status.ee.ts create mode 100644 packages/web/src/hooks/useTrialStatus.ee.ts diff --git a/packages/backend/src/graphql/queries/get-trial-status.ee.ts b/packages/backend/src/graphql/queries/get-trial-status.ee.ts index 58a8f936..77bc84ee 100644 --- a/packages/backend/src/graphql/queries/get-trial-status.ee.ts +++ b/packages/backend/src/graphql/queries/get-trial-status.ee.ts @@ -9,7 +9,9 @@ const getTrialStatus = async ( if (!appConfig.isCloud) return; const inTrial = await context.currentUser.inTrial(); - if (!inTrial) return; + const hasActiveSubscription = await context.currentUser.hasActiveSubscription(); + + if (!inTrial && hasActiveSubscription) return; return { expireAt: context.currentUser.trialExpiryDate, diff --git a/packages/backend/src/models/user.ts b/packages/backend/src/models/user.ts index f83b6c01..daba8d74 100644 --- a/packages/backend/src/models/user.ts +++ b/packages/backend/src/models/user.ts @@ -165,6 +165,16 @@ class User extends Base { this.trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate(); } + async hasActiveSubscription() { + if (!appConfig.isCloud) { + return false; + } + + const subscription = await this.$relatedQuery('currentSubscription'); + + return subscription?.isActive; + } + async inTrial() { if (!appConfig.isCloud) { return false; @@ -174,9 +184,7 @@ class User extends Base { return false; } - const subscription = await this.$relatedQuery('currentSubscription'); - - if (subscription?.isActive) { + if (await this.hasActiveSubscription()) { return false; } diff --git a/packages/web/src/components/AppBar/index.tsx b/packages/web/src/components/AppBar/index.tsx index cf32768a..9577252c 100644 --- a/packages/web/src/components/AppBar/index.tsx +++ b/packages/web/src/components/AppBar/index.tsx @@ -12,6 +12,7 @@ import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import * as URLS from 'config/urls'; import AccountDropdownMenu from 'components/AccountDropdownMenu'; +import TrialStatusBadge from 'components/TrialStatusBadge/index.ee'; import Container from 'components/Container'; import { FormattedMessage } from 'react-intl'; import { Link } from './style'; @@ -67,9 +68,10 @@ export default function AppBar(props: AppBarProps): React.ReactElement { + + ; + + const { message, status } = data; + + return ( + + ); +} diff --git a/packages/web/src/components/TrialStatusBadge/style.ee.tsx b/packages/web/src/components/TrialStatusBadge/style.ee.tsx new file mode 100644 index 00000000..18c97124 --- /dev/null +++ b/packages/web/src/components/TrialStatusBadge/style.ee.tsx @@ -0,0 +1,13 @@ +import { styled } from '@mui/material/styles'; +import MuiChip, { chipClasses } from '@mui/material/Chip'; + +export const Chip = styled(MuiChip)` + &.${chipClasses.root} { + font-weight: 500; + } + + &.${chipClasses.colorWarning} { + background: #fef3c7; + color: #78350f; + } +` as typeof MuiChip; diff --git a/packages/web/src/graphql/queries/get-trial-status.ee.ts b/packages/web/src/graphql/queries/get-trial-status.ee.ts new file mode 100644 index 00000000..ec41ec20 --- /dev/null +++ b/packages/web/src/graphql/queries/get-trial-status.ee.ts @@ -0,0 +1,9 @@ +import { gql } from '@apollo/client'; + +export const GET_TRIAL_STATUS = gql` + query GetTrialStatus { + getTrialStatus { + expireAt + } + } +`; diff --git a/packages/web/src/hooks/useBillingAndUsageData.ee.ts b/packages/web/src/hooks/useBillingAndUsageData.ee.ts index 5d6e8446..22dbb810 100644 --- a/packages/web/src/hooks/useBillingAndUsageData.ee.ts +++ b/packages/web/src/hooks/useBillingAndUsageData.ee.ts @@ -23,7 +23,7 @@ function transform(billingAndUsageData: NonNullable Date: Sat, 8 Apr 2023 22:14:36 +0200 Subject: [PATCH 2/3] feat: Implement draft version of free trial over info box --- .../UsageDataInformation/index.ee.tsx | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/packages/web/src/components/UsageDataInformation/index.ee.tsx b/packages/web/src/components/UsageDataInformation/index.ee.tsx index 4cd47a2f..19bbcf08 100644 --- a/packages/web/src/components/UsageDataInformation/index.ee.tsx +++ b/packages/web/src/components/UsageDataInformation/index.ee.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; +import Alert from '@mui/material/Alert'; +import Stack from '@mui/material/Stack'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Chip from '@mui/material/Chip'; @@ -15,7 +17,8 @@ import * as URLS from 'config/urls'; import useBillingAndUsageData from 'hooks/useBillingAndUsageData.ee'; import useFormatMessage from 'hooks/useFormatMessage'; -const capitalize = (str: string) => str[0].toUpperCase() + str.slice(1, str.length); +const capitalize = (str: string) => + str[0].toUpperCase() + str.slice(1, str.length); type BillingCardProps = { name: string; @@ -62,21 +65,13 @@ function Action(props: { action?: TBillingCardAction }) { if (type === 'link') { if (action.src.startsWith('http')) { return ( - - ) + ); } else { return ( - ); @@ -100,6 +95,21 @@ export default function UsageDataInformation() { return ( + + + + Your free trial is over. Please{' '} + upgrade + your plan to continue using Automatisch. + + + @@ -137,7 +147,9 @@ export default function UsageDataInformation() { @@ -192,15 +204,17 @@ export default function UsageDataInformation() { {/* free plan has `null` status so that we can show the upgrade button */} - {billingAndUsageData?.subscription?.status === null && } + {billingAndUsageData?.subscription?.status === null && ( + + )} From 2739d2297ffe012ac6d1821ed2daa156e1532881 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Sun, 9 Apr 2023 12:07:31 +0000 Subject: [PATCH 3/3] feat: finalize TrialOverAlert on billing page --- .../components/TrialOverAlert/index.ee.tsx | 32 +++++++++++++++++++ .../UsageDataInformation/index.ee.tsx | 15 ++------- .../web/src/helpers/translation-values.tsx | 8 +++++ packages/web/src/hooks/useTrialStatus.ee.ts | 6 ++++ packages/web/src/locales/en.json | 3 +- 5 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 packages/web/src/components/TrialOverAlert/index.ee.tsx diff --git a/packages/web/src/components/TrialOverAlert/index.ee.tsx b/packages/web/src/components/TrialOverAlert/index.ee.tsx new file mode 100644 index 00000000..8b6974f5 --- /dev/null +++ b/packages/web/src/components/TrialOverAlert/index.ee.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import Alert from '@mui/material/Alert'; +import Typography from '@mui/material/Typography'; + +import * as URLS from 'config/urls'; +import { generateInternalLink } from 'helpers/translation-values'; +import useTrialStatus from 'hooks/useTrialStatus.ee'; +import useFormatMessage from 'hooks/useFormatMessage'; + + +export default function TrialOverAlert() { + const formatMessage = useFormatMessage(); + const trialStatus = useTrialStatus(); + + if (!trialStatus || !trialStatus.over) return ; + + return ( + + + {formatMessage('trialOverAlert.text', { + link: generateInternalLink(URLS.SETTINGS_PLAN_UPGRADE) + })} + + + ); +} diff --git a/packages/web/src/components/UsageDataInformation/index.ee.tsx b/packages/web/src/components/UsageDataInformation/index.ee.tsx index 19bbcf08..7ff84a5d 100644 --- a/packages/web/src/components/UsageDataInformation/index.ee.tsx +++ b/packages/web/src/components/UsageDataInformation/index.ee.tsx @@ -13,6 +13,7 @@ import Grid from '@mui/material/Grid'; import Typography from '@mui/material/Typography'; import { TBillingCardAction } from '@automatisch/types'; +import TrialOverAlert from 'components/TrialOverAlert/index.ee'; import * as URLS from 'config/urls'; import useBillingAndUsageData from 'hooks/useBillingAndUsageData.ee'; import useFormatMessage from 'hooks/useFormatMessage'; @@ -96,19 +97,7 @@ export default function UsageDataInformation() { return ( - - - Your free trial is over. Please{' '} - upgrade - your plan to continue using Automatisch. - - + diff --git a/packages/web/src/helpers/translation-values.tsx b/packages/web/src/helpers/translation-values.tsx index 1d2925ff..0f64b6c8 100644 --- a/packages/web/src/helpers/translation-values.tsx +++ b/packages/web/src/helpers/translation-values.tsx @@ -1,5 +1,13 @@ +import { Link as RouterLink } from 'react-router-dom'; import Link from '@mui/material/Link'; +export const generateInternalLink = (link: string) => (str: string) => + ( + + {str} + + ); + export const generateExternalLink = (link: string) => (str: string) => ( diff --git a/packages/web/src/hooks/useTrialStatus.ee.ts b/packages/web/src/hooks/useTrialStatus.ee.ts index 515f525c..ece79ddd 100644 --- a/packages/web/src/hooks/useTrialStatus.ee.ts +++ b/packages/web/src/hooks/useTrialStatus.ee.ts @@ -7,6 +7,7 @@ import useFormatMessage from './useFormatMessage'; type UseTrialStatusReturn = { expireAt: DateTime; message: string; + over: boolean; status: 'error' | 'warning'; } | null; @@ -25,11 +26,13 @@ function getFeedbackPayload(date: DateTime) { return { translationEntryId: 'trialBadge.over', status: 'error' as const, + over: true, }; } else if (diffInDays <= 0) { return { translationEntryId: 'trialBadge.endsToday', status: 'warning' as const, + over: false, } } else { return { @@ -38,6 +41,7 @@ function getFeedbackPayload(date: DateTime) { remainingDays: diffInDays }, status: 'warning' as const, + over: false, } } } @@ -54,11 +58,13 @@ export default function useTrialStatus(): UseTrialStatusReturn { translationEntryId, translationEntryValues, status, + over, } = getFeedbackPayload(expireAt); return { message: formatMessage(translationEntryId, translationEntryValues), expireAt, + over, status }; } diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json index 8d2186bc..b4b7437e 100644 --- a/packages/web/src/locales/en.json +++ b/packages/web/src/locales/en.json @@ -155,5 +155,6 @@ "invoices.link": "Link", "trialBadge.xDaysLeft": "{remainingDays} trial {remainingDays, plural, one {day} other {days}} left", "trialBadge.endsToday": "Trial ends today", - "trialBadge.over": "Trial is over" + "trialBadge.over": "Trial is over", + "trialOverAlert.text": "Your free trial is over. Please upgrade your plan to continue using Automatisch." }