Apps Page update to hide apps when sessions are maxed. General refactoring and updates to the licensing notification flows.
This commit is contained in:
parent
6bd3c8c3d7
commit
132f347916
14 changed files with 198 additions and 139 deletions
|
@ -16,13 +16,15 @@
|
||||||
extraButtonText={message.extraButtonText}
|
extraButtonText={message.extraButtonText}
|
||||||
extraButtonAction={message.extraButtonAction}
|
extraButtonAction={message.extraButtonAction}
|
||||||
on:change={() => {
|
on:change={() => {
|
||||||
message.onChange()
|
if (message.onChange) {
|
||||||
|
message.onChange()
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
showCloseButton={typeof message.showCloseButton === "boolean"
|
showCloseButton={typeof message.showCloseButton === "boolean"
|
||||||
? message.showCloseButton
|
? message.showCloseButton
|
||||||
: true}
|
: true}
|
||||||
>
|
>
|
||||||
<TooltipWrapper tooltip={"test"}>
|
<TooltipWrapper tooltip={message.tooltip} disabled={false}>
|
||||||
{message.message}
|
{message.message}
|
||||||
</TooltipWrapper>
|
</TooltipWrapper>
|
||||||
</Banner>
|
</Banner>
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
|
export const BANNER_TYPES = {
|
||||||
|
INFO: "info",
|
||||||
|
NEGATIVE: "negative",
|
||||||
|
}
|
||||||
|
|
||||||
export function createBannerStore() {
|
export function createBannerStore() {
|
||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
messages: [],
|
messages: [],
|
||||||
|
@ -22,19 +27,26 @@ export function createBannerStore() {
|
||||||
const showStatus = async () => {
|
const showStatus = async () => {
|
||||||
const config = {
|
const config = {
|
||||||
message: "Some systems are experiencing issues",
|
message: "Some systems are experiencing issues",
|
||||||
type: "negative",
|
type: BANNER_TYPES.NEGATIVE,
|
||||||
extraButtonText: "View Status",
|
extraButtonText: "View Status",
|
||||||
extraButtonAction: () => window.open("https://status.budibase.com/"),
|
extraButtonAction: () => window.open("https://status.budibase.com/"),
|
||||||
}
|
}
|
||||||
|
|
||||||
await show(config)
|
await queue([config])
|
||||||
}
|
}
|
||||||
|
|
||||||
const queue = async entries => {
|
const queue = async entries => {
|
||||||
|
const priority = {
|
||||||
|
[BANNER_TYPES.NEGATIVE]: 0,
|
||||||
|
[BANNER_TYPES.INFO]: 1,
|
||||||
|
}
|
||||||
banner.update(store => {
|
banner.update(store => {
|
||||||
const sorted = [...store.messages, ...entries].sort(
|
const sorted = [...store.messages, ...entries].sort((a, b) => {
|
||||||
(a, b) => a.priority > b.priority
|
if (priority[a.type] == priority[b.type]) {
|
||||||
)
|
return 0
|
||||||
|
}
|
||||||
|
return priority[a.type] < priority[b.type] ? -1 : 1
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
...store,
|
...store,
|
||||||
messages: sorted,
|
messages: sorted,
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
export let tooltip = ""
|
export let tooltip = ""
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
|
export let disabled = true
|
||||||
|
|
||||||
let showTooltip = false
|
let showTooltip = false
|
||||||
</script>
|
</script>
|
||||||
|
@ -19,7 +20,7 @@
|
||||||
on:mouseleave={() => (showTooltip = false)}
|
on:mouseleave={() => (showTooltip = false)}
|
||||||
on:focus
|
on:focus
|
||||||
>
|
>
|
||||||
<Icon name="InfoOutline" size="S" disabled={true} />
|
<Icon name="InfoOutline" size="S" {disabled} />
|
||||||
</div>
|
</div>
|
||||||
{#if showTooltip}
|
{#if showTooltip}
|
||||||
<div class="tooltip">
|
<div class="tooltip">
|
||||||
|
|
|
@ -95,7 +95,7 @@ export { default as clickOutside } from "./Actions/click_outside"
|
||||||
|
|
||||||
// Stores
|
// Stores
|
||||||
export { notifications, createNotificationStore } from "./Stores/notifications"
|
export { notifications, createNotificationStore } from "./Stores/notifications"
|
||||||
export { banner } from "./Stores/banner"
|
export { banner, BANNER_TYPES } from "./Stores/banner"
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
export * as Helpers from "./helpers"
|
export * as Helpers from "./helpers"
|
||||||
|
|
|
@ -4,8 +4,7 @@ import { get } from "svelte/store"
|
||||||
export const getTemporalStore = () => {
|
export const getTemporalStore = () => {
|
||||||
const initialValue = {}
|
const initialValue = {}
|
||||||
|
|
||||||
//const appId = window["##BUDIBASE_APP_ID##"] || "app"
|
const localStorageKey = `bb-temporal`
|
||||||
const localStorageKey = `${123}.bb-temporal`
|
|
||||||
const store = createLocalStorageStore(localStorageKey, initialValue)
|
const store = createLocalStorageStore(localStorageKey, initialValue)
|
||||||
|
|
||||||
const setExpiring = (key, data, duration) => {
|
const setExpiring = (key, data, duration) => {
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
let accountDowngradeModal
|
let accountDowngradeModal
|
||||||
|
|
||||||
const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade`
|
$: accountUrl = $admin.accountPortalUrl
|
||||||
|
$: upgradeUrl = `${accountUrl}/portal/upgrade`
|
||||||
|
|
||||||
export function show() {
|
export function show() {
|
||||||
accountDowngradeModal.show()
|
accountDowngradeModal.show()
|
||||||
|
@ -31,15 +32,12 @@
|
||||||
: null}
|
: null}
|
||||||
>
|
>
|
||||||
<Body>
|
<Body>
|
||||||
The payment for your Business Subscription failed and we have downgraded
|
The payment for your subscription has failed and we have downgraded your
|
||||||
your account to the <span class="free-plan">Free plan</span>.
|
account to the <span class="free-plan">Free plan</span>.
|
||||||
</Body>
|
|
||||||
<Body>
|
|
||||||
Update to Business to get all your apps and user sessions back up and
|
|
||||||
running.
|
|
||||||
</Body>
|
</Body>
|
||||||
|
<Body>Upgrade to restore full functionality.</Body>
|
||||||
{#if !$auth.user.accountPortalAccess}
|
{#if !$auth.user.accountPortalAccess}
|
||||||
<Body>Please contact the account holder.</Body>
|
<Body>Please contact the account holder to upgrade.</Body>
|
||||||
{/if}
|
{/if}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
|
|
||||||
let appLimitModal
|
let appLimitModal
|
||||||
|
|
||||||
const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade`
|
$: accountUrl = $admin.accountPortalUrl
|
||||||
|
$: upgradeUrl = `${accountUrl}/portal/upgrade`
|
||||||
|
|
||||||
export function show() {
|
export function show() {
|
||||||
appLimitModal.show()
|
appLimitModal.show()
|
||||||
|
@ -19,7 +20,7 @@
|
||||||
|
|
||||||
<Modal bind:this={appLimitModal} on:hide={onDismiss}>
|
<Modal bind:this={appLimitModal} on:hide={onDismiss}>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title="Upgrade to get more apps"
|
title="Upgrade to get more apps "
|
||||||
size="M"
|
size="M"
|
||||||
showCancelButton={false}
|
showCancelButton={false}
|
||||||
confirmText={$auth.user.accountPortalAccess ? "Upgrade" : "Confirm"}
|
confirmText={$auth.user.accountPortalAccess ? "Upgrade" : "Confirm"}
|
||||||
|
@ -31,10 +32,10 @@
|
||||||
>
|
>
|
||||||
<Body>
|
<Body>
|
||||||
You are currently on our <span class="free-plan">Free plan</span>. Upgrade
|
You are currently on our <span class="free-plan">Free plan</span>. Upgrade
|
||||||
to our Pro plan to get unlimited apps.
|
to our Pro plan to get unlimited apps and additional features.
|
||||||
</Body>
|
</Body>
|
||||||
{#if !$auth.user.accountPortalAccess}
|
{#if !$auth.user.accountPortalAccess}
|
||||||
<Body>Please contact the account holder.</Body>
|
<Body>Please contact the account holder to upgrade.</Body>
|
||||||
{/if}
|
{/if}
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -1,31 +1,40 @@
|
||||||
<script>
|
<script>
|
||||||
import { Modal, ModalContent, Body } from "@budibase/bbui"
|
import { Modal, ModalContent, Body, TooltipWrapper } from "@budibase/bbui"
|
||||||
import { licensing, auth, admin } from "stores/portal"
|
import { licensing, auth, admin } from "stores/portal"
|
||||||
|
|
||||||
export let onDismiss = () => {}
|
export let onDismiss = () => {}
|
||||||
export let onShow = () => {}
|
export let onShow = () => {}
|
||||||
|
|
||||||
let sessionsModal
|
let dayPassModal
|
||||||
|
|
||||||
const outOfSessionsTitle = "You are almost out of sessions"
|
$: accountUrl = $admin.accountPortalUrl
|
||||||
const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade`
|
$: upgradeUrl = `${accountUrl}/portal/upgrade`
|
||||||
|
|
||||||
$: daysRemaining = $licensing.quotaResetDaysRemaining
|
$: daysRemaining = $licensing.quotaResetDaysRemaining
|
||||||
$: sessionsUsed = $licensing.usageMetrics?.dayPasses
|
$: quotaResetDate = $licensing.quotaResetDate
|
||||||
|
$: dayPassesUsed = $licensing.usageMetrics?.dayPasses
|
||||||
|
$: dayPassesTitle =
|
||||||
|
dayPassesUsed >= 100
|
||||||
|
? "You have run out of Day Passes"
|
||||||
|
: "You are almost out of Day Passes"
|
||||||
|
$: dayPassesBody =
|
||||||
|
dayPassesUsed >= 100
|
||||||
|
? "Upgrade your account to bring your apps back online."
|
||||||
|
: "Upgrade your account to prevent your apps from going offline."
|
||||||
|
|
||||||
export function show() {
|
export function show() {
|
||||||
sessionsModal.show()
|
dayPassModal.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hide() {
|
export function hide() {
|
||||||
sessionsModal.hide()
|
dayPassModal.hide()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={sessionsModal} on:show={onShow} on:hide={onDismiss}>
|
<Modal bind:this={dayPassModal} on:show={onShow} on:hide={onDismiss}>
|
||||||
{#if $auth.user.accountPortalAccess}
|
{#if $auth.user.accountPortalAccess}
|
||||||
<ModalContent
|
<ModalContent
|
||||||
title={outOfSessionsTitle}
|
title={dayPassesTitle}
|
||||||
size="M"
|
size="M"
|
||||||
confirmText="Upgrade"
|
confirmText="Upgrade"
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
|
@ -33,22 +42,37 @@
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Body>
|
<Body>
|
||||||
You have used <span class="session_percent">{sessionsUsed}%</span> of
|
You have used <span class="daypass_percent">{dayPassesUsed}%</span> of
|
||||||
your plans Day Passes with {daysRemaining} day{daysRemaining == 1
|
your plans Day Passes with {daysRemaining} day{daysRemaining == 1
|
||||||
? ""
|
? ""
|
||||||
: "s"} remaining.
|
: "s"} remaining.
|
||||||
|
<span class="tooltip">
|
||||||
|
<TooltipWrapper tooltip={quotaResetDate} size="S" />
|
||||||
|
</span>
|
||||||
</Body>
|
</Body>
|
||||||
<Body>Upgrade your account to prevent your apps from going offline.</Body>
|
<Body>{dayPassesBody}</Body>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
{:else}
|
{:else}
|
||||||
<ModalContent title={outOfSessionsTitle} size="M" showCancelButton={false}>
|
<ModalContent title={dayPassesTitle} size="M" showCancelButton={false}>
|
||||||
<Body>
|
<Body>
|
||||||
You have used <span class="session_percent">{sessionsUsed}%</span> of
|
You have used <span class="daypass_percent">{dayPassesUsed}%</span> of
|
||||||
your plans Day Passes with {daysRemaining} day{daysRemaining == 1
|
your plans Day Passes with {daysRemaining} day{daysRemaining == 1
|
||||||
? ""
|
? ""
|
||||||
: "s"} remaining.
|
: "s"} remaining.
|
||||||
|
<span class="tooltip">
|
||||||
|
<TooltipWrapper tooltip={quotaResetDate} size="S" />
|
||||||
|
</span>
|
||||||
</Body>
|
</Body>
|
||||||
<Body>Please contact your account holder.</Body>
|
<Body>Please contact your account holder to upgrade.</Body>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
{/if}
|
{/if}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tooltip {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.tooltip :global(.icon-container) {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import PaymentFailedModal from "./PaymentFailedModal.svelte"
|
import PaymentFailedModal from "./PaymentFailedModal.svelte"
|
||||||
import AccountDowngradedModal from "./AccountDowngradedModal.svelte"
|
import AccountDowngradedModal from "./AccountDowngradedModal.svelte"
|
||||||
import { ExpiringKeys } from "./constants"
|
import { ExpiringKeys } from "./constants"
|
||||||
import { getBanners } from "./banners"
|
import { getBanners } from "./licensingBanners"
|
||||||
import { banner } from "@budibase/bbui"
|
import { banner } from "@budibase/bbui"
|
||||||
|
|
||||||
const oneDayInSeconds = 86400
|
const oneDayInSeconds = 86400
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
{
|
{
|
||||||
key: ExpiringKeys.LICENSING_PAYMENT_FAILED,
|
key: ExpiringKeys.LICENSING_PAYMENT_FAILED,
|
||||||
criteria: () => {
|
criteria: () => {
|
||||||
return $licensing.accountPastDue
|
return $licensing.accountPastDue && !$licensing.isFreePlan()
|
||||||
},
|
},
|
||||||
action: () => {
|
action: () => {
|
||||||
paymentFailedModal.show()
|
paymentFailedModal.show()
|
||||||
|
@ -69,14 +69,7 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (userLoaded && licensingLoaded && loaded) {
|
const showNextModal = () => {
|
||||||
queuedModals = processModals()
|
|
||||||
queuedBanners = getBanners()
|
|
||||||
showNext()
|
|
||||||
banner.queue(queuedBanners)
|
|
||||||
}
|
|
||||||
|
|
||||||
const showNext = () => {
|
|
||||||
if (currentModalCfg) {
|
if (currentModalCfg) {
|
||||||
currentModalCfg.cache()
|
currentModalCfg.cache()
|
||||||
}
|
}
|
||||||
|
@ -88,6 +81,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: if (userLoaded && licensingLoaded && loaded) {
|
||||||
|
queuedModals = processModals()
|
||||||
|
queuedBanners = getBanners()
|
||||||
|
showNextModal()
|
||||||
|
banner.queue(queuedBanners)
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
auth.subscribe(state => {
|
auth.subscribe(state => {
|
||||||
if (state.user && !userLoaded) {
|
if (state.user && !userLoaded) {
|
||||||
|
@ -100,18 +100,13 @@
|
||||||
licensingLoaded = true
|
licensingLoaded = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
temporalStore.subscribe(state => {
|
|
||||||
console.log("Stored temporal ", state)
|
|
||||||
})
|
|
||||||
|
|
||||||
loaded = true
|
loaded = true
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DayPassWarningModal bind:this={dayPassModal} onDismiss={showNext} />
|
<DayPassWarningModal bind:this={dayPassModal} onDismiss={showNextModal} />
|
||||||
<PaymentFailedModal bind:this={paymentFailedModal} onDismiss={showNext} />
|
<PaymentFailedModal bind:this={paymentFailedModal} onDismiss={showNextModal} />
|
||||||
<AccountDowngradedModal
|
<AccountDowngradedModal
|
||||||
bind:this={accountDowngradeModal}
|
bind:this={accountDowngradeModal}
|
||||||
onDismiss={showNext}
|
onDismiss={showNextModal}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -7,10 +7,11 @@
|
||||||
export let onShow = () => {}
|
export let onShow = () => {}
|
||||||
|
|
||||||
let paymentFailedModal
|
let paymentFailedModal
|
||||||
let pastDueAt
|
let pastDueEndDate
|
||||||
|
|
||||||
const paymentFailedTitle = "Payment failed"
|
const paymentFailedTitle = "Payment failed"
|
||||||
const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade`
|
$: accountUrl = $admin.accountPortalUrl
|
||||||
|
$: upgradeUrl = `${accountUrl}/portal/upgrade`
|
||||||
|
|
||||||
export function show() {
|
export function show() {
|
||||||
paymentFailedModal.show()
|
paymentFailedModal.show()
|
||||||
|
@ -21,12 +22,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
auth.subscribe(state => {
|
licensing.subscribe(state => {
|
||||||
if (state.user && state.user.license?.billing?.subscription) {
|
pastDueEndDate = state.pastDueEndDate
|
||||||
pastDueAt = new Date(
|
|
||||||
state.user.license?.billing?.subscription.pastDueAt * 1000
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -48,11 +45,11 @@
|
||||||
</Body>
|
</Body>
|
||||||
<Body weight={800}>
|
<Body weight={800}>
|
||||||
<div class="tooltip-root">
|
<div class="tooltip-root">
|
||||||
{`${$licensing.paymentDueDaysRemaining} day${
|
{`${$licensing.pastDueDaysRemaining} day${
|
||||||
$licensing.paymentDueDaysRemaining == 1 ? "" : "s"
|
$licensing.pastDueDaysRemaining == 1 ? "" : "s"
|
||||||
} remaining`}
|
} remaining`}
|
||||||
<span class="tooltip">
|
<span class="tooltip">
|
||||||
<TooltipWrapper tooltip={pastDueAt.toString()} size="S" />
|
<TooltipWrapper tooltip={pastDueEndDate} size="S" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Body>
|
</Body>
|
||||||
|
@ -67,11 +64,11 @@
|
||||||
<Body>Please contact your account holder.</Body>
|
<Body>Please contact your account holder.</Body>
|
||||||
<Body weight={800}>
|
<Body weight={800}>
|
||||||
<div class="tooltip-root">
|
<div class="tooltip-root">
|
||||||
{`${$licensing.paymentDueDaysRemaining} day${
|
{`${$licensing.pastDueDaysRemaining} day${
|
||||||
$licensing.paymentDueDaysRemaining == 1 ? "" : "s"
|
$licensing.pastDueDaysRemaining == 1 ? "" : "s"
|
||||||
} remaining`}
|
} remaining`}
|
||||||
<span class="tooltip">
|
<span class="tooltip">
|
||||||
<TooltipWrapper tooltip={pastDueAt.toString()} size="S" />
|
<TooltipWrapper tooltip={pastDueEndDate} size="S" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Body>
|
</Body>
|
||||||
|
|
|
@ -6,7 +6,7 @@ export const ExpiringKeys = {
|
||||||
LICENSING_APP_LIMIT_MODAL: "licensing_app_limit_modal",
|
LICENSING_APP_LIMIT_MODAL: "licensing_app_limit_modal",
|
||||||
LICENSING_ROWS_WARNING_BANNER: "licensing_rows_warning_banner",
|
LICENSING_ROWS_WARNING_BANNER: "licensing_rows_warning_banner",
|
||||||
LICENSING_AUTOMATIONS_WARNING_BANNER: "licensing_automations_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 = {
|
export const StripeStatus = {
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { ExpiringKeys } from "./constants"
|
||||||
import { temporalStore } from "builderStore"
|
import { temporalStore } from "builderStore"
|
||||||
import { admin, auth, licensing } from "stores/portal"
|
import { admin, auth, licensing } from "stores/portal"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
import { BANNER_TYPES } from "@budibase/bbui"
|
||||||
|
|
||||||
const oneDayInSeconds = 86400
|
const oneDayInSeconds = 86400
|
||||||
const upgradeUrl = `${get(admin).accountPortalUrl}/portal/upgrade`
|
|
||||||
|
|
||||||
const defaultCacheFn = key => {
|
const defaultCacheFn = key => {
|
||||||
temporalStore.actions.setExpiring(key, {}, oneDayInSeconds)
|
temporalStore.actions.setExpiring(key, {}, oneDayInSeconds)
|
||||||
|
@ -18,36 +18,47 @@ const defaultAction = key => {
|
||||||
extraButtonText: "Upgrade Plan",
|
extraButtonText: "Upgrade Plan",
|
||||||
extraButtonAction: () => {
|
extraButtonAction: () => {
|
||||||
defaultCacheFn(key)
|
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 appAuth = get(auth)
|
||||||
const appLicensing = get(licensing)
|
const appLicensing = get(licensing)
|
||||||
|
|
||||||
|
const displayPercent =
|
||||||
|
appLicensing?.usageMetrics[metricKey] > 100
|
||||||
|
? 100
|
||||||
|
: appLicensing?.usageMetrics[metricKey]
|
||||||
|
|
||||||
let bannerConfig = {
|
let bannerConfig = {
|
||||||
key: cacheKey,
|
key: cacheKey,
|
||||||
type: "info",
|
type: BANNER_TYPES.INFO,
|
||||||
onChange: () => {
|
onChange: () => {
|
||||||
defaultCacheFn(cacheKey)
|
defaultCacheFn(cacheKey)
|
||||||
},
|
},
|
||||||
message: `You have used ${
|
message: customMessage
|
||||||
appLicensing?.usageMetrics[metricKey]
|
? customMessage
|
||||||
}% of your monthly usage of ${metricLabel} with ${
|
: `You have used ${displayPercent}% of your monthly usage of ${metricLabel} with ${
|
||||||
appLicensing.quotaResetDaysRemaining
|
appLicensing.quotaResetDaysRemaining
|
||||||
} day${
|
} day${
|
||||||
appLicensing.quotaResetDaysRemaining == 1 ? "" : "s"
|
appLicensing.quotaResetDaysRemaining == 1 ? "" : "s"
|
||||||
} remaining. All apps will be taken offline if this limit is reached. ${
|
} remaining. ${
|
||||||
appAuth.user.accountPortalAccess
|
appAuth.user.accountPortalAccess
|
||||||
? ""
|
? ""
|
||||||
: "Please contact your account holder."
|
: "Please contact your account holder to upgrade"
|
||||||
}`,
|
}`,
|
||||||
criteria: () => {
|
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
|
return !get(auth).user.accountPortalAccess
|
||||||
|
@ -60,17 +71,18 @@ const buildUsageInfoBanner = (metricKey, metricLabel, cacheKey, percentage) => {
|
||||||
|
|
||||||
const buildDayPassBanner = () => {
|
const buildDayPassBanner = () => {
|
||||||
const appAuth = get(auth)
|
const appAuth = get(auth)
|
||||||
|
const appLicensing = get(licensing)
|
||||||
if (get(licensing)?.usageMetrics["dayPasses"] >= 100) {
|
if (get(licensing)?.usageMetrics["dayPasses"] >= 100) {
|
||||||
return {
|
return {
|
||||||
key: "max_dayPasses",
|
key: "max_dayPasses",
|
||||||
type: "negative",
|
type: BANNER_TYPES.NEGATIVE,
|
||||||
criteria: () => {
|
criteria: () => {
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
message: `Your apps are currently offline. You have exceeded your plans limit for Day Passes. ${
|
message: `Your apps are currently offline. You have exceeded your plans limit for Day Passes. ${
|
||||||
appAuth.user.accountPortalAccess
|
appAuth.user.accountPortalAccess
|
||||||
? ""
|
? ""
|
||||||
: "Please contact your account holder."
|
: "Please contact your account holder to upgrade."
|
||||||
}`,
|
}`,
|
||||||
...defaultAction(),
|
...defaultAction(),
|
||||||
showCloseButton: false,
|
showCloseButton: false,
|
||||||
|
@ -81,23 +93,35 @@ const buildDayPassBanner = () => {
|
||||||
"dayPasses",
|
"dayPasses",
|
||||||
"Day Passes",
|
"Day Passes",
|
||||||
ExpiringKeys.LICENSING_DAYPASS_WARNING_BANNER,
|
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 = () => {
|
const buildPaymentFailedBanner = () => {
|
||||||
return {
|
return {
|
||||||
key: "payment_Failed",
|
key: "payment_Failed",
|
||||||
type: "negative",
|
type: BANNER_TYPES.NEGATIVE,
|
||||||
criteria: () => {
|
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
|
message: `Payment Failed - Please update your billing details or your account will be downgrades in
|
||||||
${get(licensing)?.paymentDueDaysRemaining} day${
|
${get(licensing)?.pastDueDaysRemaining} day${
|
||||||
get(licensing)?.paymentDueDaysRemaining == 1 ? "" : "s"
|
get(licensing)?.pastDueDaysRemaining == 1 ? "" : "s"
|
||||||
}`,
|
}`,
|
||||||
...defaultAction(),
|
...defaultAction(),
|
||||||
showCloseButton: false,
|
showCloseButton: false,
|
||||||
|
tooltip: get(licensing).pastDueEndDate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +145,7 @@ export const getBanners = () => {
|
||||||
"queries",
|
"queries",
|
||||||
"Queries",
|
"Queries",
|
||||||
ExpiringKeys.LICENSING_QUERIES_WARNING_BANNER,
|
ExpiringKeys.LICENSING_QUERIES_WARNING_BANNER,
|
||||||
90 // could be an array [50,75,90]
|
90
|
||||||
),
|
),
|
||||||
].filter(licensingBanner => {
|
].filter(licensingBanner => {
|
||||||
return (
|
return (
|
|
@ -13,13 +13,14 @@
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { onMount } from "svelte"
|
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 { goto } from "@roxi/routify"
|
||||||
import { AppStatus } from "constants"
|
import { AppStatus } from "constants"
|
||||||
import { gradient } from "actions"
|
import { gradient } from "actions"
|
||||||
import UpdateUserInfoModal from "components/settings/UpdateUserInfoModal.svelte"
|
import UpdateUserInfoModal from "components/settings/UpdateUserInfoModal.svelte"
|
||||||
import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte"
|
import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte"
|
||||||
import { processStringSync } from "@budibase/string-templates"
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
|
import Spaceman from "assets/bb-space-man.svg"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
|
@ -91,7 +92,7 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<Layout noPadding>
|
<Layout noPadding>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<img alt="logo" src={$organisation.logoUrl || Logo} />
|
<img class="logo" alt="logo" src={$organisation.logoUrl || Logo} />
|
||||||
<ActionMenu align="right" dataCy="user-menu">
|
<ActionMenu align="right" dataCy="user-menu">
|
||||||
<div slot="control" class="avatar">
|
<div slot="control" class="avatar">
|
||||||
<Avatar
|
<Avatar
|
||||||
|
@ -131,7 +132,17 @@
|
||||||
</Body>
|
</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider />
|
<Divider />
|
||||||
{#if userApps.length}
|
{#if $licensing.usageMetrics.dayPasses >= 100}
|
||||||
|
<div>
|
||||||
|
<Layout gap="S" justifyItems="center">
|
||||||
|
<img class="spaceman" alt="spaceman" src={Spaceman} />
|
||||||
|
<Heading size="M">
|
||||||
|
{"Your apps are currently offline."}
|
||||||
|
</Heading>
|
||||||
|
Please contact the account holder to get them back online.
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
{:else if userApps.length}
|
||||||
<Heading>Apps</Heading>
|
<Heading>Apps</Heading>
|
||||||
<div class="group">
|
<div class="group">
|
||||||
<Layout gap="S" noPadding>
|
<Layout gap="S" noPadding>
|
||||||
|
@ -194,10 +205,13 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
img {
|
img.logo {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
margin-bottom: -12px;
|
margin-bottom: -12px;
|
||||||
}
|
}
|
||||||
|
img.spaceman {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
.avatar {
|
.avatar {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
|
|
|
@ -8,7 +8,6 @@ export const createLicensingStore = () => {
|
||||||
const DEFAULT = {
|
const DEFAULT = {
|
||||||
plans: {},
|
plans: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const oneDayInMilliseconds = 86400000
|
const oneDayInMilliseconds = 86400000
|
||||||
|
|
||||||
const store = writable(DEFAULT)
|
const store = writable(DEFAULT)
|
||||||
|
@ -26,8 +25,7 @@ export const createLicensingStore = () => {
|
||||||
getUsageMetrics: async () => {
|
getUsageMetrics: async () => {
|
||||||
const quota = get(store).quotaUsage
|
const quota = get(store).quotaUsage
|
||||||
const license = get(auth).user.license
|
const license = get(auth).user.license
|
||||||
const now = Date.now()
|
const now = new Date()
|
||||||
const nowSeconds = now / 1000
|
|
||||||
|
|
||||||
const getMetrics = (keys, license, quota) => {
|
const getMetrics = (keys, license, quota) => {
|
||||||
if (!license || !quota || !keys) {
|
if (!license || !quota || !keys) {
|
||||||
|
@ -36,16 +34,12 @@ export const createLicensingStore = () => {
|
||||||
return keys.reduce((acc, key) => {
|
return keys.reduce((acc, key) => {
|
||||||
const quotaLimit = license[key].value
|
const quotaLimit = license[key].value
|
||||||
const quotaUsed = (quota[key] / quotaLimit) * 100
|
const quotaUsed = (quota[key] / quotaLimit) * 100
|
||||||
|
|
||||||
// Catch for sessions
|
|
||||||
key = key === "sessions" ? "dayPasses" : key
|
|
||||||
|
|
||||||
acc[key] = quotaLimit > -1 ? Math.round(quotaUsed) : -1
|
acc[key] = quotaLimit > -1 ? Math.round(quotaUsed) : -1
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
const monthlyMetrics = getMetrics(
|
const monthlyMetrics = getMetrics(
|
||||||
["sessions", "queries", "automations"],
|
["dayPasses", "queries", "automations"],
|
||||||
license.quotas.usage.monthly,
|
license.quotas.usage.monthly,
|
||||||
quota.monthly.current
|
quota.monthly.current
|
||||||
)
|
)
|
||||||
|
@ -55,52 +49,50 @@ export const createLicensingStore = () => {
|
||||||
quota.usageQuota
|
quota.usageQuota
|
||||||
)
|
)
|
||||||
|
|
||||||
// DEBUG
|
const getDaysBetween = (dateStart, dateEnd) => {
|
||||||
console.log("Store licensing val ", {
|
return dateEnd > dateStart
|
||||||
...monthlyMetrics,
|
? Math.round(
|
||||||
...staticMetrics,
|
(dateEnd.getTime() - dateStart.getTime()) / oneDayInMilliseconds
|
||||||
})
|
)
|
||||||
|
: 0
|
||||||
let subscriptionDaysRemaining
|
|
||||||
if (license?.billing?.subscription) {
|
|
||||||
const currentPeriodEnd = license.billing.subscription.currentPeriodEnd
|
|
||||||
const currentPeriodEndMilliseconds = currentPeriodEnd * 1000
|
|
||||||
|
|
||||||
subscriptionDaysRemaining = Math.round(
|
|
||||||
(currentPeriodEndMilliseconds - now) / oneDayInMilliseconds
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const quotaResetDaysRemaining =
|
const quotaResetDate = new Date(quota.quotaReset)
|
||||||
quota.quotaReset > now
|
const quotaResetDaysRemaining = getDaysBetween(now, quotaResetDate)
|
||||||
? Math.round((quota.quotaReset - now) / oneDayInMilliseconds)
|
|
||||||
: 0
|
|
||||||
|
|
||||||
const accountDowngraded =
|
const accountDowngraded =
|
||||||
|
license?.billing?.subscription?.downgradeAt &&
|
||||||
|
license?.billing?.subscription?.downgradeAt <= now.getTime() &&
|
||||||
license?.billing?.subscription?.status === StripeStatus.PAST_DUE &&
|
license?.billing?.subscription?.status === StripeStatus.PAST_DUE &&
|
||||||
license?.plan === Constants.PlanType.FREE
|
license?.plan.type === Constants.PlanType.FREE
|
||||||
|
|
||||||
const accountPastDue =
|
const pastDueAtMilliseconds = license?.billing?.subscription?.pastDueAt
|
||||||
nowSeconds >= license?.billing?.subscription?.currentPeriodEnd &&
|
const downgradeAtMilliseconds =
|
||||||
nowSeconds <= license?.billing?.subscription?.pastDueAt &&
|
license?.billing?.subscription?.downgradeAt
|
||||||
license?.billing?.subscription?.status === StripeStatus.PAST_DUE &&
|
let pastDueDaysRemaining
|
||||||
!accountDowngraded
|
let pastDueEndDate
|
||||||
|
|
||||||
const pastDueAtSeconds = license?.billing?.subscription?.pastDueAt
|
if (pastDueAtMilliseconds && downgradeAtMilliseconds) {
|
||||||
const pastDueAtMilliseconds = pastDueAtSeconds * 1000
|
pastDueEndDate = new Date(downgradeAtMilliseconds)
|
||||||
const paymentDueDaysRemaining = Math.round(
|
pastDueDaysRemaining = getDaysBetween(
|
||||||
(pastDueAtMilliseconds - now) / oneDayInMilliseconds
|
new Date(pastDueAtMilliseconds),
|
||||||
)
|
pastDueEndDate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
usageMetrics: { ...monthlyMetrics, ...staticMetrics },
|
usageMetrics: { ...monthlyMetrics, ...staticMetrics },
|
||||||
subscriptionDaysRemaining,
|
|
||||||
paymentDueDaysRemaining,
|
|
||||||
quotaResetDaysRemaining,
|
quotaResetDaysRemaining,
|
||||||
|
quotaResetDate,
|
||||||
accountDowngraded,
|
accountDowngraded,
|
||||||
accountPastDue,
|
accountPastDue: pastDueAtMilliseconds != null,
|
||||||
|
pastDueEndDate,
|
||||||
|
pastDueDaysRemaining,
|
||||||
|
isFreePlan: () => {
|
||||||
|
return license?.plan.type === Constants.PlanType.FREE
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue