diff --git a/scripts/tsc-with-whitelist.sh b/scripts/tsc-with-whitelist.sh index 5abbe618948..a7da250e2db 100755 --- a/scripts/tsc-with-whitelist.sh +++ b/scripts/tsc-with-whitelist.sh @@ -9,6 +9,7 @@ web-admin/src/routes/[organization]/-/settings/+layout.ts: error TS2307 web-admin/src/routes/[organization]/-/settings/+page.ts: error TS2307 web-admin/src/routes/[organization]/-/settings/billing/+page.ts: error TS2307 web-admin/src/routes/[organization]/-/settings/billing/payment/+page.ts: error TS2307 +web-admin/src/routes/[organization]/-/settings/billing/estimate/+page.ts: error TS2307 web-admin/src/routes/[organization]/-/settings/billing/upgrade/+page.ts: error TS2307 web-admin/src/routes/[organization]/-/settings/usage/+page.ts: error TS2307 web-admin/src/routes/[organization]/-/upgrade-callback/+page.ts: error TS2307 diff --git a/web-admin/src/features/billing/Payment.svelte b/web-admin/src/features/billing/Payment.svelte index bb936dab020..b63514af87d 100644 --- a/web-admin/src/features/billing/Payment.svelte +++ b/web-admin/src/features/billing/Payment.svelte @@ -1,67 +1,112 @@ - -{#if !$categorisedIssues.isLoading && $org.data?.organization?.paymentCustomerId && !onTrial && !pendingSetup} - -
+
+

Payment methods

+
+
{#if paymentIssues?.length} - - {getPaymentIssueErrorText(paymentIssues)} Please click Manage below to correct. +
+ + + {getPaymentIssueErrorText(paymentIssues)} + +
+ {:else if hasPaymentCustomer} + +
+
+ + + +
+
+

+ Payment method on file +

+

Manage your cards via Stripe

+
+
{:else} - Your payment method is valid and good to go. + No payment method on file. {/if}
- {#snippet action()} - - {/snippet} - -{/if} + {#if hasPaymentCustomer} + + {/if} +
+
+ + diff --git a/web-admin/src/features/billing/contact/BillingContactSetting.svelte b/web-admin/src/features/billing/contact/BillingContactSetting.svelte index 83f4149eba0..6b5fb987850 100644 --- a/web-admin/src/features/billing/contact/BillingContactSetting.svelte +++ b/web-admin/src/features/billing/contact/BillingContactSetting.svelte @@ -1,9 +1,7 @@ - -
- {#if $billingContactUser} - - {:else} - This org has no billing contact. - {/if} -
- {#snippet action()} - - {/snippet} -
+ +
+ + + diff --git a/web-admin/src/features/billing/plans/CancelledTeamPlan.svelte b/web-admin/src/features/billing/plans/CancelledTeamPlan.svelte index b332d700001..8df2ccd06d4 100644 --- a/web-admin/src/features/billing/plans/CancelledTeamPlan.svelte +++ b/web-admin/src/features/billing/plans/CancelledTeamPlan.svelte @@ -13,10 +13,12 @@ organization, plan, showUpgradeDialog, + billingPortalUrl, }: { organization: string; plan: V1BillingPlan; showUpgradeDialog: boolean; + billingPortalUrl: string | undefined; } = $props(); let categorisedIssues = $derived( @@ -52,6 +54,16 @@ and your subscription has ended. {/if} + {#if billingPortalUrl} +
+ View Invoice +
+ {/if} {#if plan} @@ -77,3 +89,12 @@ endDate={cancelledSubIssue?.metadata.subscriptionCancelled?.endDate} /> {/if} + + diff --git a/web-admin/src/features/billing/plans/POCPlan.svelte b/web-admin/src/features/billing/plans/POCPlan.svelte index 59a7438a9cb..c2e2d93c6b4 100644 --- a/web-admin/src/features/billing/plans/POCPlan.svelte +++ b/web-admin/src/features/billing/plans/POCPlan.svelte @@ -10,10 +10,12 @@ organization, hasPayment, plan, + billingPortalUrl, }: { organization: string; hasPayment: boolean; plan: V1BillingPlan; + billingPortalUrl: string | undefined; } = $props(); let open = $state(false); @@ -22,6 +24,16 @@
You're currently on a custom contract.
+ {#if billingPortalUrl} + + {/if}
{#snippet contact()} @@ -38,3 +50,12 @@
+ + diff --git a/web-admin/src/features/billing/plans/Plan.svelte b/web-admin/src/features/billing/plans/Plan.svelte index 5a5cc6ef62a..09e4a38e813 100644 --- a/web-admin/src/features/billing/plans/Plan.svelte +++ b/web-admin/src/features/billing/plans/Plan.svelte @@ -1,63 +1,510 @@ -{#if neverSubbed} - -{:else if isTrial} - -{:else if subHasEnded} - -{:else if subIsFreePlan} - -{:else if subIsProPlan} - -{:else if subIsTeamPlan} - -{:else if subIsManagedPlan} - -{:else if subIsEnterprisePlan} - -{/if} +
+

Plan

+ +
+ +
+
+ {#if currentPlan === "enterprise"} + Enterprise + Custom contract · Fully managed + {:else if currentPlan === "trial"} + Free Trial + {#if isTrialExpired} + Trial expired · Projects hibernated + {:else} + 30 day free trial + {/if} + {:else if currentPlan === "pro"} + Pro + Usage based pricing. $0.15/unit/hr · $1/GB storage/mo + {:else if currentPlan === "team"} + Team (Legacy) + $250/mo flat + storage + {/if} +
+ +
+ {#if currentPlan === "trial"} + + {:else if currentPlan === "team"} + + + {/if} + + + + + + $0.15/unit/hr · $1/GB storage/mo. Cancel anytime. + + +
+
+ + {#if currentPlan === "enterprise"} +

+ Fully managed slots, dedicated CSM, white-label capabilities, and custom + SLAs. Contact your CSM for contract details or changes. +

+ {/if} + + {#if currentPlan === "trial" && trialEndDate} +
+
+
+ Days used +

+ {trialDaysUsed} +

+
+
+ Days remaining +

7} + class:text-red-600={trialDaysRemaining <= 7} + > + {trialDaysRemaining} +

+
+
+
+
+
+
+ + {trialPercent}% of trial used, projects will hibernate when trial + ends + + 30 days +
+
+ {/if} + + {#if currentPlan !== "enterprise" && currentPlan !== "trial"} +
+ Current period estimate + {totalEstimate} + {#if cycleStart || cycleEnd} + {formatCycleDate(cycleStart)} – {formatCycleDate(cycleEnd)} + {/if} +
+ {/if} + + {#if currentPlan !== "enterprise"} + +
+
+
+ {prodCost} + {prodSlots} Prod Compute Units +
+
+ {devCost} + {devSlots} Dev Compute Units +
+
+ {storageCost} + {totalStorage > 0 ? formatMemorySize(totalStorage) : "0 B"} Storage +
+
+ + View Usage + + + + +
+ {/if} + + + {#if showComparePlans} + + + {#if comparePlansOpen} + + {/if} + {/if} +
+
+ + + + diff --git a/web-admin/src/features/billing/plans/PlanCards.svelte b/web-admin/src/features/billing/plans/PlanCards.svelte new file mode 100644 index 00000000000..113939e43a1 --- /dev/null +++ b/web-admin/src/features/billing/plans/PlanCards.svelte @@ -0,0 +1,268 @@ + + +
+ {#if showTrial} +
+

Free Trial

+

30 day free trial

+

No credit card required

+ +
    + {#each trialFeatures as feature} +
  • + + + + {feature} +
  • + {/each} +
+ + +
+ {/if} + + {#if showTrial && (showTeam || showPro)} + + {/if} + + {#if showTeam} +
+

Team plan (legacy)

+

$250/mo

+

Flat rate + storage

+ +
    + {#each teamFeatures as feature} +
  • + + + + {feature} +
  • + {/each} +
+ + +
+ + {/if} + + {#if showPro} +
+

Pro

+

Usage based pricing

+ + +
    + {#each proFeatures as feature} +
  • + + + + {feature} +
  • + {/each} +
+ + {#if currentPlan === "pro"} + + {:else} + + {/if} +
+ {/if} + + {#if showPro && showEnterprise} + + {/if} + + {#if showEnterprise} +
+

Enterprise

+

Custom pricing

+

Annual contract

+ +
    + {#each enterpriseFeatures as feature} +
  • + + + + {feature} +
  • + {/each} +
+ + +
+ {/if} +
+ + + + diff --git a/web-admin/src/features/billing/plans/TeamPlan.svelte b/web-admin/src/features/billing/plans/TeamPlan.svelte index ca992719673..7357deb9fca 100644 --- a/web-admin/src/features/billing/plans/TeamPlan.svelte +++ b/web-admin/src/features/billing/plans/TeamPlan.svelte @@ -28,10 +28,12 @@ organization, subscription, plan, + billingPortalUrl, }: { organization: string; subscription: V1Subscription; plan: V1BillingPlan; + billingPortalUrl: string | undefined; } = $props(); let planCanceller = $derived(createAdminServiceCancelBillingSubscription()); @@ -63,11 +65,16 @@
Next billing cycle will start on {getNextBillingCycleDate(subscription.currentBillingCycleEndDate)}. - See pricing details -> + {#if billingPortalUrl} + + {/if}
{#snippet contact()} @@ -115,3 +122,12 @@ {/snippet}
+ + diff --git a/web-admin/src/features/projects/status/overview/DeploymentSection.svelte b/web-admin/src/features/projects/status/overview/DeploymentSection.svelte index c6f3c752d5b..f96e0ad59ef 100644 --- a/web-admin/src/features/projects/status/overview/DeploymentSection.svelte +++ b/web-admin/src/features/projects/status/overview/DeploymentSection.svelte @@ -245,7 +245,7 @@ {formatMemorySize(dataSizeBytes)} @@ -281,4 +281,9 @@ .repo-link:hover { @apply underline; } + .data-size-link { + @apply no-underline; + color: inherit; + font: inherit; + } diff --git a/web-admin/src/routes/[organization]/-/settings/+layout.svelte b/web-admin/src/routes/[organization]/-/settings/+layout.svelte index d2507e0479b..8268c41dfdd 100644 --- a/web-admin/src/routes/[organization]/-/settings/+layout.svelte +++ b/web-admin/src/routes/[organization]/-/settings/+layout.svelte @@ -1,36 +1,50 @@ @@ -42,7 +56,7 @@ minWidth="180px" />
- + {@render children()}
diff --git a/web-admin/src/routes/[organization]/-/settings/billing/+page.svelte b/web-admin/src/routes/[organization]/-/settings/billing/+page.svelte index 595b023f850..fb148a77af2 100644 --- a/web-admin/src/routes/[organization]/-/settings/billing/+page.svelte +++ b/web-admin/src/routes/[organization]/-/settings/billing/+page.svelte @@ -2,23 +2,46 @@ import { createAdminServiceGetBillingSubscription, createAdminServiceListOrganizationBillingIssues, + V1BillingPlanType, } from "@rilldata/web-admin/client"; import { mergedQueryStatus } from "@rilldata/web-admin/client/utils"; import BillingContactSetting from "@rilldata/web-admin/features/billing/contact/BillingContactSetting.svelte"; import Payment from "@rilldata/web-admin/features/billing/Payment.svelte"; import Plan from "@rilldata/web-admin/features/billing/plans/Plan.svelte"; + import { + isEnterprisePlan, + isManagedPlan, + } from "@rilldata/web-admin/features/billing/plans/utils"; import Spinner from "@rilldata/web-common/features/entity-management/Spinner.svelte"; import { EntityStatus } from "@rilldata/web-common/features/entity-management/types"; import type { PageData } from "./$types"; - export let data: PageData; - - $: ({ organization, showUpgradeDialog } = data); + let { data }: { data: PageData } = $props(); - $: allStatus = mergedQueryStatus([ + let organization = $derived(data.organization); + let showUpgradeDialog = $derived(data.showUpgradeDialog); + let subscriptionQuery = $derived( createAdminServiceGetBillingSubscription(organization), - createAdminServiceListOrganizationBillingIssues(organization), - ]); + ); + let planType = $derived( + $subscriptionQuery?.data?.subscription?.plan?.planType, + ); + let planName = $derived( + $subscriptionQuery?.data?.subscription?.plan?.name ?? "", + ); + let isEnterprise = $derived( + planType === V1BillingPlanType.BILLING_PLAN_TYPE_ENTERPRISE || + planType === V1BillingPlanType.BILLING_PLAN_TYPE_MANAGED || + isManagedPlan(planName) || + isEnterprisePlan(planName), + ); + + let allStatus = $derived( + mergedQueryStatus([ + subscriptionQuery, + createAdminServiceListOrganizationBillingIssues(organization), + ]), + ); + + + + + Billing + +

Estimate your cost

+ + + +
+ +
+ +
+ +
+
+ Compute + 1 unit = 4 GiB RAM, 1 vCPU +
+ $0.15/unit/hr +
+ + +
+
+ Production + Minimum 2 units +
+
+ + clampInput(e, 2, 9999, (v) => (prodUnits = v))} + /> + +
+
+ + +
+
+ + Active hours per day + + + + + + Deployments hibernate when inactive, so you're only billed for + active hours. + + + +
+
+ + clampInput(e, 1, 24, (v) => (prodHoursPerDay = v))} + /> + +
+
+ +
+ + +
+
+ Development +
+
+ + clampInput(e, 0, 9999, (v) => (devUnits = v))} + /> + +
+
+ + +
+
+ + Active hours per day + + + + + + Deployments hibernate when inactive, so you're only billed for + active hours. + + + +
+
+ + clampInput(e, 1, 24, (v) => (devHoursPerDay = v))} + /> + +
+
+ +
+ + +
+
+ Storage + $1/GB/mo above 1 GB free +
+ +
+
+ Storage (GB) +
+
+ + clampInput(e, 1, 9999, (v) => (storageGB = v))} + /> + +
+
+
+
+ + +
+

Estimated monthly cost

+ {fmtUSD(monthlyCost)} + ~{fmtUSD(dailyCost)}/day + +
+ + +
+
+ Production + + {prodUnits} units × {prodHoursPerDay} hrs/day × {DAYS_PER_MONTH} days + × ${RATE_PER_UNIT_HR.toFixed(2)}/unit/hr + +
+ {fmtUSD(prodCost)} +
+ + +
+
+ Development + + {devUnits} units × {devHoursPerDay} hrs/day × {DAYS_PER_MONTH} days × + ${RATE_PER_UNIT_HR.toFixed(2)}/unit/hr + +
+ {fmtUSD(devCost)} +
+ + + {#if billableStorageGB > 0} +
+
+ Storage (GB) + + {billableStorageGB} billable GB × ${STORAGE_RATE_PER_GB}/GB/mo + +
+ {fmtUSD(storageCost)} +
+ {/if} + +
+ + +
+ Monthly cost + {fmtUSD(monthlyCost)} +
+ + + {#if !isPro && availableCredit > 0} +
+
+ Available credit + Applied to your first bill +
+ -{fmtUSD(availableCredit)} +
+ +
+ Estimated first bill + {fmtUSD(firstBill)} +
+ + + Then {fmtUSD(monthlyCost)}/mo at this configuration + + {/if} + + + {#if !isPro} + + {/if} + + +
+
+ + + diff --git a/web-admin/src/routes/[organization]/-/settings/billing/estimate/+page.ts b/web-admin/src/routes/[organization]/-/settings/billing/estimate/+page.ts new file mode 100644 index 00000000000..505956c4186 --- /dev/null +++ b/web-admin/src/routes/[organization]/-/settings/billing/estimate/+page.ts @@ -0,0 +1,5 @@ +import type { PageLoad } from "./$types"; + +export const load: PageLoad = async ({ params: { organization } }) => { + return { organization }; +}; diff --git a/web-admin/src/routes/[organization]/-/settings/billing/usage/+page.svelte b/web-admin/src/routes/[organization]/-/settings/billing/usage/+page.svelte new file mode 100644 index 00000000000..c51e5dd5347 --- /dev/null +++ b/web-admin/src/routes/[organization]/-/settings/billing/usage/+page.svelte @@ -0,0 +1,23 @@ +
+

Usage

+

+ View slot usage, storage consumption, and billing details for your + organization. +

+ +
+

+ Detailed usage metrics are coming soon. +

+
+
+ + diff --git a/web-admin/src/routes/[organization]/-/settings/usage/+page.svelte b/web-admin/src/routes/[organization]/-/settings/usage/+page.svelte deleted file mode 100644 index 2881c459d6f..00000000000 --- a/web-admin/src/routes/[organization]/-/settings/usage/+page.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - -{#if iframeLoading} - -{/if} - - - - diff --git a/web-admin/src/routes/[organization]/-/settings/usage/+page.ts b/web-admin/src/routes/[organization]/-/settings/usage/+page.ts deleted file mode 100644 index 92aedd23461..00000000000 --- a/web-admin/src/routes/[organization]/-/settings/usage/+page.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { isEnterprisePlan } from "@rilldata/web-admin/features/billing/plans/utils"; -import { error, redirect } from "@sveltejs/kit"; -import type { PageLoad } from "./$types"; - -export const load: PageLoad = async ({ params: { organization }, parent }) => { - const { subscription, billingPortalUrl } = await parent(); - - if (!billingPortalUrl) { - throw redirect(307, `/${organization}/-/settings`); - } - - // Orgs on an Enterprise Plan should not see this page - if (subscription?.plan && isEnterprisePlan(subscription.plan.name)) { - throw error(404, "Page not found"); - } -};