mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge pull request #1565 from ItzNotABug/poc-invoice-cycle-ref
Updated pricing estimates
This commit is contained in:
@@ -5,7 +5,11 @@
|
||||
import type { Coupon } from '$lib/sdk/billing';
|
||||
import { plansInfo, type Tier } from '$lib/stores/billing';
|
||||
import { CreditsApplied } from '.';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
|
||||
// undefined as we only need this on `change-plan`
|
||||
export let currentTier: Tier | undefined = undefined;
|
||||
export let billingPlan: Tier;
|
||||
export let collaborators: string[];
|
||||
export let couponData: Partial<Coupon>;
|
||||
@@ -14,21 +18,29 @@
|
||||
export let isDowngrade = false;
|
||||
|
||||
const today = new Date();
|
||||
const isScaleDowngrade = isDowngrade && billingPlan === BillingPlan.PRO;
|
||||
const isScaleUpgrade = !isDowngrade && billingPlan === BillingPlan.SCALE;
|
||||
const billingPayDate = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
let budgetEnabled = false;
|
||||
|
||||
$: currentPlan = $plansInfo.get(billingPlan);
|
||||
$: extraSeatsCost = 0; // 0 untile trial period later replace (collaborators?.length ?? 0) * (currentPlan?.addons?.member?.price ?? 0);
|
||||
$: grossCost = currentPlan.price + extraSeatsCost;
|
||||
$: selectedPlan = $plansInfo.get(billingPlan);
|
||||
$: currentOrgPlan = $plansInfo.get(currentTier);
|
||||
$: unUsedBalances = isScaleUpgrade
|
||||
? currentOrgPlan.price +
|
||||
(collaborators?.length ?? 0) * (currentOrgPlan?.addons?.member?.price ?? 0)
|
||||
: isScaleDowngrade
|
||||
? currentOrgPlan.price
|
||||
: 0;
|
||||
|
||||
$: extraSeatsCost = (collaborators?.length ?? 0) * (selectedPlan?.addons?.member?.price ?? 0);
|
||||
$: grossCost = isScaleUpgrade
|
||||
? selectedPlan.price + extraSeatsCost - unUsedBalances
|
||||
: selectedPlan.price + extraSeatsCost;
|
||||
$: estimatedTotal =
|
||||
couponData?.status === 'active'
|
||||
? grossCost - couponData.credits >= 0
|
||||
? grossCost - couponData.credits
|
||||
: 0
|
||||
: grossCost;
|
||||
couponData?.status === 'active' ? Math.max(0, grossCost - couponData.credits) : grossCost;
|
||||
$: trialEndDate = new Date(
|
||||
billingPayDate.getTime() + currentPlan.trialDays * 24 * 60 * 60 * 1000
|
||||
billingPayDate.getTime() + selectedPlan.trialDays * 24 * 60 * 60 * 1000
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -37,41 +49,78 @@
|
||||
style:--p-card-padding="1.5rem"
|
||||
style:--p-card-border-radius="var(--border-radius-small)">
|
||||
<slot />
|
||||
<span class="u-flex u-main-space-between">
|
||||
<p class="text">{currentPlan.name} plan</p>
|
||||
<p class="text">{formatCurrency(currentPlan.price)}</p>
|
||||
</span>
|
||||
<span class="u-flex u-main-space-between">
|
||||
<div class="u-flex u-main-space-between">
|
||||
<p class="text">{selectedPlan.name} plan</p>
|
||||
<p class="text">{formatCurrency(selectedPlan.price)}</p>
|
||||
</div>
|
||||
|
||||
<div class="u-flex u-main-space-between">
|
||||
<p class="text" class:u-bold={isDowngrade}>Additional seats ({collaborators?.length})</p>
|
||||
<p class="text" class:u-bold={isDowngrade}>
|
||||
{formatCurrency(extraSeatsCost)}
|
||||
{formatCurrency(
|
||||
isScaleDowngrade
|
||||
? (collaborators?.length ?? 0) * (selectedPlan?.addons?.member?.price ?? 0)
|
||||
: extraSeatsCost
|
||||
)}
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{#if isScaleUpgrade}
|
||||
{@const currentPlanName = currentOrgPlan.name}
|
||||
<div class="u-flex u-main-space-between">
|
||||
<div class="text">
|
||||
<span>Unused {currentPlanName} plan balance</span>
|
||||
<span
|
||||
use:tooltip={{
|
||||
placement: 'bottom',
|
||||
content: `This discount reflects the unused portion of your ${currentPlanName} plan and add-ons. Future credits for extra seats and features will apply automatically.`
|
||||
}}
|
||||
class="icon-info">
|
||||
</span>
|
||||
</div>
|
||||
<p class="text">-{formatCurrency(unUsedBalances)}</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if couponData?.status === 'active'}
|
||||
<CreditsApplied bind:couponData {fixedCoupon} />
|
||||
{/if}
|
||||
<div class="u-sep-block-start" />
|
||||
<span class="u-flex u-main-space-between">
|
||||
<div class="u-flex u-main-space-between">
|
||||
<p class="text">
|
||||
Upcoming charge<br /><span class="u-color-text-gray"
|
||||
>Due on {!currentPlan.trialDays
|
||||
>Due on {!selectedPlan.trialDays
|
||||
? toLocaleDate(billingPayDate.toString())
|
||||
: toLocaleDate(trialEndDate.toString())}</span>
|
||||
</p>
|
||||
<p class="text">
|
||||
{formatCurrency(estimatedTotal)}
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="text u-margin-block-start-16">
|
||||
You'll pay <span class="u-bold">{formatCurrency(estimatedTotal)}</span> now, with our first
|
||||
billing cycle starting on
|
||||
<span class="u-bold"
|
||||
>{!currentPlan.trialDays
|
||||
? toLocaleDate(billingPayDate.toString())
|
||||
: toLocaleDate(trialEndDate.toString())}</span
|
||||
>. Once your credits run out, you'll be charged
|
||||
<span class="u-bold">{formatCurrency(currentPlan.price)}</span> plus usage fees every 30 days.
|
||||
{#if isScaleDowngrade}
|
||||
You'll continue using the {currentOrgPlan.name} plan until your current cycle ends on
|
||||
<span class="u-bold"
|
||||
>{!selectedPlan.trialDays
|
||||
? toLocaleDate(billingPayDate.toString())
|
||||
: toLocaleDate(trialEndDate.toString())}</span
|
||||
>. Starting the next cycle, your plan will switch to {selectedPlan.name}, and you'll be
|
||||
charged
|
||||
<span class="u-bold">{formatCurrency(grossCost)}</span>
|
||||
every 30 days.
|
||||
{:else}
|
||||
You'll pay
|
||||
<span class="u-bold">{formatCurrency(estimatedTotal)}</span>
|
||||
now, with our first billing cycle starting on
|
||||
<span class="u-bold"
|
||||
>{!selectedPlan.trialDays
|
||||
? toLocaleDate(billingPayDate.toString())
|
||||
: toLocaleDate(trialEndDate.toString())}</span
|
||||
>. Once your credits run out, you'll be charged
|
||||
<span class="u-bold">{formatCurrency(selectedPlan.price)}</span> plus usage fees every 30
|
||||
days.
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
<FormList class="u-margin-block-start-24">
|
||||
|
||||
@@ -77,11 +77,7 @@
|
||||
</LabelCard>
|
||||
</li>
|
||||
<li>
|
||||
<LabelCard
|
||||
name="plan"
|
||||
bind:group={billingPlan}
|
||||
value={BillingPlan.SCALE}
|
||||
padding={1.5}>
|
||||
<LabelCard name="plan" bind:group={billingPlan} value={BillingPlan.SCALE} padding={1.5}>
|
||||
<svelte:fragment slot="custom">
|
||||
<div class="u-flex u-flex-vertical u-gap-4 u-width-full-line">
|
||||
<h4 class="body-text-2 u-bold">
|
||||
|
||||
@@ -342,14 +342,11 @@ export class Billing {
|
||||
);
|
||||
}
|
||||
|
||||
async validateOrganization(
|
||||
organizationId: string,
|
||||
invites: string[],
|
||||
): Promise<Organization> {
|
||||
async validateOrganization(organizationId: string, invites: string[]): Promise<Organization> {
|
||||
const path = `/organizations/${organizationId}/validate`;
|
||||
const params = {
|
||||
organizationId,
|
||||
invites,
|
||||
invites
|
||||
};
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return await this.client.call(
|
||||
|
||||
@@ -130,8 +130,8 @@ export async function confirmPayment(
|
||||
clientSecret: clientSecret,
|
||||
confirmParams: {
|
||||
return_url: url,
|
||||
payment_method: paymentMethod.providerMethodId,
|
||||
},
|
||||
payment_method: paymentMethod.providerMethodId
|
||||
}
|
||||
});
|
||||
if (error) {
|
||||
if (returnError) {
|
||||
|
||||
@@ -10,13 +10,11 @@ export const load: PageLoad = async ({ url, route }) => {
|
||||
const limit = getLimit(url, route, CARD_LIMIT);
|
||||
const offset = pageToOffset(page, limit);
|
||||
|
||||
const queries = [
|
||||
Query.offset(offset),
|
||||
Query.limit(limit),
|
||||
Query.orderDesc('')
|
||||
];
|
||||
const queries = [Query.offset(offset), Query.limit(limit), Query.orderDesc('')];
|
||||
|
||||
const organizations = isCloud ? await sdk.forConsole.billing.listOrganization(queries) : sdk.forConsole.teams.list(queries);
|
||||
const organizations = isCloud
|
||||
? await sdk.forConsole.billing.listOrganization(queries)
|
||||
: sdk.forConsole.teams.list(queries);
|
||||
|
||||
return {
|
||||
offset,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
InputTextarea,
|
||||
Label
|
||||
} from '$lib/elements/forms';
|
||||
import { formatCurrency } from '$lib/helpers/numbers.js';
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import {
|
||||
WizardSecondaryContainer,
|
||||
WizardSecondaryContent,
|
||||
@@ -40,7 +40,7 @@
|
||||
type Organization
|
||||
} from '$lib/stores/organization';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { confirmPayment } from '$lib/stores/stripe.js';
|
||||
import { confirmPayment } from '$lib/stores/stripe';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { VARS } from '$lib/system';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -212,7 +212,7 @@
|
||||
async function upgrade() {
|
||||
try {
|
||||
//Add collaborators
|
||||
var newCollaborators = [];
|
||||
let newCollaborators = [];
|
||||
if (collaborators?.length) {
|
||||
newCollaborators = collaborators.filter(
|
||||
(collaborator) =>
|
||||
@@ -371,11 +371,12 @@
|
||||
<svelte:fragment slot="aside">
|
||||
{#if billingPlan !== BillingPlan.FREE && $organization.billingPlan !== billingPlan && $organization.billingPlan !== BillingPlan.CUSTOM}
|
||||
<EstimatedTotalBox
|
||||
{isDowngrade}
|
||||
{billingPlan}
|
||||
{collaborators}
|
||||
bind:couponData
|
||||
bind:billingBudget
|
||||
{isDowngrade} />
|
||||
currentTier={$organization.billingPlan} />
|
||||
{:else if $organization.billingPlan !== BillingPlan.CUSTOM}
|
||||
<PlanComparisonBox downgrade={isDowngrade} />
|
||||
{/if}
|
||||
|
||||
@@ -29,7 +29,9 @@ export const load: LayoutLoad = async ({ depends, url, route }) => {
|
||||
if (account) {
|
||||
return {
|
||||
account,
|
||||
organizations: isCloud ? await sdk.forConsole.billing.listOrganization() : await sdk.forConsole.teams.list()
|
||||
organizations: isCloud
|
||||
? await sdk.forConsole.billing.listOrganization()
|
||||
: await sdk.forConsole.teams.list()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user