From 132f347916a07f4b7d487b33997af48b276af7bf Mon Sep 17 00:00:00 2001 From: Dean Date: Wed, 14 Sep 2022 18:04:58 +0100 Subject: [PATCH] Apps Page update to hide apps when sessions are maxed. General refactoring and updates to the licensing notification flows. --- packages/bbui/src/Banner/BannerDisplay.svelte | 6 +- packages/bbui/src/Stores/banner.js | 22 ++++-- .../bbui/src/Tooltip/TooltipWrapper.svelte | 3 +- packages/bbui/src/index.js | 2 +- .../src/builderStore/store/temporal.js | 3 +- .../licensing/AccountDowngradedModal.svelte | 14 ++-- .../portal/licensing/AppLimitModal.svelte | 9 ++- .../licensing/DayPassWarningModal.svelte | 52 +++++++++---- .../portal/licensing/LicensingOverlays.svelte | 31 ++++---- .../licensing/PaymentFailedModal.svelte | 25 +++---- .../components/portal/licensing/constants.js | 2 +- .../{banners.js => licensingBanners.js} | 74 ++++++++++++------- .../src/pages/builder/apps/index.svelte | 22 +++++- .../builder/src/stores/portal/licensing.js | 72 ++++++++---------- 14 files changed, 198 insertions(+), 139 deletions(-) rename packages/builder/src/components/portal/licensing/{banners.js => licensingBanners.js} (62%) diff --git a/packages/bbui/src/Banner/BannerDisplay.svelte b/packages/bbui/src/Banner/BannerDisplay.svelte index 4785fcb9ba..9ea2eaf2ec 100644 --- a/packages/bbui/src/Banner/BannerDisplay.svelte +++ b/packages/bbui/src/Banner/BannerDisplay.svelte @@ -16,13 +16,15 @@ extraButtonText={message.extraButtonText} extraButtonAction={message.extraButtonAction} on:change={() => { - message.onChange() + if (message.onChange) { + message.onChange() + } }} showCloseButton={typeof message.showCloseButton === "boolean" ? message.showCloseButton : true} > - + {message.message} diff --git a/packages/bbui/src/Stores/banner.js b/packages/bbui/src/Stores/banner.js index 745c77e188..ba6d187d97 100644 --- a/packages/bbui/src/Stores/banner.js +++ b/packages/bbui/src/Stores/banner.js @@ -1,5 +1,10 @@ import { writable } from "svelte/store" +export const BANNER_TYPES = { + INFO: "info", + NEGATIVE: "negative", +} + export function createBannerStore() { const DEFAULT_CONFIG = { messages: [], @@ -22,19 +27,26 @@ export function createBannerStore() { const showStatus = async () => { const config = { message: "Some systems are experiencing issues", - type: "negative", + type: BANNER_TYPES.NEGATIVE, extraButtonText: "View Status", extraButtonAction: () => window.open("https://status.budibase.com/"), } - await show(config) + await queue([config]) } const queue = async entries => { + const priority = { + [BANNER_TYPES.NEGATIVE]: 0, + [BANNER_TYPES.INFO]: 1, + } banner.update(store => { - const sorted = [...store.messages, ...entries].sort( - (a, b) => a.priority > b.priority - ) + const sorted = [...store.messages, ...entries].sort((a, b) => { + if (priority[a.type] == priority[b.type]) { + return 0 + } + return priority[a.type] < priority[b.type] ? -1 : 1 + }) return { ...store, messages: sorted, diff --git a/packages/bbui/src/Tooltip/TooltipWrapper.svelte b/packages/bbui/src/Tooltip/TooltipWrapper.svelte index 0c6c8e167b..e7a96f6cd8 100644 --- a/packages/bbui/src/Tooltip/TooltipWrapper.svelte +++ b/packages/bbui/src/Tooltip/TooltipWrapper.svelte @@ -4,6 +4,7 @@ export let tooltip = "" export let size = "M" + export let disabled = true let showTooltip = false @@ -19,7 +20,7 @@ on:mouseleave={() => (showTooltip = false)} on:focus > - + {#if showTooltip}
diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js index 11424b1261..ead226bdc3 100644 --- a/packages/bbui/src/index.js +++ b/packages/bbui/src/index.js @@ -95,7 +95,7 @@ export { default as clickOutside } from "./Actions/click_outside" // Stores export { notifications, createNotificationStore } from "./Stores/notifications" -export { banner } from "./Stores/banner" +export { banner, BANNER_TYPES } from "./Stores/banner" // Helpers export * as Helpers from "./helpers" diff --git a/packages/builder/src/builderStore/store/temporal.js b/packages/builder/src/builderStore/store/temporal.js index b8a75e1905..ca70fd6293 100644 --- a/packages/builder/src/builderStore/store/temporal.js +++ b/packages/builder/src/builderStore/store/temporal.js @@ -4,8 +4,7 @@ import { get } from "svelte/store" export const getTemporalStore = () => { const initialValue = {} - //const appId = window["##BUDIBASE_APP_ID##"] || "app" - const localStorageKey = `${123}.bb-temporal` + const localStorageKey = `bb-temporal` const store = createLocalStorageStore(localStorageKey, initialValue) const setExpiring = (key, data, duration) => { diff --git a/packages/builder/src/components/portal/licensing/AccountDowngradedModal.svelte b/packages/builder/src/components/portal/licensing/AccountDowngradedModal.svelte index 81fe340354..53683faa87 100644 --- a/packages/builder/src/components/portal/licensing/AccountDowngradedModal.svelte +++ b/packages/builder/src/components/portal/licensing/AccountDowngradedModal.svelte @@ -7,7 +7,8 @@ let accountDowngradeModal - const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` + $: accountUrl = $admin.accountPortalUrl + $: upgradeUrl = `${accountUrl}/portal/upgrade` export function show() { accountDowngradeModal.show() @@ -31,15 +32,12 @@ : null} > - The payment for your Business Subscription failed and we have downgraded - your account to the Free plan. - - - Update to Business to get all your apps and user sessions back up and - running. + The payment for your subscription has failed and we have downgraded your + account to the Free plan. + Upgrade to restore full functionality. {#if !$auth.user.accountPortalAccess} - Please contact the account holder. + Please contact the account holder to upgrade. {/if} diff --git a/packages/builder/src/components/portal/licensing/AppLimitModal.svelte b/packages/builder/src/components/portal/licensing/AppLimitModal.svelte index 45259edd76..39f553517e 100644 --- a/packages/builder/src/components/portal/licensing/AppLimitModal.svelte +++ b/packages/builder/src/components/portal/licensing/AppLimitModal.svelte @@ -6,7 +6,8 @@ let appLimitModal - const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` + $: accountUrl = $admin.accountPortalUrl + $: upgradeUrl = `${accountUrl}/portal/upgrade` export function show() { appLimitModal.show() @@ -19,7 +20,7 @@ You are currently on our Free plan. Upgrade - to our Pro plan to get unlimited apps. + to our Pro plan to get unlimited apps and additional features. {#if !$auth.user.accountPortalAccess} - Please contact the account holder. + Please contact the account holder to upgrade. {/if} diff --git a/packages/builder/src/components/portal/licensing/DayPassWarningModal.svelte b/packages/builder/src/components/portal/licensing/DayPassWarningModal.svelte index f164df1105..b1aade2ca5 100644 --- a/packages/builder/src/components/portal/licensing/DayPassWarningModal.svelte +++ b/packages/builder/src/components/portal/licensing/DayPassWarningModal.svelte @@ -1,31 +1,40 @@ - + {#if $auth.user.accountPortalAccess} { @@ -33,22 +42,37 @@ }} > - You have used {sessionsUsed}% of + You have used {dayPassesUsed}% of your plans Day Passes with {daysRemaining} day{daysRemaining == 1 ? "" : "s"} remaining. + + + - Upgrade your account to prevent your apps from going offline. + {dayPassesBody} {:else} - + - You have used {sessionsUsed}% of + You have used {dayPassesUsed}% of your plans Day Passes with {daysRemaining} day{daysRemaining == 1 ? "" : "s"} remaining. + + + - Please contact your account holder. + Please contact your account holder to upgrade. {/if} + + diff --git a/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte b/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte index 2a744179c0..8431b860e8 100644 --- a/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte +++ b/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte @@ -6,7 +6,7 @@ import PaymentFailedModal from "./PaymentFailedModal.svelte" import AccountDowngradedModal from "./AccountDowngradedModal.svelte" import { ExpiringKeys } from "./constants" - import { getBanners } from "./banners" + import { getBanners } from "./licensingBanners" import { banner } from "@budibase/bbui" const oneDayInSeconds = 86400 @@ -42,7 +42,7 @@ { key: ExpiringKeys.LICENSING_PAYMENT_FAILED, criteria: () => { - return $licensing.accountPastDue + return $licensing.accountPastDue && !$licensing.isFreePlan() }, action: () => { paymentFailedModal.show() @@ -69,14 +69,7 @@ }) } - $: if (userLoaded && licensingLoaded && loaded) { - queuedModals = processModals() - queuedBanners = getBanners() - showNext() - banner.queue(queuedBanners) - } - - const showNext = () => { + const showNextModal = () => { if (currentModalCfg) { currentModalCfg.cache() } @@ -88,6 +81,13 @@ } } + $: if (userLoaded && licensingLoaded && loaded) { + queuedModals = processModals() + queuedBanners = getBanners() + showNextModal() + banner.queue(queuedBanners) + } + onMount(async () => { auth.subscribe(state => { if (state.user && !userLoaded) { @@ -100,18 +100,13 @@ licensingLoaded = true } }) - - temporalStore.subscribe(state => { - console.log("Stored temporal ", state) - }) - loaded = true }) - - + + diff --git a/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte b/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte index f952684998..591742a3d0 100644 --- a/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte +++ b/packages/builder/src/components/portal/licensing/PaymentFailedModal.svelte @@ -7,10 +7,11 @@ export let onShow = () => {} let paymentFailedModal - let pastDueAt + let pastDueEndDate const paymentFailedTitle = "Payment failed" - const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` + $: accountUrl = $admin.accountPortalUrl + $: upgradeUrl = `${accountUrl}/portal/upgrade` export function show() { paymentFailedModal.show() @@ -21,12 +22,8 @@ } onMount(() => { - auth.subscribe(state => { - if (state.user && state.user.license?.billing?.subscription) { - pastDueAt = new Date( - state.user.license?.billing?.subscription.pastDueAt * 1000 - ) - } + licensing.subscribe(state => { + pastDueEndDate = state.pastDueEndDate }) }) @@ -48,11 +45,11 @@
- {`${$licensing.paymentDueDaysRemaining} day${ - $licensing.paymentDueDaysRemaining == 1 ? "" : "s" + {`${$licensing.pastDueDaysRemaining} day${ + $licensing.pastDueDaysRemaining == 1 ? "" : "s" } remaining`} - +
@@ -67,11 +64,11 @@ Please contact your account holder.
- {`${$licensing.paymentDueDaysRemaining} day${ - $licensing.paymentDueDaysRemaining == 1 ? "" : "s" + {`${$licensing.pastDueDaysRemaining} day${ + $licensing.pastDueDaysRemaining == 1 ? "" : "s" } remaining`} - +
diff --git a/packages/builder/src/components/portal/licensing/constants.js b/packages/builder/src/components/portal/licensing/constants.js index 6474b7c78f..57f3a36709 100644 --- a/packages/builder/src/components/portal/licensing/constants.js +++ b/packages/builder/src/components/portal/licensing/constants.js @@ -6,7 +6,7 @@ export const ExpiringKeys = { LICENSING_APP_LIMIT_MODAL: "licensing_app_limit_modal", LICENSING_ROWS_WARNING_BANNER: "licensing_rows_warning_banner", LICENSING_AUTOMATIONS_WARNING_BANNER: "licensing_automations_warning_banner", - LICENSING_QUERIES_WARNING_BANNER: "licensing_automations_warning_banner", + LICENSING_QUERIES_WARNING_BANNER: "licensing_queries_warning_banner", } export const StripeStatus = { diff --git a/packages/builder/src/components/portal/licensing/banners.js b/packages/builder/src/components/portal/licensing/licensingBanners.js similarity index 62% rename from packages/builder/src/components/portal/licensing/banners.js rename to packages/builder/src/components/portal/licensing/licensingBanners.js index 3f8a2fbb0f..37d8e4a540 100644 --- a/packages/builder/src/components/portal/licensing/banners.js +++ b/packages/builder/src/components/portal/licensing/licensingBanners.js @@ -2,9 +2,9 @@ import { ExpiringKeys } from "./constants" import { temporalStore } from "builderStore" import { admin, auth, licensing } from "stores/portal" import { get } from "svelte/store" +import { BANNER_TYPES } from "@budibase/bbui" const oneDayInSeconds = 86400 -const upgradeUrl = `${get(admin).accountPortalUrl}/portal/upgrade` const defaultCacheFn = key => { temporalStore.actions.setExpiring(key, {}, oneDayInSeconds) @@ -18,36 +18,47 @@ const defaultAction = key => { extraButtonText: "Upgrade Plan", extraButtonAction: () => { defaultCacheFn(key) - window.location.href = upgradeUrl + window.location.href = `${get(admin).accountPortalUrl}/portal/upgrade` }, } } -const buildUsageInfoBanner = (metricKey, metricLabel, cacheKey, percentage) => { +const buildUsageInfoBanner = ( + metricKey, + metricLabel, + cacheKey, + percentageThreshold, + customMessage +) => { const appAuth = get(auth) const appLicensing = get(licensing) + const displayPercent = + appLicensing?.usageMetrics[metricKey] > 100 + ? 100 + : appLicensing?.usageMetrics[metricKey] + let bannerConfig = { key: cacheKey, - type: "info", + type: BANNER_TYPES.INFO, onChange: () => { defaultCacheFn(cacheKey) }, - message: `You have used ${ - appLicensing?.usageMetrics[metricKey] - }% of your monthly usage of ${metricLabel} with ${ - appLicensing.quotaResetDaysRemaining - } day${ - appLicensing.quotaResetDaysRemaining == 1 ? "" : "s" - } remaining. All apps will be taken offline if this limit is reached. ${ - appAuth.user.accountPortalAccess - ? "" - : "Please contact your account holder." - }`, + message: customMessage + ? customMessage + : `You have used ${displayPercent}% of your monthly usage of ${metricLabel} with ${ + appLicensing.quotaResetDaysRemaining + } day${ + appLicensing.quotaResetDaysRemaining == 1 ? "" : "s" + } remaining. ${ + appAuth.user.accountPortalAccess + ? "" + : "Please contact your account holder to upgrade" + }`, criteria: () => { - return appLicensing?.usageMetrics[metricKey] >= percentage + return appLicensing?.usageMetrics[metricKey] >= percentageThreshold }, - priority: 0, //Banners.Priority 0, 1, 2 ?? + tooltip: appLicensing?.quotaResetDate, } return !get(auth).user.accountPortalAccess @@ -60,17 +71,18 @@ const buildUsageInfoBanner = (metricKey, metricLabel, cacheKey, percentage) => { const buildDayPassBanner = () => { const appAuth = get(auth) + const appLicensing = get(licensing) if (get(licensing)?.usageMetrics["dayPasses"] >= 100) { return { key: "max_dayPasses", - type: "negative", + type: BANNER_TYPES.NEGATIVE, criteria: () => { return true }, message: `Your apps are currently offline. You have exceeded your plans limit for Day Passes. ${ appAuth.user.accountPortalAccess ? "" - : "Please contact your account holder." + : "Please contact your account holder to upgrade." }`, ...defaultAction(), showCloseButton: false, @@ -81,23 +93,35 @@ const buildDayPassBanner = () => { "dayPasses", "Day Passes", ExpiringKeys.LICENSING_DAYPASS_WARNING_BANNER, - 90 + 90, + `You have used ${ + appLicensing?.usageMetrics["dayPasses"] + }% of your monthly usage of Day Passes with ${ + appLicensing?.quotaResetDaysRemaining + } day${ + get(licensing).quotaResetDaysRemaining == 1 ? "" : "s" + } remaining. All apps will be taken offline if this limit is reached. ${ + appAuth.user.accountPortalAccess + ? "" + : "Please contact your account holder to upgrade." + }` ) } const buildPaymentFailedBanner = () => { return { key: "payment_Failed", - type: "negative", + type: BANNER_TYPES.NEGATIVE, criteria: () => { - return get(licensing)?.accountPastDue + return get(licensing)?.accountPastDue && !get(licensing).isFreePlan() }, message: `Payment Failed - Please update your billing details or your account will be downgrades in - ${get(licensing)?.paymentDueDaysRemaining} day${ - get(licensing)?.paymentDueDaysRemaining == 1 ? "" : "s" + ${get(licensing)?.pastDueDaysRemaining} day${ + get(licensing)?.pastDueDaysRemaining == 1 ? "" : "s" }`, ...defaultAction(), showCloseButton: false, + tooltip: get(licensing).pastDueEndDate, } } @@ -121,7 +145,7 @@ export const getBanners = () => { "queries", "Queries", ExpiringKeys.LICENSING_QUERIES_WARNING_BANNER, - 90 // could be an array [50,75,90] + 90 ), ].filter(licensingBanner => { return ( diff --git a/packages/builder/src/pages/builder/apps/index.svelte b/packages/builder/src/pages/builder/apps/index.svelte index 1f5a761a42..3b606480cc 100644 --- a/packages/builder/src/pages/builder/apps/index.svelte +++ b/packages/builder/src/pages/builder/apps/index.svelte @@ -13,13 +13,14 @@ notifications, } from "@budibase/bbui" import { onMount } from "svelte" - import { apps, organisation, auth, groups } from "stores/portal" + import { apps, organisation, auth, groups, licensing } from "stores/portal" import { goto } from "@roxi/routify" import { AppStatus } from "constants" import { gradient } from "actions" import UpdateUserInfoModal from "components/settings/UpdateUserInfoModal.svelte" import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte" import { processStringSync } from "@budibase/string-templates" + import Spaceman from "assets/bb-space-man.svg" import Logo from "assets/bb-emblem.svg" let loaded = false @@ -91,7 +92,7 @@
- logo +
- {#if userApps.length} + {#if $licensing.usageMetrics.dayPasses >= 100} +
+ + spaceman + + {"Your apps are currently offline."} + + Please contact the account holder to get them back online. + +
+ {:else if userApps.length} Apps
@@ -194,10 +205,13 @@ justify-content: space-between; align-items: center; } - img { + img.logo { width: 40px; margin-bottom: -12px; } + img.spaceman { + width: 100px; + } .avatar { display: grid; grid-template-columns: auto auto; diff --git a/packages/builder/src/stores/portal/licensing.js b/packages/builder/src/stores/portal/licensing.js index cccb733a5e..ecf0ccb8c2 100644 --- a/packages/builder/src/stores/portal/licensing.js +++ b/packages/builder/src/stores/portal/licensing.js @@ -8,7 +8,6 @@ export const createLicensingStore = () => { const DEFAULT = { plans: {}, } - const oneDayInMilliseconds = 86400000 const store = writable(DEFAULT) @@ -26,8 +25,7 @@ export const createLicensingStore = () => { getUsageMetrics: async () => { const quota = get(store).quotaUsage const license = get(auth).user.license - const now = Date.now() - const nowSeconds = now / 1000 + const now = new Date() const getMetrics = (keys, license, quota) => { if (!license || !quota || !keys) { @@ -36,16 +34,12 @@ export const createLicensingStore = () => { return keys.reduce((acc, key) => { const quotaLimit = license[key].value const quotaUsed = (quota[key] / quotaLimit) * 100 - - // Catch for sessions - key = key === "sessions" ? "dayPasses" : key - acc[key] = quotaLimit > -1 ? Math.round(quotaUsed) : -1 return acc }, {}) } const monthlyMetrics = getMetrics( - ["sessions", "queries", "automations"], + ["dayPasses", "queries", "automations"], license.quotas.usage.monthly, quota.monthly.current ) @@ -55,52 +49,50 @@ export const createLicensingStore = () => { quota.usageQuota ) - // DEBUG - console.log("Store licensing val ", { - ...monthlyMetrics, - ...staticMetrics, - }) - - let subscriptionDaysRemaining - if (license?.billing?.subscription) { - const currentPeriodEnd = license.billing.subscription.currentPeriodEnd - const currentPeriodEndMilliseconds = currentPeriodEnd * 1000 - - subscriptionDaysRemaining = Math.round( - (currentPeriodEndMilliseconds - now) / oneDayInMilliseconds - ) + const getDaysBetween = (dateStart, dateEnd) => { + return dateEnd > dateStart + ? Math.round( + (dateEnd.getTime() - dateStart.getTime()) / oneDayInMilliseconds + ) + : 0 } - const quotaResetDaysRemaining = - quota.quotaReset > now - ? Math.round((quota.quotaReset - now) / oneDayInMilliseconds) - : 0 + const quotaResetDate = new Date(quota.quotaReset) + const quotaResetDaysRemaining = getDaysBetween(now, quotaResetDate) const accountDowngraded = + license?.billing?.subscription?.downgradeAt && + license?.billing?.subscription?.downgradeAt <= now.getTime() && license?.billing?.subscription?.status === StripeStatus.PAST_DUE && - license?.plan === Constants.PlanType.FREE + license?.plan.type === Constants.PlanType.FREE - const accountPastDue = - nowSeconds >= license?.billing?.subscription?.currentPeriodEnd && - nowSeconds <= license?.billing?.subscription?.pastDueAt && - license?.billing?.subscription?.status === StripeStatus.PAST_DUE && - !accountDowngraded + const pastDueAtMilliseconds = license?.billing?.subscription?.pastDueAt + const downgradeAtMilliseconds = + license?.billing?.subscription?.downgradeAt + let pastDueDaysRemaining + let pastDueEndDate - const pastDueAtSeconds = license?.billing?.subscription?.pastDueAt - const pastDueAtMilliseconds = pastDueAtSeconds * 1000 - const paymentDueDaysRemaining = Math.round( - (pastDueAtMilliseconds - now) / oneDayInMilliseconds - ) + if (pastDueAtMilliseconds && downgradeAtMilliseconds) { + pastDueEndDate = new Date(downgradeAtMilliseconds) + pastDueDaysRemaining = getDaysBetween( + new Date(pastDueAtMilliseconds), + pastDueEndDate + ) + } store.update(state => { return { ...state, usageMetrics: { ...monthlyMetrics, ...staticMetrics }, - subscriptionDaysRemaining, - paymentDueDaysRemaining, quotaResetDaysRemaining, + quotaResetDate, accountDowngraded, - accountPastDue, + accountPastDue: pastDueAtMilliseconds != null, + pastDueEndDate, + pastDueDaysRemaining, + isFreePlan: () => { + return license?.plan.type === Constants.PlanType.FREE + }, } }) },