diff --git a/src/lib/components/estimatedCard.svelte b/src/lib/components/estimatedCard.svelte new file mode 100644 index 000000000..8c935aa37 --- /dev/null +++ b/src/lib/components/estimatedCard.svelte @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts index 3f451a4bf..678861167 100644 --- a/src/lib/components/index.ts +++ b/src/lib/components/index.ts @@ -84,3 +84,4 @@ export { default as UsageCard } from './usageCard.svelte'; export { default as ViewToggle } from './viewToggle.svelte'; export { default as RegionEndpoint } from './regionEndpoint.svelte'; export { default as ExpirationInput } from './expirationInput.svelte'; +export { default as EstimatedCard } from './estimatedCard.svelte'; diff --git a/src/lib/layout/createProject.svelte b/src/lib/layout/createProject.svelte index 4e419c0b1..2199f0a4b 100644 --- a/src/lib/layout/createProject.svelte +++ b/src/lib/layout/createProject.svelte @@ -4,13 +4,14 @@ import { CustomId } from '$lib/components/index.js'; import { getFlagUrl } from '$lib/helpers/flag'; import { isCloud } from '$lib/system.js'; - import { currentPlan } from '$lib/stores/organization'; + import { currentPlan, organization } from '$lib/stores/organization'; import { Button } from '$lib/elements/forms'; import { base } from '$app/paths'; import { page } from '$app/state'; import type { Models } from '@appwrite.io/console'; import { filterRegions } from '$lib/helpers/regions'; import type { Snippet } from 'svelte'; + import { BillingPlan } from '$lib/constants'; let { projectName = $bindable(''), @@ -31,8 +32,11 @@ } = $props(); let showCustomId = $state(false); + let isProPlan = $derived($organization?.billingPlan === BillingPlan.PRO); let projectsLimited = $derived( - $currentPlan?.projects > 0 && projects && projects >= $currentPlan?.projects + isProPlan + ? projects && projects >= 2 + : $currentPlan?.projects > 0 && projects && projects >= $currentPlan?.projects ); @@ -46,24 +50,11 @@ {#if showTitle} Create your project {/if} - {#if projectsLimited} - - Extra projects are available on paid plans for an additional fee - - - - - {/if} + + {#if projectsLimited} + {#if isProPlan} + + You've reached your limit of 2 projects included on the Pro plan. Each extra + project costs $15/month. + + {:else} + + Extra projects are available on paid plans for an additional fee + + + + + {/if} + {/if} + {#if isCloud && regions.length > 0} {/if} - Billing + currentInvoice={data?.billingInvoice} + organizationUsage={data?.organizationUsage} + usageProjects={data?.usageProjects} /> { const { organization, scopes, currentPlan, countryList, locale } = await parent(); @@ -57,6 +59,47 @@ export const load: PageLoad = async ({ parent, depends }) => { organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION)) : false; + // load organization usage data for planSummary component + let organizationUsage = null; + let usageProjects: Record = {}; + + // load projects directly for the planSummary component + try { + const projectsResponse = await sdk.forConsole.projects.list([ + Query.equal('teamId', organization.$id), + Query.limit(1000) + ]); + + if (projectsResponse.projects.length > 0) { + // mock data since organizationUsage is not availlable due to some reason + organizationUsage = { + projects: projectsResponse.projects.map((project, index) => ({ + projectId: project.$id, + storage: 0, + executions: 0, + executionsMBSeconds: 0, + bandwidth: 0, + databasesReads: 0, + databasesWrites: 0, + users: 0, + authPhoneTotal: 40102, + authPhoneEstimate: 8.4, + imageTransformations: 0 + })) + }; + + // Create usageProjects mapping + for (const project of projectsResponse.projects) { + usageProjects[project.$id] = { + name: project.name, + region: project.region + }; + } + } + } catch (e) { + // ignore error + } + const [paymentMethods, addressList, billingAddress, availableCredit] = await Promise.all([ sdk.forConsole.billing.listPaymentMethods(), sdk.forConsole.billing.listAddresses(), @@ -76,6 +119,8 @@ export const load: PageLoad = async ({ parent, depends }) => { billingInvoice, areCreditsSupported, countryList, - locale + locale, + organizationUsage, + usageProjects }; }; diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index 11b2e824c..ad2c37097 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -1,293 +1,334 @@ {#if $organization} - {#if currentPlan.usagePerProject} - - - - {currentPlan.name} plan - - - Next payment of ${currentAggregation.amount} - will occur on - {toLocaleDate($organization?.billingNextInvoiceDate)} - - - - - - - - Base plan + + {currentPlan.name} plan + + + Next payment of {formatCurrency(currentInvoice?.amount || currentPlan?.price || 0)} + will occur on + {toLocaleDate($organization?.billingNextInvoiceDate)}. + + + +
+ + Current billing cycle ({new Date( + $organization?.billingCurrentInvoiceDate + ).toLocaleDateString('en', { day: 'numeric', month: 'short' })}-{new Date( + $organization?.billingNextInvoiceDate + ).toLocaleDateString('en', { day: 'numeric', month: 'short' })}) + + + Estimate, subject to change based on usage. + +
+ + + {#each billingData as row} + + {#each columns as col} + root.toggle(row.id)}> - {isTrial || - $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION - ? formatCurrency(0) - : currentPlan - ? formatCurrency(currentPlan?.price) - : ''} + {row.cells?.[col.id] ?? ''} -
-
-
- {#each currentAggregation.resources.filter((r) => r.amount && r.amount > 0 && Object.keys(currentPlan.addons).includes(r.resourceId) && currentPlan.addons[r.resourceId].price > 0) as excess, i} - {#if i > 0} - - {/if} - - - - {excess.resourceId} - - {formatCurrency(excess.amount)} - - - - - {/each} - {#each currentAggregation.projectBreakdown as projectBreakdown} - - - {formatCurrency(projectBreakdown.amount)} - - - {#each projectBreakdown.resources as resource, i} - {#if i > 0} - - {/if} + + {/each} - - - - {resource.resourceId} - - - {formatCurrency(resource.amount)} - - - - - - {formatNumberWithCommas(resource.value)} - - {abbreviateNumber(resource.value)} - - - - {/each} - - - {/each} -
-
- {:else} - - Payment estimates - A breakdown of your estimated upcoming payment for the current billing period. Totals displayed - exclude accumulated credits and applicable taxes. - -

- Due at: {toLocaleDate($organization?.billingNextInvoiceDate)} -

- - - - - {currentPlan.name} plan - - - {isTrial || - $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION - ? formatCurrency(0) - : currentPlan - ? formatCurrency(currentPlan?.price) - : ''} - - - - {#if currentPlan.budgeting && extraUsage > 0} - r.amount && r.amount > 0) - .length.toString()}> - - {formatCurrency(extraUsage >= 0 ? extraUsage : 0)} - - - {#each currentAggregation.resources.filter((r) => r.amount && r.amount > 0) as excess, i} - {#if i > 0} - - {/if} - - - - - {excess.resourceId} + + {#if row.children} + {#each row.children as child (child.id)} + + {/each} {/if} + + + {/each} - {#if currentPlan.supportsCredits && availableCredit > 0} - - - - Credits to be applied - - - -{formatCurrency( - Math.min(availableCredit, currentInvoice?.amount ?? 0) - )} - - - {/if} + + {}}> + + Total + + + {}}> + + + + {}}> + + {formatCurrency(totalAmount)} + + + + - {#if $organization?.billingPlan !== BillingPlan.FREE && $organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION} - - - - - Current total (USD) - - - - Estimates are updated daily and may differ from your - final invoice. - - - - - - {formatCurrency( - Math.max( - (currentInvoice?.amount ?? 0) - - Math.min( - availableCredit, - currentInvoice?.amount ?? 0 - ), - 0 - ) - )} - - - {/if} - - -
- - {#if $organization?.billingPlan === BillingPlan.FREE || $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION} -
- + +
+ {#if $organization?.billingPlan === BillingPlan.FREE || $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION} +
+ + +
+ {:else} +
+ {#if $organization?.billingPlanDowngrade !== null} + + {:else} -
- {:else} -
- {#if $organization?.billingPlanDowngrade !== null} - - {:else} - - {/if} - -
- {/if} - - - {/if} + {/if} + +
+ {/if} +
+ {/if}