diff --git a/src/lib/components/billing/estimatedTotalBox.svelte b/src/lib/components/billing/estimatedTotalBox.svelte index 3ff31041c..303e8de23 100644 --- a/src/lib/components/billing/estimatedTotalBox.svelte +++ b/src/lib/components/billing/estimatedTotalBox.svelte @@ -11,6 +11,7 @@ export let couponData: Partial; export let billingBudget: number; export let fixedCoupon = false; // If true, the coupon cannot be removed + export let isDowngrade = false; const today = new Date(); const billingPayDate = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000); @@ -41,8 +42,8 @@

{formatCurrency(currentPlan.price)}

-

Additional seats ({collaborators?.length})

-

+

Additional seats ({collaborators?.length})

+

{formatCurrency(extraSeatsCost)}

diff --git a/src/lib/components/billing/index.ts b/src/lib/components/billing/index.ts index b8aac8ada..266d217d8 100644 --- a/src/lib/components/billing/index.ts +++ b/src/lib/components/billing/index.ts @@ -6,3 +6,4 @@ export { default as EstimatedTotalBox } from './estimatedTotalBox.svelte'; export { default as PlanComparisonBox } from './planComparisonBox.svelte'; export { default as EmptyCardCloud } from './emptyCardCloud.svelte'; export { default as CreditsApplied } from './creditsApplied.svelte'; +export { default as PlanSelection } from './planSelection.svelte'; diff --git a/src/lib/components/billing/planComparisonBox.svelte b/src/lib/components/billing/planComparisonBox.svelte index ce1abe329..e297d8e75 100644 --- a/src/lib/components/billing/planComparisonBox.svelte +++ b/src/lib/components/billing/planComparisonBox.svelte @@ -1,30 +1,38 @@ - - - (selectedTab = 'tier-0')}> - Free - - (selectedTab = 'tier-1')}> - Pro - - + +
+ + (selectedTab = BillingPlan.FREE)}> + {tierFree.name} + + (selectedTab = BillingPlan.PRO)}> + {tierPro.name} + + (selectedTab = BillingPlan.SCALE)}> + {tierScale.name} + + +
- {#if selectedTab === 'tier-0'} + {#if selectedTab === BillingPlan.FREE}

{plan.name} plan

{#if downgrade}
    @@ -86,16 +94,50 @@
{/if} - {:else if selectedTab === 'tier-1'} + {:else if selectedTab === BillingPlan.PRO}

{plan.name} plan

-
    -
  • Everything in the Free plan, plus:
  • +

    Everything in the Free plan, plus:

    +
    • Unlimited databases, buckets, functions
    • {plan.bandwidth}GB bandwidth
    • {plan.storage}GB storage
    • {formatNum(plan.executions)} executions
    • Email support
    + {:else if selectedTab === BillingPlan.SCALE} +

    {plan.name} plan

    +

    Everything in the Pro plan, plus:

    +
      +
    • Unlimited seats
    • +
    • Organization roles Coming soon
    • +
    • SOC-2, HIPAA compliance
    • +
    • SSO Coming soon
    • +
    • Priority support
    • +
    {/if}
+ + diff --git a/src/lib/components/billing/planExcess.svelte b/src/lib/components/billing/planExcess.svelte index 10d0842d7..352f0b29b 100644 --- a/src/lib/components/billing/planExcess.svelte +++ b/src/lib/components/billing/planExcess.svelte @@ -23,6 +23,7 @@ import { tooltip } from '$lib/actions/tooltip'; export let tier: Tier; + export let members: number; const plan = $plansInfo?.get(tier); let excess: { @@ -41,7 +42,7 @@ $organization.billingCurrentInvoiceDate, new Date().toISOString() ); - excess = calculateExcess(usage, plan, $organization); + excess = calculateExcess(usage, plan, members); showExcess = Object.values(excess).some((value) => value > 0); }); @@ -98,7 +99,8 @@

- {humanFileSize(excess?.storage)} + {humanFileSize(excess?.storage).value} + {humanFileSize(excess?.storage).unit}

