From be53138d55a9efbd893473007837086dac92128e Mon Sep 17 00:00:00 2001 From: ChiragAgg5k Date: Wed, 5 Mar 2025 15:52:09 +0530 Subject: [PATCH 01/18] chore: replaced coupon type with sdk one --- src/lib/components/billing/couponInput.svelte | 4 ++-- src/lib/components/billing/creditsApplied.svelte | 4 ++-- src/lib/components/billing/discountsApplied.svelte | 4 ++-- src/lib/components/billing/estimatedTotal.svelte | 7 ++++--- .../components/billing/validateCreditModal.svelte | 4 ++-- src/lib/layout/unauthenticated.svelte | 4 ++-- src/lib/sdk/billing.ts | 14 ++------------ src/routes/(console)/apply-credit/+page.ts | 4 ++-- .../(console)/create-organization/+page.svelte | 9 +++++---- .../billing/wizard/addCredit.svelte | 4 ++-- .../change-plan/+page.svelte | 5 +++-- 11 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/lib/components/billing/couponInput.svelte b/src/lib/components/billing/couponInput.svelte index a6c1658eca..586a08d681 100644 --- a/src/lib/components/billing/couponInput.svelte +++ b/src/lib/components/billing/couponInput.svelte @@ -1,15 +1,15 @@ @@ -12,8 +12,13 @@ import { addSubPanel, registerCommands, updateCommandGroupRanks } from '$lib/commandCenter'; import { PlatformsPanel } from '$lib/commandCenter/panels'; import { Heading, Tab } from '$lib/components'; + import { total } from '$lib/helpers/array'; import { humanFileSize } from '$lib/helpers/sizeConvertion'; + import { formatNum } from '$lib/helpers/string'; import { Container, type UsagePeriods } from '$lib/layout'; + import { periodToDates } from '$lib/layout/usage.svelte'; + import { canWriteProjects } from '$lib/stores/roles'; + import type { Models } from '@appwrite.io/console'; import { onMount } from 'svelte'; import { onboarding, project } from '../store'; import Bandwidth from './bandwidth.svelte'; @@ -22,11 +27,6 @@ import Realtime from './realtime.svelte'; import Requests from './requests.svelte'; import { usage } from './store'; - import { formatNum } from '$lib/helpers/string'; - import { total } from '$lib/helpers/array'; - import type { Metric } from '$lib/sdk/usage'; - import { periodToDates } from '$lib/layout/usage.svelte'; - import { canWriteProjects } from '$lib/stores/roles'; $: projectId = $page.params.project; $: path = `${base}/project-${projectId}/overview`; From adc455ac680f5de5b27d0233fc96758a16257484 Mon Sep 17 00:00:00 2001 From: ChiragAgg5k Date: Wed, 5 Mar 2025 16:09:07 +0530 Subject: [PATCH 03/18] refactor: remove usage and backups --- src/lib/sdk/backups.ts | 275 --------------- src/lib/sdk/usage.ts | 325 ------------------ src/lib/stores/sdk.ts | 4 +- .../settings/usage/[[invoice]]/+page.ts | 20 +- 4 files changed, 21 insertions(+), 603 deletions(-) delete mode 100644 src/lib/sdk/backups.ts delete mode 100644 src/lib/sdk/usage.ts diff --git a/src/lib/sdk/backups.ts b/src/lib/sdk/backups.ts deleted file mode 100644 index bc903b2170..0000000000 --- a/src/lib/sdk/backups.ts +++ /dev/null @@ -1,275 +0,0 @@ -import type { Models } from '@appwrite.io/console'; -import { AppwriteException, Client, type Payload } from '@appwrite.io/console'; - -export class Backups { - client: Client; - - constructor(client: Client) { - this.client = client; - } - - async createArchive(services: string[], resourceId?: string): Promise { - if (typeof services === 'undefined') { - throw new AppwriteException('Missing required parameter: "services"'); - } - const apiPath = '/backups/archives'; - const payload: Payload = {}; - if (typeof services !== 'undefined') { - payload['services'] = services; - } - if (typeof resourceId !== 'undefined') { - payload['resourceId'] = resourceId; - } - const uri = new URL(this.client.config.endpoint + apiPath); - - const apiHeaders: { [header: string]: string } = { - 'content-type': 'application/json' - }; - - return await this.client.call('post', uri, apiHeaders, payload); - } - - async deleteArchive(archiveId: string): Promise { - if (typeof archiveId === 'undefined') { - throw new AppwriteException('Missing required parameter: "archiveId"'); - } - const apiPath = '/backups/archives/{archiveId}'.replace('{archiveId}', archiveId); - const payload: Payload = {}; - const uri = new URL(this.client.config.endpoint + apiPath); - - const apiHeaders: { [header: string]: string } = { - 'content-type': 'application/json' - }; - - return await this.client.call('delete', uri, apiHeaders, payload); - } - - async listArchives(queries?: string[]): Promise { - const apiPath = '/backups/archives'; - const payload: Payload = {}; - if (typeof queries !== 'undefined') { - payload['queries'] = queries; - } - const uri = new URL(this.client.config.endpoint + apiPath); - - const apiHeaders: { [header: string]: string } = { - 'content-type': 'application/json' - }; - - return await this.client.call('get', uri, apiHeaders, payload); - } - - async getArchive(archiveId: string): Promise { - if (typeof archiveId === 'undefined') { - throw new AppwriteException('Missing required parameter: "archiveId"'); - } - const apiPath = '/backups/archives/{archiveId}'.replace('{archiveId}', archiveId); - const payload: Payload = {}; - const uri = new URL(this.client.config.endpoint + apiPath); - - const apiHeaders: { [header: string]: string } = { - 'content-type': 'application/json' - }; - - return await this.client.call('get', uri, apiHeaders, payload); - } - - async listPolicies(queries?: string[]): Promise { - const apiPath = '/backups/policies'; - const payload: Payload = {}; - if (typeof queries !== 'undefined') { - payload['queries'] = queries; - } - const uri = new URL(this.client.config.endpoint + apiPath); - - const apiHeaders: { [header: string]: string } = { - 'content-type': 'application/json' - }; - - return await this.client.call('get', uri, apiHeaders, payload); - } - - async createPolicy( - policyId: string, - services: string[], - retention: number, - schedule: string, - name?: string, - resourceId?: string, - enabled?: boolean - ): Promise { - if (typeof policyId === 'undefined') { - throw new AppwriteException('Missing required parameter: "policyId"'); - } - if (typeof services === 'undefined') { - throw new AppwriteException('Missing required parameter: "services"'); - } - if (typeof retention === 'undefined') { - throw new AppwriteException('Missing required parameter: "retention"'); - } - if (typeof schedule === 'undefined') { - throw new AppwriteException('Missing required parameter: "schedule"'); - } - const apiPath = '/backups/policies'; - const payload: Payload = {}; - if (typeof policyId !== 'undefined') { - payload['policyId'] = policyId; - } - if (typeof name !== 'undefined') { - payload['name'] = name; - } - if (typeof services !== 'undefined') { - payload['services'] = services; - } - if (typeof resourceId !== 'undefined') { - payload['resourceId'] = resourceId; - } - if (typeof enabled !== 'undefined') { - payload['enabled'] = enabled; - } - if (typeof retention !== 'undefined') { - payload['retention'] = retention; - } - if (typeof schedule !== 'undefined') { - payload['schedule'] = schedule; - } - const uri = new URL(this.client.config.endpoint + apiPath); - - const apiHeaders: { [header: string]: string } = { - 'content-type': 'application/json' - }; - - return await this.client.call('post', uri, apiHeaders, payload); - } - - async getPolicy(policyId: string): Promise { - if (typeof policyId === 'undefined') { - throw new AppwriteException('Missing required parameter: "policyId"'); - } - const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId); - const payload: Payload = {}; - const uri = new URL(this.client.config.endpoint + apiPath); - - const apiHeaders: { [header: string]: string } = { - 'content-type': 'application/json' - }; - - return await this.client.call('get', uri, apiHeaders, payload); - } - - async updatePolicy( - policyId: string, - name?: string, - retention?: number, - schedule?: string, - enabled?: boolean - ): Promise { - if (typeof policyId === 'undefined') { - throw new AppwriteException('Missing required parameter: "policyId"'); - } - const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId); - const payload: Payload = {}; - if (typeof name !== 'undefined') { - payload['name'] = name; - } - if (typeof retention !== 'undefined') { - payload['retention'] = retention; - } - if (typeof schedule !== 'undefined') { - payload['schedule'] = schedule; - } - if (typeof enabled !== 'undefined') { - payload['enabled'] = enabled; - } - const uri = new URL(this.client.config.endpoint + apiPath); - - const apiHeaders: { [header: string]: string } = { - 'content-type': 'application/json' - }; - - return await this.client.call('patch', uri, apiHeaders, payload); - } - - async deletePolicy(policyId: string): Promise { - if (typeof policyId === 'undefined') { - throw new AppwriteException('Missing required parameter: "policyId"'); - } - const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId); - const payload: Payload = {}; - const uri = new URL(this.client.config.endpoint + apiPath); - - const apiHeaders: { [header: string]: string } = { - 'content-type': 'application/json' - }; - - return await this.client.call('delete', uri, apiHeaders, payload); - } - - async createRestoration( - archiveId: string, - services: string[], - newResourceId?: string, - newResourceName?: string - ): Promise { - if (typeof archiveId === 'undefined') { - throw new AppwriteException('Missing required parameter: "archiveId"'); - } - if (typeof services === 'undefined') { - throw new AppwriteException('Missing required parameter: "services"'); - } - const apiPath = '/backups/restoration'; - const payload: Payload = {}; - if (typeof archiveId !== 'undefined') { - payload['archiveId'] = archiveId; - } - if (typeof services !== 'undefined') { - payload['services'] = services; - } - if (typeof newResourceId !== 'undefined') { - payload['newResourceId'] = newResourceId; - } - if (typeof newResourceName !== 'undefined') { - payload['newResourceName'] = newResourceName; - } - const uri = new URL(this.client.config.endpoint + apiPath); - - const apiHeaders: { [header: string]: string } = { - 'content-type': 'application/json' - }; - - return await this.client.call('post', uri, apiHeaders, payload); - } - - async listRestorations(queries?: string[]): Promise { - const apiPath = '/backups/restorations'; - const payload: Payload = {}; - if (typeof queries !== 'undefined') { - payload['queries'] = queries; - } - const uri = new URL(this.client.config.endpoint + apiPath); - - const apiHeaders: { [header: string]: string } = { - 'content-type': 'application/json' - }; - - return await this.client.call('get', uri, apiHeaders, payload); - } - - async getRestoration(restorationId: string): Promise { - if (typeof restorationId === 'undefined') { - throw new AppwriteException('Missing required parameter: "restorationId"'); - } - const apiPath = '/backups/restorations/{restorationId}'.replace( - '{restorationId}', - restorationId - ); - const payload: Payload = {}; - const uri = new URL(this.client.config.endpoint + apiPath); - - const apiHeaders: { [header: string]: string } = { - 'content-type': 'application/json' - }; - - return await this.client.call('get', uri, apiHeaders, payload); - } -} diff --git a/src/lib/sdk/usage.ts b/src/lib/sdk/usage.ts deleted file mode 100644 index fcce718017..0000000000 --- a/src/lib/sdk/usage.ts +++ /dev/null @@ -1,325 +0,0 @@ -import type { Models } from '@appwrite.io/console'; - -export function accumulateUsage(usage: Models.Metric[], base: number): Models.Metric[] { - const accumulation = usage.reduce( - (carry, item) => { - const value = item.value + carry.currentTotal; - return { - currentTotal: value, - metrics: [...carry.metrics, { ...item, value }] - }; - }, - { - currentTotal: base, - metrics: [] - } - ); - - return accumulation.metrics; -} - -/** - * UsageDatabases - */ -export type UsageDatabases = { - /** - * The time range of the usage stats. - */ - range: string; - /** - * Aggregated total statistics of documents. - */ - databasesTotal: number; - /** - * Aggregated total statistics of collections. - */ - collectionsTotal: number; - /** - * Aggregated total statistics of documents. - */ - documentsTotal: number; - /** - * Aggregated total statistics of documents per period. - */ - databases: Models.Metric[]; - /** - * Aggregated total statistics of collections per period. - */ - collections: Models.Metric[]; - /** - * Aggregated total statistics of documents per period. - */ - documents: Models.Metric[]; -}; -/** - * UsageDatabase - */ -export type UsageDatabase = { - /** - * The time range of the usage stats. - */ - range: string; - /** - * Aggregated total statistics of collections. - */ - collectionsTotal: number; - /** - * Aggregated total statistics of documents. - */ - documentsTotal: number; - /** - * Aggregated statistics collections per period. - */ - collections: Models.Metric[]; - /** - * Aggregated statistics of documents per period. - */ - documents: Models.Metric[]; -}; -/** - * UsageCollection - */ -export type UsageCollection = { - /** - * The time range of the usage stats. - */ - range: string; - /** - * Aggregated total statistics of documents. - */ - documentsTotal: number; - /** - * Aggregated statistics of documents per period. - */ - documents: Models.Metric[]; -}; -/** - * UsageUsers - */ -export type UsageUsers = { - /** - * The time range of the usage stats. - */ - range: string; - /** - * Aggregated total statistics of users. - */ - usersTotal: number; - /** - * Aggregated total statistics sessions created. - */ - sessionsTotal: number; - /** - * Aggregated statistics of users per period. - */ - users: Models.Metric[]; - /** - * Aggregated statistics sessions created per period. - */ - sessions: Models.Metric[]; -}; -/** - * StorageUsage - */ -export type UsageStorage = { - /** - * The time range of the usage stats. - */ - range: string; - /** - * Aggregated total statistics of buckets - */ - bucketsTotal: number; - /** - * Aggregated total statistics of files. - */ - filesTotal: number; - /** - * Aggregated total statistics of files storage (in bytes). - */ - filesStorageTotal: number; - /** - * Aggregated statistics of buckets per period. - */ - buckets: Models.Metric[]; - /** - * Aggregated statistics of files per period. - */ - files: Models.Metric[]; - /** - * Aggregated statistics of storage (in bytes) per period . - */ - storage: Models.Metric[]; -}; -/** - * UsageBuckets - */ -export type UsageBuckets = { - /** - * The time range of the usage stats. - */ - range: string; - /** - * Aggregated total statistics of bucket files. - */ - filesTotal: number; - /** - * Aggregated total statistics of bucket files storage. - */ - filesStorageTotal: number; - /** - * Aggregated statistics of bucket files per period. - */ - files: Models.Metric[]; - /** - * Aggregated statistics of bucket storage files per period. - */ - storage: Models.Metric[]; - /** - * Aggregated statistics of bucket image transformations per period. - */ - imageTransformations: Models.Metric[]; - /** - * Total aggregated number of bucket image transformations. - */ - imageTransformationsTotal: number; -}; -/** - * UsageFunctions - */ -export type UsageFunctions = { - /** - * The time range of the usage stats. - */ - range: string; - /** - * Aggregated total statistics of functions. - */ - functionsTotal: number; - /** - * Aggregated total statistics of function deployments. - */ - deploymentsTotal: number; - /** - * Aggregated total statistics of function deployments storage. - */ - deploymentsStorageTotal: number; - /** - * Aggregated total statistics of function builds. - */ - buildsTotal: number; - /** - * Aggregated total statistics of builds storage. - */ - buildsStorageTotal: number; - /** - * Aggregated total statistics of build compute time. - */ - buildsTimeTotal: number; - /** - * Aggregated total statistics of functions executions. - */ - executionsTotal: number; - /** - * Aggregated total statistics of functions execution compute time. - */ - executionsTimeTotal: number; - /** - * Aggregated statistics of functions per period. - */ - functions: Models.Metric[]; - /** - * Aggregated statistics of deployments per period. - */ - deployments: Models.Metric[]; - /** - * Aggregated statistics of deployments storage per period. - */ - deploymentsStorage: Models.Metric[]; - /** - * Aggregated statistics of builds per period. - */ - builds: Models.Metric[]; - /** - * Aggregated statistics of storage per period. - */ - buildsStorage: Models.Metric[]; - /** - * Aggregated statistics of builds compute time per period. - */ - buildsTime: Models.Metric[]; - /** - * Aggregated statistics of executions per period. - */ - executions: Models.Metric[]; - /** - * Aggregated statistics of execution compute time per period. - */ - executionsTime: Models.Metric[]; -}; -/** - * UsageProject - */ -export type UsageProject = { - /** - * The time range of the usage stats. - */ - range: number; - /** - * Aggregated statistics of number of requests per period. - */ - requests: string[]; - /** - * Aggregated statistics of consumed bandwidth per period. - */ - network: string[]; - /** - * Aggregated statistics of total function executions. - */ - executionsTotal: number; - /** - * Aggregated statistics of total number of documents. - */ - documentsTotal: number; - /** - * Aggregated statistics of total number of databases. - */ - databasesTotal: number; - /** - * Aggregated statistics of total number of users. - */ - usersTotal: number; - /** - * Aggregated statistics of total occupied storage size (in bytes). - */ - filesStorageTotal: number; - /** - * Aggregated statistics of total number of buckets. - */ - bucketsTotal: number; - - /** - * Aggregated statistics of total number of SMS sent. - */ - authPhoneTotal: number; - - /** - * Aggregated statistics of estimated SMS cost. - */ - authPhoneEstimate: number; - - /** - * Aggregated statistics of total number SMS by country. - */ - authPhoneCountriesBreakdown: Models.MetricBreakdown[]; - - /** - * Array of image transformations per period. - */ - imageTransformations: Models.Metric[]; - - /** - * Aggregated statistics of total number of image transformations. - */ - imageTransformationsTotal: number; -}; diff --git a/src/lib/stores/sdk.ts b/src/lib/stores/sdk.ts index c4b3189ddd..5488692290 100644 --- a/src/lib/stores/sdk.ts +++ b/src/lib/stores/sdk.ts @@ -1,9 +1,11 @@ import { getProjectId } from '$lib/helpers/project'; +import { Sources } from '$lib/sdk/sources'; import { VARS } from '$lib/system'; import { Account, Assistant, Avatars, + Backups, Client, Console, Databases, @@ -22,8 +24,6 @@ import { Vcs } from '@appwrite.io/console'; import { Billing } from '../sdk/billing'; -import { Backups } from '../sdk/backups'; -import { Sources } from '$lib/sdk/sources'; export function getApiEndpoint(): string { if (VARS.APPWRITE_ENDPOINT) return VARS.APPWRITE_ENDPOINT; diff --git a/src/routes/(console)/project-[project]/settings/usage/[[invoice]]/+page.ts b/src/routes/(console)/project-[project]/settings/usage/[[invoice]]/+page.ts index 2cb455ae0f..d84e4b7cf6 100644 --- a/src/routes/(console)/project-[project]/settings/usage/[[invoice]]/+page.ts +++ b/src/routes/(console)/project-[project]/settings/usage/[[invoice]]/+page.ts @@ -1,9 +1,27 @@ import type { Aggregation, Invoice } from '$lib/sdk/billing'; -import { accumulateUsage } from '$lib/sdk/usage'; import { getSdkForProject, sdk } from '$lib/stores/sdk'; +import type { Models } from '@appwrite.io/console'; import { Query } from '@appwrite.io/console'; import type { PageLoad } from './$types'; +function accumulateUsage(usage: Models.Metric[], base: number): Models.Metric[] { + const accumulation = usage.reduce( + (carry, item) => { + const value = item.value + carry.currentTotal; + return { + currentTotal: value, + metrics: [...carry.metrics, { ...item, value }] + }; + }, + { + currentTotal: base, + metrics: [] + } + ); + + return accumulation.metrics; +} + export const load: PageLoad = async ({ params, parent }) => { const { invoice, project } = params; const { organization } = await parent(); From b8b2e5236c272d8e5c5ef799c95fe9ae7951c107 Mon Sep 17 00:00:00 2001 From: ChiragAgg5k Date: Wed, 5 Mar 2025 17:04:54 +0530 Subject: [PATCH 04/18] refactor: payment and billing methods --- .../billing/selectPaymentMethod.svelte | 8 +- src/lib/components/creditCardInfo.svelte | 4 +- src/lib/sdk/billing.ts | 98 +++---------------- src/lib/stores/billing.ts | 10 +- src/lib/stores/stripe.ts | 12 +-- .../account/payments/editPaymentModal.svelte | 10 +- .../account/payments/paymentMethods.svelte | 14 +-- .../(console)/apply-credit/+page.svelte | 8 +- .../create-organization/+page.svelte | 3 +- .../billing/+page.svelte | 34 +++---- .../billing/availableCredit.svelte | 20 ++-- .../billing/paymentMethods.svelte | 20 ++-- .../billing/planSummary.svelte | 15 +-- .../billing/replaceCard.svelte | 14 +-- .../billing/retryPaymentModal.svelte | 19 ++-- .../billing/wizard/paymentDetails.svelte | 16 +-- .../change-plan/+page.svelte | 3 +- .../settings/invoicesTable.svelte | 15 +-- 18 files changed, 125 insertions(+), 198 deletions(-) diff --git a/src/lib/components/billing/selectPaymentMethod.svelte b/src/lib/components/billing/selectPaymentMethod.svelte index 8e82a806e9..4363fa38fe 100644 --- a/src/lib/components/billing/selectPaymentMethod.svelte +++ b/src/lib/components/billing/selectPaymentMethod.svelte @@ -1,14 +1,14 @@
diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index 6248926404..b00239aef2 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -4,31 +4,6 @@ import type { Client, Models } from '@appwrite.io/console'; import type { PaymentMethod } from '@stripe/stripe-js'; import type { Organization, OrganizationError, OrganizationList } from '../stores/organization'; -export type PaymentMethodData = { - $id: string; - $createdAt: string; - $updatedAt: string; - providerMethodId: string; - providerUserId: string; - userId: string; - expiryMonth: number; - expiryYear: number; - expired: boolean; - last4: string; - country: string; - brand: string; - clientSecret: string; - failed: boolean; - name: string; - mandateId?: string; - lastError?: string; -}; - -export type PaymentList = { - paymentMethods: PaymentMethodData[]; - total: number; -}; - export type Invoice = { $id: string; $createdAt: Date; @@ -91,55 +66,6 @@ export type EstimationDeleteOrganization = { unpaidInvoices: Invoice[]; }; -export type Credit = { - /** - * Credit ID. - */ - $id: string; - /** - * Credit creation time in ISO 8601 format. - */ - $createdAt: string; - /** - * Credit update date in ISO 8601 format. - */ - $updatedAt: string; - /** - * coupon ID - */ - couponId: string; - /** - * ID of the User. - */ - userId: string; - /** - * ID of the Team. - */ - teamId: string; - /** - * Provided credit amount - */ - total: number; - /** - * Remaining credit amount - */ - credits: number; - /** - * Credit expiration time in ISO 8601 format. - */ - expiration: string; - /** - * Status of the credit. Can be one of `disabled`, `active` or `expired`. - */ - status: string; -}; - -export type CreditList = { - available: number; - credits: Credit[]; - total: number; -}; - export type Aggregation = { $id: string; /** @@ -816,7 +742,7 @@ export class Billing { ); } - async addCredit(organizationId: string, couponId: string): Promise { + async addCredit(organizationId: string, couponId: string): Promise { const path = `/organizations/${organizationId}/credits`; const params = { couponId @@ -831,7 +757,7 @@ export class Billing { params ); } - async listCredits(organizationId: string, queries = []): Promise { + async listCredits(organizationId: string, queries = []): Promise { const path = `/organizations/${organizationId}/credits`; const params = { queries @@ -847,7 +773,7 @@ export class Billing { ); } - async getCredit(organizationId: string, creditId: string): Promise { + async getCredit(organizationId: string, creditId: string): Promise { const path = `/organizations/${organizationId}/credits/${creditId}`; const params = { creditId @@ -973,7 +899,7 @@ export class Billing { async getOrganizationPaymentMethod( organizationId: string, paymentMethodId: string - ): Promise { + ): Promise { const path = `/organizations/${organizationId}/payment-methods/${paymentMethodId}`; const params = { organizationId, @@ -1012,7 +938,7 @@ export class Billing { //ACCOUNT - async listPaymentMethods(queries: [] = []): Promise { + async listPaymentMethods(queries: [] = []): Promise { const path = `/account/payment-methods`; const params = { queries @@ -1028,7 +954,7 @@ export class Billing { ); } - async getPaymentMethod(paymentMethodId: string): Promise { + async getPaymentMethod(paymentMethodId: string): Promise { const path = `/account/payment-methods/${paymentMethodId}`; const params = { paymentMethodId @@ -1044,7 +970,7 @@ export class Billing { ); } - async createPaymentMethod(): Promise { + async createPaymentMethod(): Promise { const path = `/account/payment-methods`; const params = {}; const uri = new URL(this.client.config.endpoint + path); @@ -1062,7 +988,7 @@ export class Billing { paymentMethodId: string, providerMethodId: string | PaymentMethod, name: string - ): Promise { + ): Promise { const path = `/account/payment-methods/${paymentMethodId}/provider`; const params = { paymentMethodId, @@ -1084,7 +1010,7 @@ export class Billing { paymentMethodId: string, expiryMonth: string, expiryYear: string - ): Promise { + ): Promise { const path = `/account/payment-methods/${paymentMethodId}`; const params = { paymentMethodId, @@ -1102,7 +1028,7 @@ export class Billing { ); } - async deletePaymentMethod(paymentMethodId: string): Promise { + async deletePaymentMethod(paymentMethodId: string): Promise { const path = `/account/payment-methods/${paymentMethodId}`; const params = { paymentMethodId @@ -1117,7 +1043,7 @@ export class Billing { params ); } - async setDefaultPaymentMethod(paymentMethodId: string): Promise { + async setDefaultPaymentMethod(paymentMethodId: string): Promise { const path = `/account/payment-methods/${paymentMethodId}/default`; const params = { paymentMethodId @@ -1136,7 +1062,7 @@ export class Billing { async setupPaymentMandate( organizationId: string, paymentMethodId: string - ): Promise { + ): Promise { const path = `/account/payment-methods/${paymentMethodId}/setup`; const params = { organizationId, diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index dd698c9121..1166558c50 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -17,13 +17,12 @@ import type { Aggregation, Invoice, InvoiceList, - PaymentList, - PaymentMethodData, Plan, PlansMap } from '$lib/sdk/billing'; import { isCloud } from '$lib/system'; import { activeHeaderAlert, orgMissingPaymentMethod } from '$routes/(console)/store'; +import type { Models } from '@appwrite.io/console'; import { Query } from '@appwrite.io/console'; import { derived, get, writable } from 'svelte/store'; import { headerAlert } from './headerAlert'; @@ -58,7 +57,10 @@ export const roles = [ } ]; -export const paymentMethods = derived(page, ($page) => $page.data.paymentMethods as PaymentList); +export const paymentMethods = derived( + page, + ($page) => $page.data.paymentMethods as Models.PaymentMethodList +); export const addressList = derived(page, ($page) => $page.data.addressList as AddressesList); export const plansInfo = derived(page, ($page) => $page.data.plansInfo as PlansMap); export const daysLeftInTrial = writable(0); @@ -414,7 +416,7 @@ export function checkForMarkedForDeletion(org: Organization) { } } -export const paymentMissingMandate = writable(null); +export const paymentMissingMandate = writable(null); export async function checkForMandate(org: Organization) { const paymentId = org.paymentMethodId ?? org.backupPaymentMethodId; diff --git a/src/lib/stores/stripe.ts b/src/lib/stores/stripe.ts index 58edfd4316..488e7c16e7 100644 --- a/src/lib/stores/stripe.ts +++ b/src/lib/stores/stripe.ts @@ -1,15 +1,15 @@ +import { base } from '$app/paths'; +import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; +import type { Models } from '@appwrite.io/console'; import type { Stripe, StripeElement, StripeElements } from '@stripe/stripe-js'; -import { sdk } from './sdk'; -import { app } from './app'; import { get, writable } from 'svelte/store'; -import type { PaymentMethodData } from '$lib/sdk/billing'; -import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; +import { app } from './app'; import { addNotification } from './notifications'; import { organization } from './organization'; -import { base } from '$app/paths'; +import { sdk } from './sdk'; export const stripe = writable(); -let paymentMethod: PaymentMethodData; +let paymentMethod: Models.PaymentMethod; let clientSecret: string; let elements: StripeElements; let paymentElement: StripeElement; diff --git a/src/routes/(console)/account/payments/editPaymentModal.svelte b/src/routes/(console)/account/payments/editPaymentModal.svelte index 56529fdc9a..bc58d5d5e3 100644 --- a/src/routes/(console)/account/payments/editPaymentModal.svelte +++ b/src/routes/(console)/account/payments/editPaymentModal.svelte @@ -1,15 +1,15 @@ diff --git a/src/routes/(console)/apply-credit/+page.svelte b/src/routes/(console)/apply-credit/+page.svelte index 55c0045dc7..5f1e7e6f74 100644 --- a/src/routes/(console)/apply-credit/+page.svelte +++ b/src/routes/(console)/apply-credit/+page.svelte @@ -12,7 +12,7 @@ WizardSecondaryContent, WizardSecondaryFooter } from '$lib/layout'; - import { type PaymentList, type Plan } from '$lib/sdk/billing'; + import { type Plan } from '$lib/sdk/billing'; import { app } from '$lib/stores/app'; import { isOrganization } from '$lib/stores/billing.js'; import { addNotification } from '$lib/stores/notifications'; @@ -23,10 +23,10 @@ } from '$lib/stores/organization'; import { sdk } from '$lib/stores/sdk'; import { confirmPayment } from '$lib/stores/stripe.js'; - import { ID } from '@appwrite.io/console'; + import { getCampaignImageUrl } from '$routes/(public)/card/helpers'; + import { ID, type Models } from '@appwrite.io/console'; import { onMount } from 'svelte'; import { writable } from 'svelte/store'; - import { getCampaignImageUrl } from '$routes/(public)/card/helpers'; export let data; @@ -51,7 +51,7 @@ let formComponent: Form; let couponForm: Form; let isSubmitting = writable(false); - let methods: PaymentList; + let methods: Models.PaymentMethodList; let paymentMethodId: string; let collaborators: string[]; let taxId: string; diff --git a/src/routes/(console)/create-organization/+page.svelte b/src/routes/(console)/create-organization/+page.svelte index d1ec27bce8..c54ef6d4a0 100644 --- a/src/routes/(console)/create-organization/+page.svelte +++ b/src/routes/(console)/create-organization/+page.svelte @@ -14,7 +14,6 @@ WizardSecondaryContent, WizardSecondaryFooter } from '$lib/layout'; - import type { PaymentList } from '$lib/sdk/billing'; import { isOrganization, tierToPlan } from '$lib/stores/billing'; import { addNotification } from '$lib/stores/notifications'; import { @@ -43,7 +42,7 @@ let formComponent: Form; let isSubmitting = writable(false); - let methods: PaymentList; + let methods: Models.PaymentMethodList; let name: string; let billingPlan: BillingPlan = BillingPlan.FREE; let paymentMethodId: string; diff --git a/src/routes/(console)/organization-[organization]/billing/+page.svelte b/src/routes/(console)/organization-[organization]/billing/+page.svelte index c370fbf314..694b0af836 100644 --- a/src/routes/(console)/organization-[organization]/billing/+page.svelte +++ b/src/routes/(console)/organization-[organization]/billing/+page.svelte @@ -1,35 +1,35 @@ diff --git a/src/routes/(console)/project-[project]/settings/deleteProject.svelte b/src/routes/(console)/project-[project]/settings/deleteProject.svelte index 34ae0206c2..4a130f4de5 100644 --- a/src/routes/(console)/project-[project]/settings/deleteProject.svelte +++ b/src/routes/(console)/project-[project]/settings/deleteProject.svelte @@ -18,7 +18,7 @@ let regions: Models.ConsoleRegionList; onMount(async () => { if (isCloud) { - regions = await sdk.forConsole.billing.listRegions(); + regions = await sdk.forConsole.console.regions(); } }); From 5abc743eba80f919779a63381b0286e854fc7382 Mon Sep 17 00:00:00 2001 From: ChiragAgg5k Date: Thu, 6 Mar 2025 17:22:43 +0530 Subject: [PATCH 09/18] refactor: account payment methods --- .../billing/selectPaymentMethod.svelte | 2 +- src/lib/sdk/billing.ts | 132 ------------------ src/lib/stores/billing.ts | 2 +- src/lib/stores/stripe.ts | 8 +- .../(console)/account/payments/+page.ts | 2 +- .../payments/deletePaymentModal.svelte | 4 +- .../account/payments/editPaymentModal.svelte | 6 +- .../(console)/apply-credit/+page.svelte | 2 +- .../create-organization/+page.svelte | 2 +- .../billing/+page.ts | 4 +- .../billing/addCreditModal.svelte | 2 +- .../billing/addCreditWizard.svelte | 17 ++- .../billing/availableCredit.svelte | 2 +- .../billing/replaceCard.svelte | 2 +- .../billing/retryPaymentModal.svelte | 2 +- .../billing/wizard/paymentDetails.svelte | 2 +- .../change-plan/+page.svelte | 2 +- 17 files changed, 32 insertions(+), 161 deletions(-) diff --git a/src/lib/components/billing/selectPaymentMethod.svelte b/src/lib/components/billing/selectPaymentMethod.svelte index 4363fa38fe..31ebfea1a0 100644 --- a/src/lib/components/billing/selectPaymentMethod.svelte +++ b/src/lib/components/billing/selectPaymentMethod.svelte @@ -29,7 +29,7 @@ async function cardSaved(event: CustomEvent) { value = event.detail.$id; - methods = await sdk.forConsole.billing.listPaymentMethods(); + methods = await sdk.forConsole.account.listPaymentMethods(); } onMount(() => { diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index 6ba951aa59..1e62c9066c 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -585,53 +585,6 @@ export class Billing { ); } - async addCredit(organizationId: string, couponId: string): Promise { - const path = `/organizations/${organizationId}/credits`; - const params = { - couponId - }; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'POST', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } - async listCredits(organizationId: string, queries = []): Promise { - const path = `/organizations/${organizationId}/credits`; - const params = { - queries - }; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'get', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } - - async getCredit(organizationId: string, creditId: string): Promise { - const path = `/organizations/${organizationId}/credits/${creditId}`; - const params = { - creditId - }; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'GET', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } - async getCampaign(campaignId: string): Promise { const path = `/console/campaigns/${campaignId}`; const uri = new URL(this.client.config.endpoint + path); @@ -779,54 +732,6 @@ export class Billing { ); } - //ACCOUNT - - async listPaymentMethods(queries: [] = []): Promise { - const path = `/account/payment-methods`; - const params = { - queries - }; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'GET', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } - - async getPaymentMethod(paymentMethodId: string): Promise { - const path = `/account/payment-methods/${paymentMethodId}`; - const params = { - paymentMethodId - }; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'GET', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } - - async createPaymentMethod(): Promise { - const path = `/account/payment-methods`; - const params = {}; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'POST', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } - async setPaymentMethod( paymentMethodId: string, providerMethodId: string | PaymentMethod, @@ -849,43 +754,6 @@ export class Billing { ); } - async updatePaymentMethod( - paymentMethodId: string, - expiryMonth: string, - expiryYear: string - ): Promise { - const path = `/account/payment-methods/${paymentMethodId}`; - const params = { - paymentMethodId, - expiryMonth, - expiryYear - }; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'patch', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } - - async deletePaymentMethod(paymentMethodId: string): Promise { - const path = `/account/payment-methods/${paymentMethodId}`; - const params = { - paymentMethodId - }; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'delete', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } async setDefaultPaymentMethod(paymentMethodId: string): Promise { const path = `/account/payment-methods/${paymentMethodId}/default`; const params = { diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index c04407c2a8..b8c8071a19 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -414,7 +414,7 @@ export const paymentMissingMandate = writable(null); export async function checkForMandate(org: Organization) { const paymentId = org.paymentMethodId ?? org.backupPaymentMethodId; if (!paymentId) return; - const paymentMethod = await sdk.forConsole.billing.getPaymentMethod(paymentId); + const paymentMethod = await sdk.forConsole.account.getPaymentMethod(paymentId); if (paymentMethod?.mandateId === null && paymentMethod?.country.toLowerCase() === 'in') { headerAlert.add({ id: 'paymentMandate', diff --git a/src/lib/stores/stripe.ts b/src/lib/stores/stripe.ts index 488e7c16e7..3df5d3f905 100644 --- a/src/lib/stores/stripe.ts +++ b/src/lib/stores/stripe.ts @@ -20,7 +20,7 @@ export async function initializeStripe() { if (!get(stripe)) return; isStripeInitialized.set(true); - const methods = await sdk.forConsole.billing.listPaymentMethods(); + const methods = await sdk.forConsole.account.listPaymentMethods(); // Get the client secret from empty payment method if available clientSecret = methods.paymentMethods?.filter( @@ -29,7 +29,7 @@ export async function initializeStripe() { // If there is no payment method, create an empty one and get the client secret if (!clientSecret) { - paymentMethod = await sdk.forConsole.billing.createPaymentMethod(); + paymentMethod = await sdk.forConsole.account.createPaymentMethod(); clientSecret = paymentMethod.clientSecret; } @@ -56,7 +56,7 @@ export async function submitStripeCard(name: string, organizationId?: string) { try { // If a payment method was created during initialization, use it, otherwise create a new one if (!paymentMethod) { - paymentMethod = await sdk.forConsole.billing.createPaymentMethod(); + paymentMethod = await sdk.forConsole.account.createPaymentMethod(); clientSecret = paymentMethod.clientSecret; } @@ -123,7 +123,7 @@ export async function confirmPayment( const url = window.location.origin + (route ? route : `${base}/organization-${orgId}/billing`); - const paymentMethod = await sdk.forConsole.billing.getPaymentMethod(paymentMethodId); + const paymentMethod = await sdk.forConsole.account.getPaymentMethod(paymentMethodId); const { error } = await get(stripe).confirmPayment({ clientSecret: clientSecret, diff --git a/src/routes/(console)/account/payments/+page.ts b/src/routes/(console)/account/payments/+page.ts index 2892a28bad..c528968a63 100644 --- a/src/routes/(console)/account/payments/+page.ts +++ b/src/routes/(console)/account/payments/+page.ts @@ -7,7 +7,7 @@ export const load: PageLoad = async ({ depends }) => { depends(Dependencies.ADDRESS); const [paymentMethods, addressList] = await Promise.all([ - sdk.forConsole.billing.listPaymentMethods(), + sdk.forConsole.account.listPaymentMethods(), sdk.forConsole.account.listBillingAddresses() ]); return { diff --git a/src/routes/(console)/account/payments/deletePaymentModal.svelte b/src/routes/(console)/account/payments/deletePaymentModal.svelte index 59240ec391..2922ec667e 100644 --- a/src/routes/(console)/account/payments/deletePaymentModal.svelte +++ b/src/routes/(console)/account/payments/deletePaymentModal.svelte @@ -1,7 +1,7 @@ diff --git a/src/routes/(console)/organization-[organization]/header.svelte b/src/routes/(console)/organization-[organization]/header.svelte index c2438d1f38..cf3e08f31d 100644 --- a/src/routes/(console)/organization-[organization]/header.svelte +++ b/src/routes/(console)/organization-[organization]/header.svelte @@ -118,7 +118,7 @@ FREE {/if} - {#if isCloud && $organization?.billingTrialStartDate && $daysLeftInTrial > 0 && $organization.billingPlan !== BillingPlan.FREE && $plansInfo.get($organization.billingPlan)?.trialDays} + {#if isCloud && $organization?.billingTrialStartDate && $daysLeftInTrial > 0 && $organization.billingPlan !== BillingPlan.FREE && $plansInfo.get($organization.billingPlan)?.trial}
Date: Tue, 11 Mar 2025 14:38:00 +0530 Subject: [PATCH 14/18] refactor: endpoints --- src/lib/sdk/billing.ts | 121 ------------------ src/lib/stores/billing.ts | 10 +- src/routes/(console)/+layout.ts | 2 +- .../account/payments/billingAddress.svelte | 11 +- .../payments/deleteAddressModal.svelte | 4 +- .../account/payments/editAddressModal.svelte | 4 +- .../(console)/apply-credit/+page.svelte | 2 +- .../organization-[organization]/+layout.ts | 2 +- .../billing/+page.svelte | 4 +- .../billing/+page.ts | 10 +- .../billing/billingAddress.svelte | 6 +- .../billing/paymentHistory.svelte | 2 +- .../billing/removeAddress.svelte | 8 +- .../invoices/[invoiceId]/download/+page.ts | 5 +- .../invoices/[invoiceId]/view/+page.ts | 5 +- .../settings/+page.ts | 6 +- .../usage/[[invoice]]/+page.ts | 6 +- .../(console)/project-[project]/+layout.ts | 2 +- .../settings/usage/[[invoice]]/+page.ts | 4 +- .../storage/bucket-[bucket]/settings/+page.ts | 4 +- 20 files changed, 52 insertions(+), 166 deletions(-) diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index 879b78b8a1..ae1bd6b347 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -29,31 +29,6 @@ export type EstimationDeleteOrganization = { unpaidInvoices: Models.Invoice[]; }; -export type Address = { - $id: string; - streetAddress: string; - addressLine2?: string; - country: string; - city: string; - state?: string; - postalCode: string; - userId: string; -}; - -export type AddressesList = { - billingAddresses: Address[]; - total: number; -}; - -export type AdditionalResource = { - currency: string; - invoiceDesc: string; - price: number; - unit: string; - value: number; - multiplier?: number; -}; - export class Billing { client: Client; @@ -144,14 +119,6 @@ export class Billing { }); } - async getOrganizationPlan(organizationId: string): Promise { - const path = `/organizations/${organizationId}/plan`; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call('get', uri, { - 'content-type': 'application/json' - }); - } - async getPlan(planId: string): Promise { const path = `/console/plans/${planId}`; const uri = new URL(this.client.config.endpoint + path); @@ -223,44 +190,6 @@ export class Billing { }); } - async listInvoices( - organizationId: string, - queries: string[] = [] - ): Promise { - const path = `/organizations/${organizationId}/invoices`; - const params = { - organizationId, - queries - }; - - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'get', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } - - async getInvoice(organizationId: string, invoiceId: string): Promise { - const path = `/organizations/${organizationId}/invoices/${invoiceId}`; - const params = { - organizationId, - invoiceId - }; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'get', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } - async getInvoiceView( organizationId: string, invoiceId: string @@ -385,22 +314,6 @@ export class Billing { ); } - async removeBillingAddress(organizationId: string): Promise { - const path = `/organizations/${organizationId}/billing-address`; - const params = { - organizationId - }; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'DELETE', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } - async updateTaxId(organizationId: string, taxId: string): Promise { const path = `/organizations/${organizationId}/taxId`; const params = { @@ -438,26 +351,6 @@ export class Billing { ); } - async getOrganizationBillingAddress( - organizationId: string, - billingAddressId: string - ): Promise
{ - const path = `/organizations/${organizationId}/billing-addresses/${billingAddressId}`; - const params = { - organizationId, - billingAddressId - }; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'GET', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } - async setPaymentMethod( paymentMethodId: string, providerMethodId: string | PaymentMethod, @@ -515,18 +408,4 @@ export class Billing { params ); } - - async getPlansInfo(): Promise { - const path = `/console/plans`; - const params = {}; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'GET', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } } diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 73e6df0f28..f2f424793e 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -12,7 +12,6 @@ import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants'; import { cachedStore } from '$lib/helpers/cache'; import { sizeToBytes, type Size } from '$lib/helpers/sizeConvertion'; -import type { AddressesList } from '$lib/sdk/billing'; import { isCloud } from '$lib/system'; import { activeHeaderAlert, orgMissingPaymentMethod } from '$routes/(console)/store'; import type { Models } from '@appwrite.io/console'; @@ -55,7 +54,10 @@ export const paymentMethods = derived( page, ($page) => $page.data.paymentMethods as Models.PaymentMethodList ); -export const addressList = derived(page, ($page) => $page.data.addressList as AddressesList); +export const addressList = derived( + page, + ($page) => $page.data.addressList as Models.BillingAddressList +); export const plansInfo = derived( page, ($page) => $page.data.plansInfo as Map @@ -155,7 +157,7 @@ export const failedInvoice = cachedStore< load: async (orgId) => { if (!isCloud) set(null); if (!get(canSeeBilling)) set(null); - const failedInvoices = await sdk.forConsole.billing.listInvoices(orgId, [ + const failedInvoices = await sdk.forConsole.organizations.listInvoices(orgId, [ Query.equal('status', 'failed') ]); // const failedInvoices = invoices.invoices; @@ -340,7 +342,7 @@ export async function checkForUsageLimit(org: Organization) { export async function checkPaymentAuthorizationRequired(org: Organization) { if (org.billingPlan === BillingPlan.FREE) return; - const invoices = await sdk.forConsole.billing.listInvoices(org.$id, [ + const invoices = await sdk.forConsole.organizations.listInvoices(org.$id, [ Query.equal('status', 'requires_authentication') ]); diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index 8cd3524df8..0187bc11c0 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -24,7 +24,7 @@ export const load: LayoutLoad = async ({ fetch, depends, parent }) => { let plansInfo = new Map(); if (isCloud) { - const plansArray = await sdk.forConsole.billing.getPlansInfo(); + const plansArray = await sdk.forConsole.console.plans(); plansInfo = plansArray.plans.reduce((map, plan) => { map.set(plan.$id as Tier, plan); return map; diff --git a/src/routes/(console)/account/payments/billingAddress.svelte b/src/routes/(console)/account/payments/billingAddress.svelte index ef51b3d5d1..906f3cbcc5 100644 --- a/src/routes/(console)/account/payments/billingAddress.svelte +++ b/src/routes/(console)/account/payments/billingAddress.svelte @@ -1,5 +1,7 @@