Free trial close to expiration message (#13638)

* Add free_trial to deploy camunda script

* Free trial banner

* Don't show upgrade button for enterprise trial

* Add link option to banners

* Show free trial banner in portal

* Only admins should see free trial modal

* Fix days remaining

* Ignore subscription cancelled message for trial

* Remove unused code

---------

Co-authored-by: José Vte. Calderón <jose@budibase.com>
This commit is contained in:
melohagan 2024-05-08 13:38:50 +01:00 committed by GitHub
parent 24edfea6ea
commit 68cb2636df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 93 additions and 17 deletions

View File

@ -8,6 +8,8 @@
export let size = "S" export let size = "S"
export let extraButtonText export let extraButtonText
export let extraButtonAction export let extraButtonAction
export let extraLinkText
export let extraLinkAction
export let showCloseButton = true export let showCloseButton = true
let show = true let show = true
@ -28,8 +30,13 @@
<use xlink:href="#spectrum-icon-18-{icon}" /> <use xlink:href="#spectrum-icon-18-{icon}" />
</svg> </svg>
<div class="spectrum-Toast-body"> <div class="spectrum-Toast-body">
<div class="spectrum-Toast-content"> <div class="spectrum-Toast-content row-content">
<slot /> <slot />
{#if extraLinkText}
<button class="link" on:click={extraLinkAction}>
<u>{extraLinkText}</u>
</button>
{/if}
</div> </div>
{#if extraButtonText && extraButtonAction} {#if extraButtonText && extraButtonAction}
<button <button
@ -73,4 +80,23 @@
.spectrum-Button { .spectrum-Button {
border: 1px solid rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.2);
} }
.row-content {
display: flex;
}
.link {
background: none;
border: none;
margin: 0;
margin-left: 0.5em;
padding: 0;
cursor: pointer;
color: white;
font-weight: 600;
}
u {
font-weight: 600;
}
</style> </style>

View File

@ -0,0 +1,43 @@
<script>
import "@spectrum-css/toast/dist/index-vars.css"
import Portal from "svelte-portal"
import { fly } from "svelte/transition"
import { Banner, BANNER_TYPES } from "@budibase/bbui"
import { licensing } from "stores/portal"
export let show = true
const oneDayInSeconds = 86400
$: license = $licensing.license
function daysUntilCancel() {
const cancelAt = license?.billing?.subscription?.cancelAt
const diffTime = Math.abs(cancelAt - new Date().getTime()) / 1000
return Math.floor(diffTime / oneDayInSeconds)
}
</script>
<Portal target=".banner-container">
<div class="banner">
{#if show}
<div transition:fly={{ y: -30 }}>
<Banner
type={BANNER_TYPES.INFO}
extraLinkText={"Please select a plan."}
extraLinkAction={$licensing.goToUpgradePage}
showCloseButton={false}
>
Your free trial will end in {daysUntilCancel()} days.
</Banner>
</div>
{/if}
</div>
</Portal>
<style>
.banner {
pointer-events: none;
width: 100%;
}
</style>

View File

@ -12,7 +12,7 @@ const defaultCacheFn = key => {
const upgradeAction = key => { const upgradeAction = key => {
return defaultNavigateAction( return defaultNavigateAction(
key, key,
"Upgrade Plan", "Upgrade",
`${get(admin).accountPortalUrl}/portal/upgrade` `${get(admin).accountPortalUrl}/portal/upgrade`
) )
} }

View File

@ -5,6 +5,7 @@
import { auth, licensing } from "stores/portal" import { auth, licensing } from "stores/portal"
import { API } from "api" import { API } from "api"
import { PlanType } from "@budibase/types" import { PlanType } from "@budibase/types"
import { sdk } from "@budibase/shared-core"
let freeTrialModal let freeTrialModal
@ -14,7 +15,8 @@
const showFreeTrialModal = (planType, freeTrialModal) => { const showFreeTrialModal = (planType, freeTrialModal) => {
if ( if (
planType === PlanType.ENTERPRISE_BASIC_TRIAL && planType === PlanType.ENTERPRISE_BASIC_TRIAL &&
!$auth.user?.freeTrialConfirmedAt !$auth.user?.freeTrialConfirmedAt &&
sdk.users.isAdmin($auth.user)
) { ) {
freeTrialModal?.show() freeTrialModal?.show()
} }

View File

@ -6,7 +6,7 @@
import { sdk } from "@budibase/shared-core" import { sdk } from "@budibase/shared-core"
</script> </script>
{#if isEnabled(TENANT_FEATURE_FLAGS.LICENSING) && !$licensing.isEnterprisePlan} {#if isEnabled(TENANT_FEATURE_FLAGS.LICENSING) && !$licensing.isEnterprisePlan && !$licensing.isEnterpriseTrial}
{#if $admin.cloud && $auth?.user?.accountPortalAccess} {#if $admin.cloud && $auth?.user?.accountPortalAccess}
<Button <Button
cta cta

View File

@ -1,7 +1,7 @@
<script> <script>
import { isActive, redirect, goto, url } from "@roxi/routify" import { isActive, redirect, goto, url } from "@roxi/routify"
import { Icon, notifications, Tabs, Tab } from "@budibase/bbui" import { Icon, notifications, Tabs, Tab } from "@budibase/bbui"
import { organisation, auth, menu, appsStore } from "stores/portal" import { organisation, auth, menu, appsStore, licensing } from "stores/portal"
import { onMount } from "svelte" import { onMount } from "svelte"
import UpgradeButton from "./_components/UpgradeButton.svelte" import UpgradeButton from "./_components/UpgradeButton.svelte"
import MobileMenu from "./_components/MobileMenu.svelte" import MobileMenu from "./_components/MobileMenu.svelte"
@ -10,6 +10,8 @@
import HelpMenu from "components/common/HelpMenu.svelte" import HelpMenu from "components/common/HelpMenu.svelte"
import VerificationPromptBanner from "components/common/VerificationPromptBanner.svelte" import VerificationPromptBanner from "components/common/VerificationPromptBanner.svelte"
import { sdk } from "@budibase/shared-core" import { sdk } from "@budibase/shared-core"
import EnterpriseBasicTrialBanner from "components/portal/licensing/EnterpriseBasicTrialBanner.svelte"
import { Constants } from "@budibase/frontend-core"
let loaded = false let loaded = false
let mobileMenuVisible = false let mobileMenuVisible = false
@ -33,6 +35,14 @@
const showMobileMenu = () => (mobileMenuVisible = true) const showMobileMenu = () => (mobileMenuVisible = true)
const hideMobileMenu = () => (mobileMenuVisible = false) const hideMobileMenu = () => (mobileMenuVisible = false)
const showFreeTrialBanner = () => {
return (
$licensing.license?.plan?.type ===
Constants.PlanType.ENTERPRISE_BASIC_TRIAL &&
sdk.users.isAdmin($auth.user)
)
}
onMount(async () => { onMount(async () => {
// Prevent non-builders from accessing the portal // Prevent non-builders from accessing the portal
if ($auth.user) { if ($auth.user) {
@ -58,6 +68,7 @@
<HelpMenu /> <HelpMenu />
<div class="container"> <div class="container">
<VerificationPromptBanner /> <VerificationPromptBanner />
<EnterpriseBasicTrialBanner show={showFreeTrialBanner()} />
<div class="nav"> <div class="nav">
<div class="branding"> <div class="branding">
<Logo /> <Logo />

View File

@ -29,6 +29,7 @@
const manageUrl = `${$admin.accountPortalUrl}/portal/billing` const manageUrl = `${$admin.accountPortalUrl}/portal/billing`
const WARN_USAGE = ["Queries", "Automations", "Rows", "Day Passes", "Users"] const WARN_USAGE = ["Queries", "Automations", "Rows", "Day Passes", "Users"]
const oneDayInSeconds = 86400
const EXCLUDE_QUOTAS = { const EXCLUDE_QUOTAS = {
Queries: () => true, Queries: () => true,
@ -104,24 +105,17 @@
if (!timestamp) { if (!timestamp) {
return return
} }
const now = new Date() const diffTime = Math.abs(timestamp - new Date().getTime()) / 1000
now.setHours(0) return Math.floor(diffTime / oneDayInSeconds)
now.setMinutes(0)
const thenDate = new Date(timestamp)
thenDate.setHours(0)
thenDate.setMinutes(0)
const difference = thenDate.getTime() - now
// return the difference in days
return (difference / (1000 * 3600 * 24)).toFixed(0)
} }
const setTextRows = () => { const setTextRows = () => {
textRows = [] textRows = []
if (cancelAt && !usesInvoicing) { if (cancelAt && !usesInvoicing) {
if (plan?.type !== Constants.PlanType.ENTERPRISE_BASIC_TRIAL) {
textRows.push({ message: "Subscription has been cancelled" }) textRows.push({ message: "Subscription has been cancelled" })
}
textRows.push({ textRows.push({
message: `${getDaysRemaining(cancelAt)} days remaining`, message: `${getDaysRemaining(cancelAt)} days remaining`,
tooltip: new Date(cancelAt), tooltip: new Date(cancelAt),