diff --git a/src/lib/components/billing/planSelection.svelte b/src/lib/components/billing/planSelection.svelte new file mode 100644 index 000000000..c2af34653 --- /dev/null +++ b/src/lib/components/billing/planSelection.svelte @@ -0,0 +1,92 @@ + + +{#if billingPlan} + +{/if} diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 5e54db955..ce7adccf7 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -150,7 +150,7 @@ export const tierPro: TierData = { }; export const tierScale: TierData = { name: 'Scale', - description: 'For scaling teams that need dedicated support.' + description: 'For scaling teams and agencies that need dedicated support.' }; export const showUsageRatesModal = writable(false); @@ -422,14 +422,14 @@ export const upgradeURL = derived( export const hideBillingHeaderRoutes = ['/console/create-organization', '/console/account']; -export function calculateExcess(usage: OrganizationUsage, plan: Plan, org: Organization) { +export function calculateExcess(usage: OrganizationUsage, plan: Plan, members: number) { const totBandwidth = usage?.bandwidth?.length > 0 ? last(usage.bandwidth).value : 0; return { bandwidth: calculateResourceSurplus(totBandwidth, plan.bandwidth), storage: calculateResourceSurplus(usage?.storageTotal, plan.storage, 'GB'), users: calculateResourceSurplus(usage?.usersTotal, plan.users), executions: calculateResourceSurplus(usage?.executionsTotal, plan.executions, 'GB'), - members: calculateResourceSurplus(org.total, plan.members) + members: calculateResourceSurplus(members, plan.members) }; } diff --git a/src/lib/stores/campaigns.ts b/src/lib/stores/campaigns.ts index 3f7866b9e..315a4230f 100644 --- a/src/lib/stores/campaigns.ts +++ b/src/lib/stores/campaigns.ts @@ -1,11 +1,14 @@ //campaign welcome and startup +import { BillingPlan } from '$lib/constants'; + export type CampaignData = { title: string; description: string; template: 'card' | 'review'; data?: Record; onlyNewOrgs?: boolean; + plan?: BillingPlan; footer?: boolean; }; @@ -22,7 +25,7 @@ campaigns template: 'card', title: 'Welcome to the Startups program!', description: - "We're excited to have you on board. Add the coupon code to your Appwrite Pro account to join." + "We're excited to have you on board. Add the coupon code to your Appwrite Scale account to join." }) .set('RenderATL2024', { template: 'card', @@ -52,10 +55,11 @@ campaigns template: 'review', title: 'Welcome to the Startups program', description: - "We're excited to have you on board. Add your credit code to your Appwrite Pro account to join.", + "We're excited to have you on board. Add your credit code to your Appwrite Scale account to join.", onlyNewOrgs: true, + plan: BillingPlan.SCALE, data: { - cta: 'Get everything out of Cloud with Pro', + cta: 'Get everything out of Cloud with Scale', claimed: 'Your credits will be valid for 12 months.', unclaimed: 'Apply your code to join the Startups program.', reviews: [ diff --git a/src/lib/stores/organization.ts b/src/lib/stores/organization.ts index 2beb13b03..f0f363d56 100644 --- a/src/lib/stores/organization.ts +++ b/src/lib/stores/organization.ts @@ -19,7 +19,7 @@ export type Organization = Models.Team> & { billingAddressId?: string; amount: number; billingTaxId?: string; - billingPlanDowngrade?: string; + billingPlanDowngrade?: Tier; }; export type OrganizationList = { diff --git a/src/routes/console/apply-credit/+page.svelte b/src/routes/console/apply-credit/+page.svelte index c9f0db557..231c8820f 100644 --- a/src/routes/console/apply-credit/+page.svelte +++ b/src/routes/console/apply-credit/+page.svelte @@ -70,6 +70,7 @@ let coupon: string; let couponData = data?.couponData; let campaign = campaigns.get(data?.couponData?.campaign ?? data?.campaign); + let billingPlan = BillingPlan.PRO; onMount(async () => { await loadPaymentMethods(); @@ -80,6 +81,9 @@ selectedOrgId = $page.url.searchParams.get('org'); canSelectOrg = false; } + if (campaign?.plan) { + billingPlan = campaign.plan; + } }); async function loadPaymentMethods() { @@ -101,15 +105,15 @@ org = await sdk.forConsole.billing.createOrganization( newOrgId, name, - BillingPlan.PRO, + billingPlan, paymentMethodId ); } // Upgrade existing org - else if (selectedOrg?.billingPlan !== BillingPlan.PRO) { + else if (selectedOrg?.billingPlan === BillingPlan.FREE) { org = await sdk.forConsole.billing.updatePlan( selectedOrg.$id, - BillingPlan.PRO, + billingPlan, paymentMethodId, null ); @@ -270,7 +274,7 @@ {/if} - {#if selectedOrg?.billingPlan === BillingPlan.PRO} + {#if selectedOrg?.billingPlan !== BillingPlan.FREE}
(org as Organization)?.billingPlan === BillingPlan.FREE ); const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$/i; @@ -175,9 +174,7 @@ } } - $: freePlan = $plansInfo.get(BillingPlan.FREE); - $: proPlan = $plansInfo.get(BillingPlan.PRO); - $: if (billingPlan === BillingPlan.PRO) { + $: if (billingPlan !== BillingPlan.FREE) { loadPaymentMethods(); } @@ -205,53 +202,9 @@ For more details on our plans, visit our .

-
    -
  • - - -
    -

    - {tierFree.name} -

    -

    {tierFree.description}

    -

    - {formatCurrency(freePlan?.price ?? 0)} -

    -
    -
    -
    -
  • - -
  • - - -
    -

    - {tierPro.name} -

    -

    - {tierPro.description} -

    -

    - {formatCurrency(proPlan?.price ?? 0)} per member/month + usage -

    -
    -
    -
    -
  • -
- {#if billingPlan === BillingPlan.PRO} - + + {#if billingPlan !== BillingPlan.FREE} + - Your organization will change to a {tierToPlan(BillingPlan.FREE).name} plan once your current - billing cycle ends and your invoice is paid on {toLocaleDate( + Your organization will change to a {tierToPlan($organization?.billingPlanDowngrade) + .name} plan once your current billing cycle ends and your invoice is paid on {toLocaleDate( $organization.billingNextInvoiceDate )}. diff --git a/src/routes/console/organization-[organization]/billing/planSummary.svelte b/src/routes/console/organization-[organization]/billing/planSummary.svelte index 81edb295e..04d1aa258 100644 --- a/src/routes/console/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/console/organization-[organization]/billing/planSummary.svelte @@ -58,7 +58,7 @@ {isTrial ? formatCurrency(0) : formatCurrency(currentPlan?.price)} - {#if $organization?.billingPlan !== BillingPlan.FREE && extraUsage} + {#if $organization?.billingPlan !== BillingPlan.FREE && extraUsage > 0} Add-ons $organization.billingPlan; $: isDowngrade = billingPlan < $organization.billingPlan; - $: freePlan = $plansInfo.get(BillingPlan.FREE); - $: proPlan = $plansInfo.get(BillingPlan.PRO); - $: if (billingPlan === BillingPlan.PRO) { + $: if (billingPlan !== BillingPlan.FREE) { loadPaymentMethods(); } $: isButtonDisabled = $organization.billingPlan === billingPlan; @@ -261,72 +264,37 @@ For more details on our plans, visit our .

- {#if anyOrgFree && billingPlan === BillingPlan.PRO} - - You are limited to one {tierToPlan(BillingPlan.FREE).name} organization per account. - Consider upgrading or deleting . - - {/if} -
    -
  • - - -
    -

    - {tierFree.name} - {#if $organization.billingPlan === BillingPlan.FREE} - Current plan - {/if} -

    -

    {tierFree.description}

    -

    - {formatCurrency(freePlan?.price ?? 0)} -

    -
    -
    -
    -
  • + -
  • - - -
    -

    - {tierPro.name} - {#if $organization.billingPlan === BillingPlan.PRO} - Current plan - {/if} -

    -

    - {tierPro.description} -

    -

    - {formatCurrency(proPlan?.price ?? 0)} per member/month + usage -

    -
    -
    -
    -
  • -
{#if isDowngrade} - + {#if billingPlan === BillingPlan.FREE} + + {:else if billingPlan === BillingPlan.PRO && $organization.billingPlan === BillingPlan.SCALE} + {@const extraMembers = collaborators?.length ?? 0} + + + Your monthly payments will be adjusted for the Pro plan + + After switching plans, + you will be charged {formatCurrency( + extraMembers * + ($plansInfo?.get(billingPlan)?.addons?.member?.price ?? 0) + )} monthly for {extraMembers} team members. This will be reflected in + your next invoice. + + {/if} {/if} - {#if billingPlan === BillingPlan.PRO && $organization.billingPlan !== BillingPlan.PRO} + + {#if billingPlan !== BillingPlan.FREE && $organization.billingPlan === BillingPlan.FREE} {/if} {/if} - {#if !isUpgrade && billingPlan === BillingPlan.FREE && $organization.billingPlan !== BillingPlan.FREE} + {#if isDowngrade} - {#if billingPlan !== BillingPlan.FREE && $organization.billingPlan !== BillingPlan.PRO} + {#if billingPlan !== BillingPlan.FREE && $organization.billingPlan !== billingPlan} + bind:billingBudget + {isDowngrade} /> {:else} {/if} diff --git a/src/routes/console/organization-[organization]/change-plan/+page.ts b/src/routes/console/organization-[organization]/change-plan/+page.ts index cde580678..b10332405 100644 --- a/src/routes/console/organization-[organization]/change-plan/+page.ts +++ b/src/routes/console/organization-[organization]/change-plan/+page.ts @@ -2,8 +2,8 @@ import { Dependencies } from '$lib/constants'; export const load = async ({ depends, parent }) => { const { members } = await parent(); - depends(Dependencies.ORGANIZATION); + depends(Dependencies.ORGANIZATION); return { members }; diff --git a/src/routes/console/organization-[organization]/createMember.svelte b/src/routes/console/organization-[organization]/createMember.svelte index a0c4ff0dc..c3195a1f2 100644 --- a/src/routes/console/organization-[organization]/createMember.svelte +++ b/src/routes/console/organization-[organization]/createMember.svelte @@ -59,14 +59,12 @@ {#if isCloud} - - {#if $organization?.billingPlan === BillingPlan.SCALE} - You can add unlimited organization members on the {plan.name} plan at no cost. - {:else if $organization?.billingPlan === BillingPlan.PRO} + {#if $organization?.billingPlan === BillingPlan.PRO} + You can add unlimited organization members on the {plan.name} plan for {formatCurrency(plan.addons.member.price)} each per billing period. - {/if} - + + {/if} {/if}