Feat: plan summary using new estimation endpoint

This commit is contained in:
Damodar Lohani
2025-03-26 06:07:26 +00:00
parent 9ced8ce272
commit 48ff33fae1
4 changed files with 124 additions and 145 deletions
+27
View File
@@ -82,6 +82,25 @@ export type EstimationItem = {
value: number;
};
export type EstimationOrganization = {
amount: number;
grossAmount: number;
credits: number;
discount: number;
items: EstimationItem[];
discounts: EstimationItem[];
usage: {
name: string;
value: number /* service over the limit*/;
amount: number /* price of service over the limit*/;
rate: number;
desc: string;
}[];
trialDays: number;
trialEndDate: string | undefined;
error: string | undefined;
};
export type EstimationDeleteOrganization = {
amount: number;
grossAmount: number;
@@ -485,6 +504,14 @@ export class Billing {
});
}
async estimationGetOrganization(organizationId: string): Promise<EstimationOrganization> {
const path = `/organizations/${organizationId}/estimations`;
const uri = new URL(this.client.config.endpoint + path);
return await this.client.call('get', uri, {
'content-type': 'application/json'
});
}
async getOrganizationPlan(organizationId: string): Promise<Plan> {
const path = `/organizations/${organizationId}/plan`;
const uri = new URL(this.client.config.endpoint + path);
@@ -118,10 +118,8 @@
<Heading tag="h2" size="5">Billing</Heading>
</div>
<PlanSummary
creditList={data?.creditList}
currentPlan={data?.aggregationBillingPlan}
currentAggregation={data?.billingAggregation}
currentInvoice={data?.billingInvoice} />
estimation={data?.estimationOrganization}
currentPlan={data?.aggregationBillingPlan} />
<PaymentHistory />
<PaymentMethods />
<BillingAddress billingAddress={data?.billingAddress} />
@@ -39,32 +39,29 @@ export const load: PageLoad = async ({ parent, depends }) => {
// ignore error
}
let billingInvoice = null;
try {
billingInvoice = await sdk.forConsole.billing.getInvoice(
organization.$id,
(organization as Organization)?.billingInvoiceId
);
} catch (e) {
// ignore error
}
const [paymentMethods, addressList, billingAddress, creditList, aggregationBillingPlan] =
await Promise.all([
sdk.forConsole.billing.listPaymentMethods(),
sdk.forConsole.billing.listAddresses(),
billingAddressPromise,
sdk.forConsole.billing.listCredits(organization.$id),
sdk.forConsole.billing.getPlan(billingAggregation?.plan ?? organization.billingPlan)
]);
const [
estimationOrganization,
paymentMethods,
addressList,
billingAddress,
creditList,
aggregationBillingPlan
] = await Promise.all([
sdk.forConsole.billing.estimationGetOrganization(organization.$id),
sdk.forConsole.billing.listPaymentMethods(),
sdk.forConsole.billing.listAddresses(),
billingAddressPromise,
sdk.forConsole.billing.listCredits(organization.$id),
sdk.forConsole.billing.getPlan(billingAggregation?.plan ?? organization.billingPlan)
]);
return {
estimationOrganization,
paymentMethods,
addressList,
billingAddress,
aggregationBillingPlan,
creditList,
billingAggregation,
billingInvoice
billingAggregation
};
};
@@ -3,9 +3,9 @@
import { Card, CardGrid, Collapsible, CollapsibleItem, Heading } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { toLocaleDate } from '$lib/helpers/date';
import { plansInfo, upgradeURL } from '$lib/stores/billing';
import { upgradeURL } from '$lib/stores/billing';
import { organization } from '$lib/stores/organization';
import type { Aggregation, CreditList, Invoice, Plan } from '$lib/sdk/billing';
import type { EstimationOrganization, Plan } from '$lib/sdk/billing';
import { abbreviateNumber, formatCurrency, formatNumberWithCommas } from '$lib/helpers/numbers';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { BillingPlan } from '$lib/constants';
@@ -13,19 +13,14 @@
import { tooltip } from '$lib/actions/tooltip';
import CancelDowngradeModel from './cancelDowngradeModal.svelte';
export let estimation: EstimationOrganization;
export let currentPlan: Plan;
export let creditList: CreditList;
export let currentInvoice: Invoice | undefined = undefined;
export let currentAggregation: Aggregation | undefined = undefined;
let showCancel: boolean = false;
const availableCredit = creditList.available;
const today = new Date();
const isTrial =
new Date($organization?.billingStartDate).getTime() - today.getTime() > 0 &&
$plansInfo.get($organization.billingPlan)?.trialDays;
const extraUsage = currentInvoice ? currentInvoice.amount - currentPlan?.price : 0;
const extraUsage = estimation.usage.length
? estimation.usage.reduce((total, usage) => total + usage.amount, 0)
: 0;
</script>
{#if $organization}
@@ -43,26 +38,19 @@
<Card isTile style="--p-card-padding: 1.5rem;" class="card-only-on-desktop">
<Collapsible>
<div class="u-flex-vertical u-gap-8">
<div class="u-flex">
<span class="body-text-2">{currentPlan.name} plan</span>
<div class="body-text-2 u-margin-inline-start-auto">
{isTrial ||
$organization?.billingPlan === BillingPlan.GITHUB_EDUCATION
? formatCurrency(0)
: currentPlan
? formatCurrency(currentPlan?.price)
: ''}
{#each estimation.items as item}
<div class="u-flex">
<span class="body-text-2">{item.label}</span>
<div class="body-text-2 u-margin-inline-start-auto">
{formatCurrency(item.value)}
</div>
</div>
</div>
{#if currentPlan.budgeting && extraUsage > 0}
{/each}
{#if estimation.usage.length}
<CollapsibleItem gap={8}>
<svelte:fragment slot="beforetitle">
<span class="body-text-2"><b>Add-ons</b></span><span
class="inline-tag"
>{currentAggregation.additionalMembers > 0
? currentInvoice.usage.length + 1
: currentInvoice.usage.length}</span>
class="inline-tag">{estimation.usage.length}</span>
<div class="icon">
<span class="icon-cheveron-down" aria-hidden="true"></span>
</div>
@@ -70,106 +58,84 @@
<svelte:fragment slot="end">
<div class="body-text-2 u-margin-inline-start-auto">
<b>
{formatCurrency(extraUsage >= 0 ? extraUsage : 0)}
{formatCurrency(extraUsage)}
</b>
</div>
</svelte:fragment>
<ul>
{#if currentAggregation.additionalMembers}
{#each estimation.usage as excess}
<li class="u-flex-vertical u-gap-4 u-padding-block-8">
<div class="u-flex u-gap-4">
<h5 class="body-text-2 u-stretch">
Additional members
</h5>
<div>
{formatCurrency(
currentAggregation.additionalMemberAmount
)}
{#if ['storage', 'bandwidth'].includes(excess.name)}
{@const excessValue = humanFileSize(excess.value)}
<div class="u-flex u-main-space-between">
<h5 class="body-text-2 u-stretch u-capitalize">
{excess.name}
</h5>
{formatCurrency(excess.amount)}
</div>
</div>
<div class="u-flex u-gap-4">
<h5
class="body-text-2 u-stretch u-color-text-offline">
{currentAggregation.additionalMembers}
class="body-text-2 u-stretch u-color-text-offline"
title={formatNumberWithCommas(
excess.value ?? 0
) + 'bytes'}>
{excessValue.value ?? 0}{excessValue.unit}
</h5>
</div>
{:else if excess.name === 'member'}
<div class="u-flex u-main-space-between">
<h5 class="body-text-2 u-stretch u-capitalize">
Additional members
</h5>
{formatCurrency(excess.amount)}
</div>
<h5
class="body-text-2 u-stretch u-color-text-offline"
title={excess.value.toString()}>
{excess.value}
</h5>
{:else}
<div class="u-flex u-main-space-between">
<h5 class="body-text-2 u-stretch u-capitalize">
{excess.name}
</h5>
{formatCurrency(excess.amount)}
</div>
<h5
class="body-text-2 u-stretch u-color-text-offline"
title={formatNumberWithCommas(excess.value)}>
{abbreviateNumber(excess.value)}
</h5>
{/if}
</li>
{/if}
{#if currentInvoice?.usage}
{#each currentInvoice.usage as excess, i}
<li
class="u-flex-vertical u-gap-4 {currentAggregation.additionalMembers >
0
? 'u-padding-block-8'
: 'u-padding-block-start-8'}"
class:u-sep-block-start={i > 0 ||
currentAggregation.additionalMembers > 0}>
{#if ['storage', 'bandwidth'].includes(excess.name)}
{@const excessValue = humanFileSize(
excess.value
)}
<div class="u-flex u-main-space-between">
<h5
class="body-text-2 u-stretch u-capitalize">
{excess.name}
</h5>
{formatCurrency(excess.amount)}
</div>
<h5
class="body-text-2 u-stretch u-color-text-offline"
title={formatNumberWithCommas(
excess.value ?? 0
) + 'bytes'}>
{excessValue.value ?? 0}{excessValue.unit}
</h5>
{:else if excess.name !== 'member'}
<div class="u-flex u-main-space-between">
<h5
class="body-text-2 u-stretch u-capitalize">
{excess.name}
</h5>
{formatCurrency(excess.amount)}
</div>
<h5
class="body-text-2 u-stretch u-color-text-offline"
title={formatNumberWithCommas(
excess.value
)}>
{abbreviateNumber(excess.value)}
</h5>
{/if}
</li>
{/each}
{/if}
{/each}
</ul>
</CollapsibleItem>
{/if}
{#if currentPlan.supportsCredits && availableCredit > 0}
{#if estimation.discounts.length}
<CollapsibleItem noContent gap={4}>
<span class="body-text-2 u-flex u-cross-center u-gap-2"
><svg
width="16"
height="17"
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14.166 7.93434C14.4784 8.24676 14.4784 8.75329 14.166 9.06571L8.56603 14.6657C8.25361 14.9781 7.74708 14.9781 7.43466 14.6657L1.83466 9.06571C1.67842 8.90947 1.60032 8.70469 1.60034 8.49992V4.50002C1.60034 3.17454 2.67486 2.10002 4.00034 2.10002H8.00056C8.20523 2.10008 8.40987 2.17818 8.56603 2.33434L14.166 7.93434ZM4.00034 5.30002C4.44217 5.30002 4.80034 4.94185 4.80034 4.50002C4.80034 4.05819 4.44217 3.70002 4.00034 3.70002C3.55851 3.70002 3.20034 4.05819 3.20034 4.50002C3.20034 4.94185 3.55851 5.30002 4.00034 5.30002Z"
fill="#00BC5D" />
</svg>
Credits to be applied</span>
{#each estimation.discounts as discount}
<span class="body-text-2 u-flex u-cross-center u-gap-2"
><svg
width="16"
height="17"
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14.166 7.93434C14.4784 8.24676 14.4784 8.75329 14.166 9.06571L8.56603 14.6657C8.25361 14.9781 7.74708 14.9781 7.43466 14.6657L1.83466 9.06571C1.67842 8.90947 1.60032 8.70469 1.60034 8.49992V4.50002C1.60034 3.17454 2.67486 2.10002 4.00034 2.10002H8.00056C8.20523 2.10008 8.40987 2.17818 8.56603 2.33434L14.166 7.93434ZM4.00034 5.30002C4.44217 5.30002 4.80034 4.94185 4.80034 4.50002C4.80034 4.05819 4.44217 3.70002 4.00034 3.70002C3.55851 3.70002 3.20034 4.05819 3.20034 4.50002C3.20034 4.94185 3.55851 5.30002 4.00034 5.30002Z"
fill="#00BC5D" />
</svg>
{discount.label}</span>
<div
class="body-text-2 u-margin-inline-start-auto"
style="color: var(--web-green-500, #10B981)">
-{formatCurrency(
Math.min(availableCredit, currentInvoice?.amount ?? 0)
)}
</div>
<div
class="body-text-2 u-margin-inline-start-auto"
style="color: var(--web-green-500, #10B981)">
-{formatCurrency(discount.value)}
</div>
{/each}
</CollapsibleItem>
{/if}
@@ -190,16 +156,7 @@
}}></span>
</span>
<div class="body-text-2 u-margin-inline-start-auto">
{formatCurrency(
Math.max(
(currentInvoice?.amount ?? 0) -
Math.min(
availableCredit,
currentInvoice?.amount ?? 0
),
0
)
)}
{formatCurrency(estimation.grossAmount)}
</div>
</CollapsibleItem>
{/if}