From 2f7bee814de1e266740ecde97adf4e2bd4006fe2 Mon Sep 17 00:00:00 2001 From: Henri Courdent <122811744+hcourdent@users.noreply.github.com> Date: Wed, 11 Feb 2026 17:30:41 +0100 Subject: [PATCH] Team plans page clarifications & readability (#7910) * Team plans page clarifications & readability * Using SettingsPageHeader --- .../components/settings/PremiumInfo.svelte | 408 ++++++++++++------ 1 file changed, 275 insertions(+), 133 deletions(-) diff --git a/frontend/src/lib/components/settings/PremiumInfo.svelte b/frontend/src/lib/components/settings/PremiumInfo.svelte index 8683ca2356..27ead12646 100644 --- a/frontend/src/lib/components/settings/PremiumInfo.svelte +++ b/frontend/src/lib/components/settings/PremiumInfo.svelte @@ -7,10 +7,10 @@ import { workspaceStore } from '$lib/stores' import { Button } from '../common' import Tooltip from '../Tooltip.svelte' + import SettingsPageHeader from '$lib/components/settings/SettingsPageHeader.svelte' import { ExternalLink, Pen, X } from 'lucide-svelte' import Section from '../Section.svelte' import Range from '../Range.svelte' - import Label from '../Label.svelte' import Modal from '../common/modal/Modal.svelte' import { slide } from 'svelte/transition' @@ -71,12 +71,14 @@ const info = await WorkspaceService.getPremiumInfo({ workspace: $workspaceStore! }) const developerNb = users?.filter((x) => !x.operator)?.length ?? 0 const operatorNb = users?.filter((x) => x.operator)?.length ?? 0 + const usage = info.usage ?? 0 + const seatsFromUsers = Math.ceil(developerNb + operatorNb / 2) - const seatsFromExtraComps = Math.max(Math.ceil((info.usage ?? 0) / 10000) - seatsFromUsers, 0) + const seatsFromExtraComps = Math.max(Math.ceil(usage / 10000) - seatsFromUsers, 0) const usedSeats = seatsFromUsers + seatsFromExtraComps premiumInfo = { ...info, - usage: info.usage ?? 0, + usage, owner: info.owner, developerNb, operatorNb, @@ -112,9 +114,10 @@ } } - let estimatedDevs = 1 + let estimatedDevsRaw = 1 let estimatedOps = 0 + $: estimatedDevs = Math.max(1, estimatedDevsRaw) $: estimatedSeats = estimatedDevs + Math.ceil(estimatedOps / 2) let estimatedExecs = 1 @@ -125,6 +128,8 @@ } } $: estimatedSeats && updateExecs() + + const formatNumber = (value: number) => value.toLocaleString('en-US') @@ -150,20 +155,19 @@
-
- {#if premiumInfo?.premium && plan} - Plan: {capitalize(plan)} plan{plan === 'team' ? ' (usage-based)' : ''} - {:else} - Plan: Free plan - {/if} -
+ {#if premiumInfo?.status === 'past_due'}

{#if premiumInfo.max_tolerated_executions === undefined || premiumInfo.usage > premiumInfo.max_tolerated_executions} Your last invoice is unpaid, you cannot run any more jobs. Please update your payment - method in the customer portal to continue running jobs. + method in the Customer Portal to continue running jobs. {:else} - Your last invoice is unpaid. Please update your payment method in the customer portal to + Your last invoice is unpaid. Please update your payment method in the Customer Portal to prevent the interruption of your job executions. {/if}

@@ -238,78 +242,125 @@ - Developers - - Actual pricing is calculated on the MAXIMUM number of users in a given billing - period, see the customer portal for more info. - +
+
Developers
+

+ Calculated on the MAXIMUM number of users in a given billing + period, see the Customer Portal for more info. +

+
-
- {premiumInfo.developerNb} +
+ {formatNumber(premiumInfo.developerNb)}
- Operators - - Actual pricing is calculated on the MAXIMUM number of operators in a given - billing period, see the customer portal for more info. - +
+
Operators
+

+ Calculated on the MAXIMUM number of operators in a given + billing period, see the Customer Portal for more info. +

+
-
- {premiumInfo.operatorNb} +
+ {formatNumber(premiumInfo.operatorNb)} +
+ + + + +
+
Seats from users
+

+ 1 developer = 1 seat, 2 operators = 1 seat. +

+

+ u = ceil({formatNumber(premiumInfo.developerNb)} + {formatNumber(premiumInfo.operatorNb)}/2) + = {formatNumber(premiumInfo.seatsFromUsers)} +

+
+
+ +
+ {formatNumber(premiumInfo.seatsFromUsers)}
-
Number of seats for users
+ +
+
Executions this month
+

+ One execution equals one job + up to 1 second on a worker with 2GB of memory, with each additional + second counting as an extra execution. +

+
+
-
- u = ceil({premiumInfo.developerNb} + {premiumInfo.operatorNb}/2) = {premiumInfo.seatsFromUsers} +
+ {formatNumber(premiumInfo.usage)}
- Computations executed this month + +
+
Included executions from user seats
+

+ Each seat includes 10,000 executions per month. +

+
+
-
- {premiumInfo.usage} +
+ - {formatNumber(premiumInfo.seatsFromUsers * 10000)}
- - Included computations with users (10k per user seat) + + +
+
Extra seats from computations
+

+ Additional seats created by executions beyond the included quota. +

+

+ c = ceil(max(0, {formatNumber(premiumInfo.usage)} - + {formatNumber(premiumInfo.seatsFromUsers * 10000)}) / 10,000) = + {formatNumber(premiumInfo.seatsFromExtraComps)} +

+
+
-
- - {premiumInfo.seatsFromUsers * 10000} +
+ {formatNumber(premiumInfo.seatsFromExtraComps)}
- -
Number of seats for extra computations
- -
- c = ceil({Math.max(0, premiumInfo.usage - premiumInfo.seatsFromUsers * 10000)} / - 10 000) = {premiumInfo.seatsFromExtraComps} + + +
+
+ Used seats (billed) +
+

+ Highest between seats from 'Developers + Operators' and 'Seats from executions'. + This is the number of seats used for billing this month. +

+

+ u + c = {formatNumber(premiumInfo.usedSeats)} +

- - -
Used seats Highest between seats from developers + operators and seats from computations -
-
- u + c = {premiumInfo.usedSeats} +
+ {formatNumber(premiumInfo.usedSeats)}
@@ -328,68 +379,145 @@
-
- - - -
-
-
Seats for users = {estimatedDevs} devs + {estimatedOps} ops / 2
-
{pluralize(estimatedSeats, 'seat')}
-
-
-
Extra computations = {estimatedExecs * 10}k execs - {estimatedSeats * 10}k included with - users
-
{(estimatedExecs - estimatedSeats) * 10}k extra execs = {pluralize( - estimatedExecs - estimatedSeats, - 'seat' - )}
-
-
-
Total
-
-
- {pluralize(estimatedSeats + (estimatedExecs - estimatedSeats), 'seat')} = {(estimatedSeats + - (estimatedExecs - estimatedSeats)) * - 10}$ / month +
+
+
Estimate your monthly cost
+

+ This is a rough estimate based on your expected team size and workload. Actual billing is based + on the maximum number of users and executions in a given month. +

+
+ +
+ +
+
+
+
Developers
+
+ {estimatedDevs} dev{estimatedDevs === 1 ? '' : 's'} +
+ +
+ +
+
+
Operators
+
+ {estimatedOps} operator{estimatedOps === 1 ? '' : 's'} +
+
+ +

+ 2 operators = 1 seat +

+
+ +
+
+
+
Monthly executions
+ + One execution equals one job up to 1 second on a virtual CPU with 2 GB of memory, with + each additional second counting as an extra execution. + +
+
+ {estimatedExecs * 10}k executions / month +
+
+ `${v * 10}k`} + hideInput + /> +

+ Each seat includes 10k executions per month. +

+
+
+ + + {#if estimatedSeats} +
+
+
Seats from users
+
+ {pluralize(estimatedSeats, 'seat')} +
+
+ +
+
+ Based on {estimatedDevs} dev{estimatedDevs === 1 ? '' : 's'} and + {` ${estimatedOps} operator${estimatedOps === 1 ? '' : 's'}`} +
+
+ +
+ +
+
Included executions
+
{estimatedSeats * 10}k
+
+ +
+
Extra executions
+
+ {(estimatedExecs - estimatedSeats) * 10}k +
+
+ +
+
Charged as additional seats
+
+ ≈ {pluralize(estimatedExecs - estimatedSeats, 'seat')} +
+
+ +
+ +
+
Total estimated seats
+
+
+ {pluralize(estimatedSeats + (estimatedExecs - estimatedSeats), 'seat')} +
+
+ {(estimatedSeats + (estimatedExecs - estimatedSeats)) * 10}$ / month +
+
+
+ {#if premiumInfo?.premium && plan === 'team'} {/if} + +
+ {#if (estimatedSeats + (estimatedExecs - estimatedSeats)) * 10 > 700} + + Higher usage? Explore Cloud Enterprise + + {/if} +
-
- {#if (estimatedSeats + (estimatedExecs - estimatedSeats)) * 10 > 700} - - You should consider subscribing to Cloud Enterprise - {/if}
@@ -410,15 +538,25 @@ {planTitle}
    - {#each planDesc as item} -
  • {@html item}
  • + {#each planDesc as item, i} + {#if planTitle === 'Team' && i === 1} +
  • + Every seat includes 10 000 executions + + One execution equals one job up to 1 second on a virtual CPU with 2 GB of memory, with + each additional second counting as an extra execution. + +
  • + {:else} +
  • {@html item}
  • + {/if} {/each}
- {#if planTitle == 'Team'} - {#if plan != 'team'} -
+
+ {#if planTitle == 'Team'} + {#if plan != 'team'} {#if plan != 'enterprise'} -
- {:else} -
Workspace is on enterprise plan
+ {:else} +
+ Workspace is on enterprise plan +
+ {/if} + {:else if planTitle === 'Free'} + {#if plan} +
+ Cancel your plan in the Customer Portal to downgrade to the free plan +
+ {:else} +
+ Workspace is on the free plan +
+ {/if} {/if} - {:else if planTitle === 'Free'} - {#if plan} -
- Cancel your plan in the customer portal to downgrade to the free plan -
- {:else} -
Workspace is on the free plan
- {/if} - {/if} +
{/each}