diff --git a/packages/builder/src/components/usage/Usage.svelte b/packages/builder/src/components/usage/Usage.svelte index cd9071785d..27383019e8 100644 --- a/packages/builder/src/components/usage/Usage.svelte +++ b/packages/builder/src/components/usage/Usage.svelte @@ -1,10 +1,13 @@
- +
+ {#if showWarning} + + {/if} +
+ {usage.name} +
+
{#if unlimited} - {usage.used} + {usage.used} / Unlimited {:else} {usage.used} / {usage.total} {/if}
{#if unlimited} - Unlimited + {:else} - + + {/if} + {#if showWarning} + + To get more queries upgrade your plan + {/if}
@@ -51,6 +82,13 @@ display: flex; flex-direction: row; justify-content: space-between; - gap: var(--spacing-m); + margin-bottom: 12px; + } + .header-container { + display: flex; + } + .heading { + margin-top: 3px; + margin-left: 5px; } diff --git a/packages/builder/src/components/usage/UsageDashCard.svelte b/packages/builder/src/components/usage/UsageDashCard.svelte new file mode 100644 index 0000000000..ff0ac7ca49 --- /dev/null +++ b/packages/builder/src/components/usage/UsageDashCard.svelte @@ -0,0 +1,108 @@ + + +
+
+
+ +
+ {description} +
+ {title} +
+ {subtitle} +
+
+ {#each textRows as row} + {row} + {/each} +
+
+
+
+ {#if secondaryDefined} +
+ +
+ {/if} + {#if primaryDefined} +
+ +
+ {/if} +
+
+
+ +
+
+ + diff --git a/packages/builder/src/components/usage/index.js b/packages/builder/src/components/usage/index.js new file mode 100644 index 0000000000..48c0e2ea2c --- /dev/null +++ b/packages/builder/src/components/usage/index.js @@ -0,0 +1,2 @@ +export { default as Usage } from "./Usage.svelte" +export { default as DashCard } from "./UsageDashCard.svelte" diff --git a/packages/builder/src/constants/index.js b/packages/builder/src/constants/index.js index 4e2ca37b9c..bcfbfffb81 100644 --- a/packages/builder/src/constants/index.js +++ b/packages/builder/src/constants/index.js @@ -57,3 +57,10 @@ export const DefaultAppTheme = { navBackground: "var(--spectrum-global-color-gray-50)", navTextColor: "var(--spectrum-global-color-gray-800)", } + +export const PlanType = { + FREE: "free", + PRO: "pro", + BUSINESS: "business", + ENTERPRISE: "enterprise", +} diff --git a/packages/builder/src/pages/builder/portal/settings/usage.svelte b/packages/builder/src/pages/builder/portal/settings/usage.svelte index 069c37b555..6ee7e45e25 100644 --- a/packages/builder/src/pages/builder/portal/settings/usage.svelte +++ b/packages/builder/src/pages/builder/portal/settings/usage.svelte @@ -5,20 +5,39 @@ Heading, Layout, notifications, - Link, + Page, + Detail, } from "@budibase/bbui" import { onMount } from "svelte" - import { admin, auth, licensing } from "stores/portal" - import Usage from "components/usage/Usage.svelte" + import { admin, auth, licensing } from "../../../../stores/portal" + import { PlanType } from "../../../../constants" + import { DashCard, Usage } from "../../../../components/usage" let staticUsage = [] let monthlyUsage = [] + let price + let lastPayment + let cancelAt + let nextPayment + let balance let loaded = false + let textRows = [] + let daysRemainingInMonth + + const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` + const manageUrl = `${$admin.accountPortalUrl}/portal/billing` + + const warnUsage = ["Queries", "Automations", "Rows"] $: quotaUsage = $licensing.quotaUsage $: license = $auth.user?.license - const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` + const numberFormatter = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }) const setMonthlyUsage = () => { monthlyUsage = [] @@ -34,6 +53,7 @@ } } } + monthlyUsage = monthlyUsage.sort((a, b) => a.name.localeCompare(b.name)) } const setStaticUsage = () => { @@ -48,6 +68,52 @@ }) } } + staticUsage = staticUsage.sort((a, b) => a.name.localeCompare(b.name)) + } + + const setNextPayment = () => { + const periodEnd = license?.billing.subscription?.currentPeriodEnd + const cancelAt = license?.billing.subscription?.cancelAt + if (periodEnd) { + if (cancelAt && periodEnd <= cancelAt) { + return + } + nextPayment = `Next payment: ${getLocaleDataString(periodEnd)}` + } + } + + const setCancelAt = () => { + cancelAt = license?.billing.subscription?.cancelAt + } + + const setLastPayment = () => { + const periodStart = license?.billing.subscription?.currentPeriodStart + if (periodStart) { + lastPayment = `Last payment: ${getLocaleDataString(periodStart)}` + } + } + + const setBalance = () => { + const customerBalance = license?.billing.customer.balance + if (customerBalance) { + balance = `Balance: ${numberFormatter.format( + (customerBalance / 100) * -1 + )}` + } + } + + const getLocaleDataString = epoch => { + const date = new Date(epoch * 1000) + return date.toLocaleDateString("default", { + day: "numeric", + month: "long", + year: "numeric", + }) + } + + const setPrice = () => { + const planPrice = license.plan.price + price = `${numberFormatter.format(planPrice.amountMonthly / 100)} per month` } const capitalise = string => { @@ -56,6 +122,69 @@ } } + const planTitle = () => { + return capitalise(license?.plan.type) + } + + const planSubtitle = () => { + return `${license?.plan.price.sessions} day passes` + } + + const getDaysRemaining = timestamp => { + if (!timestamp) { + return + } + const now = new Date() + now.setHours(0) + 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 = () => { + textRows = [] + + if (cancelAt) { + textRows.push("Subscription has been cancelled") + textRows.push(`${getDaysRemaining(cancelAt * 1000)} days remaining`) + } else { + if (price) { + textRows.push(price) + } + if (lastPayment) { + textRows.push(lastPayment) + } + if (nextPayment) { + textRows.push(nextPayment) + } + } + } + + const setDaysRemainingInMonth = () => { + let now = new Date() + now = new Date(now.getFullYear(), now.getMonth(), now.getDate()) + + const firstNextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1) + const difference = firstNextMonth.getTime() - now.getTime() + + // return the difference in days + daysRemainingInMonth = (difference / (1000 * 3600 * 24)).toFixed(0) + } + + const goToAccountPortal = () => { + if (license?.plan.type === PlanType.FREE) { + window.location.href = upgradeUrl + } else { + window.location.href = manageUrl + } + } + const init = async () => { try { await licensing.getQuotaUsage() @@ -71,69 +200,99 @@ }) $: { - if (license && quotaUsage) { - setMonthlyUsage() - setStaticUsage() + if (license) { + setPrice() + setBalance() + setLastPayment() + setNextPayment() + setCancelAt() + setTextRows() + setDaysRemainingInMonth() + + if (quotaUsage) { + setMonthlyUsage() + setStaticUsage() + } } } -{#if loaded} - - Usage - Get information about your current usage within Budibase. - {#if $admin.cloud} - {#if $auth.user?.accountPortalAccess} - To upgrade your plan and usage limits visit your Account. - {:else} - Contact your account holder to upgrade your usage limits. - {/if} - {/if} - - - - - - - - YOUR PLAN - {capitalise(license?.plan.type)} - - - USAGE -
- {#each staticUsage as usage} -
- -
- {/each} -
-
- {#if monthlyUsage.length} - - MONTHLY -
- {#each monthlyUsage as usage} -
- -
- {/each} -
+ + {#if loaded} + + + Billing + Get information about your current usage and manage your plan -
- {/if} - -{/if} + + + + +
+ + {#each staticUsage as usage} +
+ +
+ {/each} +
+
+
+ {#if monthlyUsage.length} +
+ + Monthly +
+ Resets in {daysRemainingInMonth} days +
+
+ + {#each monthlyUsage as usage} +
+ +
+ {/each} +
+
+
+
+ {/if} +
+
+ + {/if} +