From 2a058be69ee0a86a2867ca5ecf4d6b18345c4241 Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Tue, 2 Sep 2025 19:38:40 +0530
Subject: [PATCH 01/82] fix org projects pagination and add archived pagination
---
src/lib/components/archiveProject.svelte | 20 ++++++++----
.../organization-[organization]/+page.svelte | 13 +++++---
.../organization-[organization]/+page.ts | 31 +++++++++++++++++--
3 files changed, 52 insertions(+), 12 deletions(-)
diff --git a/src/lib/components/archiveProject.svelte b/src/lib/components/archiveProject.svelte
index 3ebb938c1..e2858acbc 100644
--- a/src/lib/components/archiveProject.svelte
+++ b/src/lib/components/archiveProject.svelte
@@ -1,6 +1,6 @@
@@ -160,8 +160,14 @@
-
- {#each projectsToArchive as project}
+
+ {#snippet children(items)}
+
+ {#each items as project}
{@const platforms = filterPlatforms(
project.platforms.map((platform) => getPlatformInfo(platform.type))
)}
@@ -255,8 +261,10 @@
{/if}
- {/each}
-
+ {/each}
+
+ {/snippet}
+
diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte
index 27af7cbff..7e958cc69 100644
--- a/src/routes/(console)/organization-[organization]/+page.svelte
+++ b/src/routes/(console)/organization-[organization]/+page.svelte
@@ -104,9 +104,14 @@
return project.status !== 'active';
}
- $: projectsToArchive = data.projects.projects.filter((project) => project.status !== 'active');
+ $: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
+ (project) => project.status !== 'active'
+ );
- $: activeProjects = data.projects.projects.filter((project) => project.status === 'active');
+ $: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
+ (project) => project.status === 'active'
+ );
+ $: activeTotalOverall = data?.activeTotalOverall ?? data?.organization?.projects?.length ?? data?.projects?.total ?? 0;
function clearSearch() {
searchQuery?.clearInput();
}
@@ -157,7 +162,7 @@
{#if activeProjects.length > 0}
{#each activeProjects as project}
@@ -242,7 +247,7 @@
name="Projects"
limit={data.limit}
offset={data.offset}
- total={data.projects.total} />
+ total={activeTotalOverall} />
search: search || undefined
});
+ let allProjects: typeof projects.projects = [];
+ let fetchedCount = 0;
+ const total = projects.total;
+
+ while (fetchedCount < total) {
+ const next = await sdk.forConsole.projects.list({
+ queries: [
+ Query.offset(fetchedCount),
+ Query.equal('teamId', params.organization),
+ Query.limit(limit),
+ Query.orderDesc('')
+ ],
+ search: search || undefined
+ });
+ allProjects = allProjects.concat(next.projects);
+ fetchedCount += next.projects.length;
+ if (next.projects.length === 0) break;
+ }
+
+ const allActiveProjects = allProjects.filter((p) => p.status === 'active');
+ const allArchivedProjects = allProjects.filter((p) => p.status !== 'active');
+
+ const activeProjectsForPage = allActiveProjects.slice(offset, offset + limit);
+
// set `default` if no region!
- for (const project of projects.projects) {
+ for (const project of allProjects) {
project.region ??= 'default';
}
return {
offset,
limit,
- projects,
+ projects: { ...projects, projects: allProjects, total: allActiveProjects.length },
+ activeProjectsPage: activeProjectsForPage,
+ archivedProjectsPage: allArchivedProjects,
+ activeTotalOverall: allActiveProjects.length,
search
};
};
From 82cc38d49cbe84d63dab7f4d3b0de218affa4ec2 Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Tue, 2 Sep 2025 19:43:04 +0530
Subject: [PATCH 02/82] lint issue
---
src/lib/components/archiveProject.svelte | 190 +++++++++---------
.../organization-[organization]/+page.svelte | 6 +-
.../organization-[organization]/+page.ts | 2 +-
3 files changed, 106 insertions(+), 92 deletions(-)
diff --git a/src/lib/components/archiveProject.svelte b/src/lib/components/archiveProject.svelte
index e2858acbc..0fdd91c33 100644
--- a/src/lib/components/archiveProject.svelte
+++ b/src/lib/components/archiveProject.svelte
@@ -168,99 +168,109 @@
{#snippet children(items)}
{#each items as project}
- {@const platforms = filterPlatforms(
- project.platforms.map((platform) => getPlatformInfo(platform.type))
- )}
- {@const formatted = formatName(project.name)}
-
-
- {project?.platforms?.length ? project?.platforms?.length : 'No'} apps
-
- {formatted}
-
-
-
- {
- e.preventDefault();
- e.stopPropagation();
- readOnlyInfoOpen = {
- ...readOnlyInfoOpen,
- [project.$id]: !readOnlyInfoOpen[project.$id]
- };
- }}>
-
- Read only
-
-
-
-
- Archived projects are read-only. You can view
- and migrate their data, but they no longer
- accept edits or requests.
-
-
-
-
-
-
-
- handleUnarchiveProject(project)}
- >Unarchive project
- handleMigrateProject(project)}
- >Migrate project
-
-
-
-
+ {@const platforms = filterPlatforms(
+ project.platforms.map((platform) =>
+ getPlatformInfo(platform.type)
+ )
+ )}
+ {@const formatted = formatName(project.name)}
+
+
+ {project?.platforms?.length
+ ? project?.platforms?.length
+ : 'No'} apps
+
+ {formatted}
+
+
+
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ readOnlyInfoOpen = {
+ ...readOnlyInfoOpen,
+ [project.$id]:
+ !readOnlyInfoOpen[project.$id]
+ };
+ }}>
+
+ Read only
+
+
+
+
+ Archived projects are read-only. You can
+ view and migrate their data, but they no
+ longer accept edits or requests.
+
+
+
+
+
+
+
+
+ handleUnarchiveProject(project)}
+ >Unarchive project
+
+ handleMigrateProject(project)}
+ >Migrate project
+
+
+
+
- {#each platforms.slice(0, 2) as platform}
- {@const icon = getIconForPlatform(platform.icon)}
-
-
-
- {/each}
+ {#each platforms.slice(0, 2) as platform}
+ {@const icon = getIconForPlatform(platform.icon)}
+
+
+
+ {/each}
- {#if platforms.length > 3}
-
- {/if}
+ {#if platforms.length > 3}
+
+ {/if}
-
- {#if isCloud && $regionsStore?.regions}
- {@const region = findRegion(project)}
- {region?.name}
- {/if}
-
-
+
+ {#if isCloud && $regionsStore?.regions}
+ {@const region = findRegion(project)}
+ {region?.name}
+ {/if}
+
+
{/each}
{/snippet}
diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte
index 7e958cc69..683f0be6a 100644
--- a/src/routes/(console)/organization-[organization]/+page.svelte
+++ b/src/routes/(console)/organization-[organization]/+page.svelte
@@ -111,7 +111,11 @@
$: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
(project) => project.status === 'active'
);
- $: activeTotalOverall = data?.activeTotalOverall ?? data?.organization?.projects?.length ?? data?.projects?.total ?? 0;
+ $: activeTotalOverall =
+ data?.activeTotalOverall ??
+ data?.organization?.projects?.length ??
+ data?.projects?.total ??
+ 0;
function clearSearch() {
searchQuery?.clearInput();
}
diff --git a/src/routes/(console)/organization-[organization]/+page.ts b/src/routes/(console)/organization-[organization]/+page.ts
index b640aaba4..e7d97ed24 100644
--- a/src/routes/(console)/organization-[organization]/+page.ts
+++ b/src/routes/(console)/organization-[organization]/+page.ts
@@ -49,7 +49,7 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
const allActiveProjects = allProjects.filter((p) => p.status === 'active');
const allArchivedProjects = allProjects.filter((p) => p.status !== 'active');
-
+
const activeProjectsForPage = allActiveProjects.slice(offset, offset + limit);
// set `default` if no region!
From 11e460c89bbb67760e11512d7ee65f830cf83d2b Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Wed, 3 Sep 2025 11:53:54 +0530
Subject: [PATCH 03/82] fix: correct off-by-one error in platform badge
conditional rendering
---
src/lib/components/archiveProject.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/lib/components/archiveProject.svelte b/src/lib/components/archiveProject.svelte
index 0fdd91c33..c2b514d16 100644
--- a/src/lib/components/archiveProject.svelte
+++ b/src/lib/components/archiveProject.svelte
@@ -257,7 +257,7 @@
{/each}
- {#if platforms.length > 3}
+ {#if platforms.length > 2}
Date: Tue, 9 Sep 2025 12:33:56 +0530
Subject: [PATCH 04/82] feat(org-projects): add separate pagination for
archived projects and optimize queries
---
src/lib/components/archiveProject.svelte | 236 +++++++++---------
src/lib/components/archivedLimit.svelte | 44 ++++
src/lib/components/archivedPagination.svelte | 23 ++
.../archivedPaginationWithLimit.svelte | 31 +++
.../organization-[organization]/+page.svelte | 5 +-
.../organization-[organization]/+page.ts | 71 ++++--
6 files changed, 266 insertions(+), 144 deletions(-)
create mode 100644 src/lib/components/archivedLimit.svelte
create mode 100644 src/lib/components/archivedPagination.svelte
create mode 100644 src/lib/components/archivedPaginationWithLimit.svelte
diff --git a/src/lib/components/archiveProject.svelte b/src/lib/components/archiveProject.svelte
index c2b514d16..a2b096073 100644
--- a/src/lib/components/archiveProject.svelte
+++ b/src/lib/components/archiveProject.svelte
@@ -1,6 +1,7 @@
+
+
+
+
+ {name} per page. Total: {sum >= 5000 ? `${sum}+` : sum}
+
+
diff --git a/src/lib/components/archivedPagination.svelte b/src/lib/components/archivedPagination.svelte
new file mode 100644
index 000000000..ae3a71404
--- /dev/null
+++ b/src/lib/components/archivedPagination.svelte
@@ -0,0 +1,23 @@
+
+
+
diff --git a/src/lib/components/archivedPaginationWithLimit.svelte b/src/lib/components/archivedPaginationWithLimit.svelte
new file mode 100644
index 000000000..7b5d694c0
--- /dev/null
+++ b/src/lib/components/archivedPaginationWithLimit.svelte
@@ -0,0 +1,31 @@
+
+
+
+ {#if showLimit}
+
+ {/if}
+
+
+
diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte
index 683f0be6a..df5dc380c 100644
--- a/src/routes/(console)/organization-[organization]/+page.svelte
+++ b/src/routes/(console)/organization-[organization]/+page.svelte
@@ -257,7 +257,10 @@
+ currentPlan={$currentPlan}
+ archivedTotalOverall={data.archivedTotalOverall}
+ archivedOffset={data.archivedOffset}
+ limit={data.limit} />
diff --git a/src/routes/(console)/organization-[organization]/+page.ts b/src/routes/(console)/organization-[organization]/+page.ts
index e7d97ed24..bf1e20408 100644
--- a/src/routes/(console)/organization-[organization]/+page.ts
+++ b/src/routes/(console)/organization-[organization]/+page.ts
@@ -18,52 +18,69 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
const offset = pageToOffset(page, limit);
const search = getSearch(url);
- const projects = await sdk.forConsole.projects.list({
+ const archivedPage = parseInt(url.searchParams.get('archivedPage') || '1');
+ const archivedOffset = pageToOffset(archivedPage, limit);
+
+ // fetch active projects with offset set
+ const activeProjects = await sdk.forConsole.projects.list({
queries: [
Query.offset(offset),
Query.equal('teamId', params.organization),
+ Query.or([Query.equal('status', 'active'), Query.isNull('status')]),
Query.limit(limit),
Query.orderDesc('')
],
search: search || undefined
});
- let allProjects: typeof projects.projects = [];
- let fetchedCount = 0;
- const total = projects.total;
+ // Fetch archived projects with separate pagination
+ const archivedProjects = await sdk.forConsole.projects.list({
+ queries: [
+ Query.offset(archivedOffset),
+ Query.equal('teamId', params.organization),
+ Query.equal('status', 'archived'),
+ Query.limit(limit),
+ Query.orderDesc('')
+ ],
+ search: search || undefined
+ });
- while (fetchedCount < total) {
- const next = await sdk.forConsole.projects.list({
- queries: [
- Query.offset(fetchedCount),
- Query.equal('teamId', params.organization),
- Query.limit(limit),
- Query.orderDesc('')
- ],
- search: search || undefined
- });
- allProjects = allProjects.concat(next.projects);
- fetchedCount += next.projects.length;
- if (next.projects.length === 0) break;
- }
+ // get total counts
+ const activeTotal = await sdk.forConsole.projects.list({
+ queries: [
+ Query.equal('teamId', params.organization),
+ Query.or([Query.equal('status', 'active'), Query.isNull('status')])
+ ],
+ search: search || undefined
+ });
- const allActiveProjects = allProjects.filter((p) => p.status === 'active');
- const allArchivedProjects = allProjects.filter((p) => p.status !== 'active');
-
- const activeProjectsForPage = allActiveProjects.slice(offset, offset + limit);
+ const archivedTotal = await sdk.forConsole.projects.list({
+ queries: [Query.equal('teamId', params.organization), Query.equal('status', 'archived')],
+ search: search || undefined
+ });
// set `default` if no region!
- for (const project of allProjects) {
+ for (const project of activeProjects.projects) {
+ project.region ??= 'default';
+ }
+ for (const project of archivedProjects.projects) {
project.region ??= 'default';
}
return {
offset,
limit,
- projects: { ...projects, projects: allProjects, total: allActiveProjects.length },
- activeProjectsPage: activeProjectsForPage,
- archivedProjectsPage: allArchivedProjects,
- activeTotalOverall: allActiveProjects.length,
+ projects: {
+ ...activeProjects,
+ projects: activeProjects.projects,
+ total: activeTotal.total
+ },
+ activeProjectsPage: activeProjects.projects,
+ archivedProjectsPage: archivedProjects.projects,
+ activeTotalOverall: activeTotal.total,
+ archivedTotalOverall: archivedTotal.total,
+ archivedOffset,
+ archivedPage,
search
};
};
From 655b053c37011afde900e07dc751ea176360157d Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Tue, 9 Sep 2025 12:41:26 +0530
Subject: [PATCH 05/82] fix: lint issues
---
.../organization-[organization]/+page.svelte | 21 +++++++------------
1 file changed, 8 insertions(+), 13 deletions(-)
diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte
index 31c8e492a..46b8f2769 100644
--- a/src/routes/(console)/organization-[organization]/+page.svelte
+++ b/src/routes/(console)/organization-[organization]/+page.svelte
@@ -109,24 +109,19 @@
return project.status === 'archived';
}
-$: projectsToArchive =
- (data.archivedProjectsPage ?? data.projects.projects).filter(
- (project) =>
- isCloud
- ? project.status === 'archived'
- : project.status !== 'active' // fallback for non-cloud
+ $: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
+ (project) => (isCloud ? project.status === 'archived' : project.status !== 'active') // fallback for non-cloud
);
-$: activeProjects =
- (data.activeProjectsPage ?? data.projects.projects).filter(
+ $: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
(project) => project.status === 'active'
);
-$: activeTotalOverall =
- data?.activeTotalOverall ??
- data?.organization?.projects?.length ??
- data?.projects?.total ??
- 0;
+ $: activeTotalOverall =
+ data?.activeTotalOverall ??
+ data?.organization?.projects?.length ??
+ data?.projects?.total ??
+ 0;
function clearSearch() {
searchQuery?.clearInput();
}
From 4f4c679e7cef1e750c95ed77e8e095f431b3156f Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Tue, 9 Sep 2025 17:46:31 +0530
Subject: [PATCH 06/82] fixes
---
src/lib/components/archiveProject.svelte | 19 ++---
src/lib/components/archivedLimit.svelte | 44 -----------
src/lib/components/archivedPagination.svelte | 23 ------
.../archivedPaginationWithLimit.svelte | 31 --------
src/lib/components/limit.svelte | 13 +++-
src/lib/components/pagination.svelte | 10 ++-
src/lib/components/paginationWithLimit.svelte | 21 +++++-
src/lib/components/paginator.svelte | 6 +-
.../organization-[organization]/+page.svelte | 2 +-
.../organization-[organization]/+page.ts | 73 +++++++++----------
10 files changed, 86 insertions(+), 156 deletions(-)
delete mode 100644 src/lib/components/archivedLimit.svelte
delete mode 100644 src/lib/components/archivedPagination.svelte
delete mode 100644 src/lib/components/archivedPaginationWithLimit.svelte
diff --git a/src/lib/components/archiveProject.svelte b/src/lib/components/archiveProject.svelte
index 2cfa7c606..d7c5b6a1b 100644
--- a/src/lib/components/archiveProject.svelte
+++ b/src/lib/components/archiveProject.svelte
@@ -1,5 +1,5 @@
-
-
-
-
- {name} per page. Total: {sum >= 5000 ? `${sum}+` : sum}
-
-
diff --git a/src/lib/components/archivedPagination.svelte b/src/lib/components/archivedPagination.svelte
deleted file mode 100644
index ae3a71404..000000000
--- a/src/lib/components/archivedPagination.svelte
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
diff --git a/src/lib/components/archivedPaginationWithLimit.svelte b/src/lib/components/archivedPaginationWithLimit.svelte
deleted file mode 100644
index 7b5d694c0..000000000
--- a/src/lib/components/archivedPaginationWithLimit.svelte
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- {#if showLimit}
-
- {/if}
-
-
-
diff --git a/src/lib/components/limit.svelte b/src/lib/components/limit.svelte
index c8e72a401..8a7c1ef67 100644
--- a/src/lib/components/limit.svelte
+++ b/src/lib/components/limit.svelte
@@ -8,6 +8,8 @@
export let sum: number;
export let limit: number;
export let name: string;
+ export let pageParam: string = 'page';
+ export let removeOnFirstPage: boolean = false;
const options = [
{ label: '6', value: 6 },
@@ -23,10 +25,15 @@
url.searchParams.set('limit', limit.toString());
await preferences.setLimit(limit);
- if (url.searchParams.has('page')) {
- const page = Number(url.searchParams.get('page'));
+ if (url.searchParams.has(pageParam)) {
+ const page = Number(url.searchParams.get(pageParam));
const newPage = Math.floor(((page - 1) * previousLimit) / limit);
- url.searchParams.set('page', newPage.toString());
+ const safePage = Math.max(1, Number.isFinite(newPage) ? newPage : 1);
+ if (removeOnFirstPage && safePage === 1) {
+ url.searchParams.delete(pageParam);
+ } else {
+ url.searchParams.set(pageParam, safePage.toString());
+ }
}
await goto(url.toString());
diff --git a/src/lib/components/pagination.svelte b/src/lib/components/pagination.svelte
index f272f7f66..975e4cf6e 100644
--- a/src/lib/components/pagination.svelte
+++ b/src/lib/components/pagination.svelte
@@ -6,15 +6,21 @@
export let limit: number;
export let offset: number;
export let useCreateLink = true;
+ export let pageParam: string = 'page';
+ export let removeOnFirstPage: boolean = false;
$: currentPage = Math.floor(offset / limit + 1);
function getLink(page: number): string {
const url = new URL(pageStore.url);
if (page === 1) {
- url.searchParams.delete('page');
+ if (removeOnFirstPage) {
+ url.searchParams.delete(pageParam);
+ } else {
+ url.searchParams.set(pageParam, '1');
+ }
} else {
- url.searchParams.set('page', page.toString());
+ url.searchParams.set(pageParam, page.toString());
}
return url.toString();
diff --git a/src/lib/components/paginationWithLimit.svelte b/src/lib/components/paginationWithLimit.svelte
index 7cf78f6b5..7522f3521 100644
--- a/src/lib/components/paginationWithLimit.svelte
+++ b/src/lib/components/paginationWithLimit.svelte
@@ -8,13 +8,19 @@
offset,
total,
name,
- useCreateLink = true
+ useCreateLink = true,
+ pageParam = 'page',
+ removeOnFirstPage = false,
+ ...restProps
}: {
limit: number;
offset: number;
total: number;
name: string;
useCreateLink?: boolean;
+ pageParam?: string;
+ removeOnFirstPage?: boolean;
+ [key: string]: any;
} = $props();
const showLimit = $derived(!!useCreateLink);
@@ -22,10 +28,17 @@
const alignItems = $derived(showLimit ? 'center' : 'flex-end');
-
+
{#if showLimit}
-
+
{/if}
-
+
diff --git a/src/lib/components/paginator.svelte b/src/lib/components/paginator.svelte
index ae8aaf9c4..8a1b2b6de 100644
--- a/src/lib/components/paginator.svelte
+++ b/src/lib/components/paginator.svelte
@@ -13,7 +13,8 @@
name = 'items',
gap = 's',
offset = $bindable(0),
- children
+ children,
+ ...restProps
}: {
items: T[];
limit?: number;
@@ -26,6 +27,7 @@
| undefined;
offset?: number;
children: Snippet<[T[], number]>;
+ [key: string]: any;
} = $props();
let total = $derived(items.length);
@@ -33,7 +35,7 @@
let paginatedItems = $derived(items.slice(offset, offset + limit));
-
+
{@render children(paginatedItems, limit)}
{#if !hideFooter}
diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte
index 46b8f2769..9fb546837 100644
--- a/src/routes/(console)/organization-[organization]/+page.svelte
+++ b/src/routes/(console)/organization-[organization]/+page.svelte
@@ -110,7 +110,7 @@
}
$: projectsToArchive = (data.archivedProjectsPage ?? data.projects.projects).filter(
- (project) => (isCloud ? project.status === 'archived' : project.status !== 'active') // fallback for non-cloud
+ (project) => project.status === 'archived'
);
$: activeProjects = (data.activeProjectsPage ?? data.projects.projects).filter(
diff --git a/src/routes/(console)/organization-[organization]/+page.ts b/src/routes/(console)/organization-[organization]/+page.ts
index bf1e20408..ce972e3ba 100644
--- a/src/routes/(console)/organization-[organization]/+page.ts
+++ b/src/routes/(console)/organization-[organization]/+page.ts
@@ -21,43 +21,42 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
const archivedPage = parseInt(url.searchParams.get('archivedPage') || '1');
const archivedOffset = pageToOffset(archivedPage, limit);
- // fetch active projects with offset set
- const activeProjects = await sdk.forConsole.projects.list({
- queries: [
- Query.offset(offset),
- Query.equal('teamId', params.organization),
- Query.or([Query.equal('status', 'active'), Query.isNull('status')]),
- Query.limit(limit),
- Query.orderDesc('')
- ],
- search: search || undefined
- });
-
- // Fetch archived projects with separate pagination
- const archivedProjects = await sdk.forConsole.projects.list({
- queries: [
- Query.offset(archivedOffset),
- Query.equal('teamId', params.organization),
- Query.equal('status', 'archived'),
- Query.limit(limit),
- Query.orderDesc('')
- ],
- search: search || undefined
- });
-
- // get total counts
- const activeTotal = await sdk.forConsole.projects.list({
- queries: [
- Query.equal('teamId', params.organization),
- Query.or([Query.equal('status', 'active'), Query.isNull('status')])
- ],
- search: search || undefined
- });
-
- const archivedTotal = await sdk.forConsole.projects.list({
- queries: [Query.equal('teamId', params.organization), Query.equal('status', 'archived')],
- search: search || undefined
- });
+ const [activeProjects, archivedProjects, activeTotal, archivedTotal] = await Promise.all([
+ sdk.forConsole.projects.list({
+ queries: [
+ Query.offset(offset),
+ Query.equal('teamId', params.organization),
+ Query.or([Query.equal('status', 'active'), Query.isNull('status')]),
+ Query.limit(limit),
+ Query.orderDesc('')
+ ],
+ search: search || undefined
+ }),
+ sdk.forConsole.projects.list({
+ queries: [
+ Query.offset(archivedOffset),
+ Query.equal('teamId', params.organization),
+ Query.equal('status', 'archived'),
+ Query.limit(limit),
+ Query.orderDesc('')
+ ],
+ search: search || undefined
+ }),
+ sdk.forConsole.projects.list({
+ queries: [
+ Query.equal('teamId', params.organization),
+ Query.or([Query.equal('status', 'active'), Query.isNull('status')])
+ ],
+ search: search || undefined
+ }),
+ sdk.forConsole.projects.list({
+ queries: [
+ Query.equal('teamId', params.organization),
+ Query.equal('status', 'archived')
+ ],
+ search: search || undefined
+ })
+ ]);
// set `default` if no region!
for (const project of activeProjects.projects) {
From 7287571c505491f169437a2db3a0fe1f50c1f364 Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Tue, 9 Sep 2025 17:55:32 +0530
Subject: [PATCH 07/82] lint issue
---
src/lib/components/paginationWithLimit.svelte | 2 +-
src/lib/components/paginator.svelte | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/lib/components/paginationWithLimit.svelte b/src/lib/components/paginationWithLimit.svelte
index 7522f3521..b48fbc0f6 100644
--- a/src/lib/components/paginationWithLimit.svelte
+++ b/src/lib/components/paginationWithLimit.svelte
@@ -20,7 +20,7 @@
useCreateLink?: boolean;
pageParam?: string;
removeOnFirstPage?: boolean;
- [key: string]: any;
+ [key: string]: unknown;
} = $props();
const showLimit = $derived(!!useCreateLink);
diff --git a/src/lib/components/paginator.svelte b/src/lib/components/paginator.svelte
index 8a1b2b6de..053e2ea14 100644
--- a/src/lib/components/paginator.svelte
+++ b/src/lib/components/paginator.svelte
@@ -27,7 +27,7 @@
| undefined;
offset?: number;
children: Snippet<[T[], number]>;
- [key: string]: any;
+ [key: string]: unknown;
} = $props();
let total = $derived(items.length);
From 6ad7ee10c51d556fd563e5f253ca0b4f5eb10f29 Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Mon, 15 Sep 2025 14:00:39 +0530
Subject: [PATCH 08/82] Update
src/routes/(console)/organization-[organization]/+page.ts
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
src/routes/(console)/organization-[organization]/+page.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/routes/(console)/organization-[organization]/+page.ts b/src/routes/(console)/organization-[organization]/+page.ts
index ce972e3ba..047a2b965 100644
--- a/src/routes/(console)/organization-[organization]/+page.ts
+++ b/src/routes/(console)/organization-[organization]/+page.ts
@@ -18,9 +18,9 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
const offset = pageToOffset(page, limit);
const search = getSearch(url);
- const archivedPage = parseInt(url.searchParams.get('archivedPage') || '1');
+ const archivedPageRaw = parseInt(url.searchParams.get('archivedPage') || '1', 10);
+ const archivedPage = Number.isFinite(archivedPageRaw) && archivedPageRaw > 0 ? archivedPageRaw : 1;
const archivedOffset = pageToOffset(archivedPage, limit);
-
const [activeProjects, archivedProjects, activeTotal, archivedTotal] = await Promise.all([
sdk.forConsole.projects.list({
queries: [
From c8bbb979fc46be1fe017de0538ec004bcb3b9025 Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Mon, 15 Sep 2025 14:06:10 +0530
Subject: [PATCH 09/82] lint issue
---
src/routes/(console)/organization-[organization]/+page.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/routes/(console)/organization-[organization]/+page.ts b/src/routes/(console)/organization-[organization]/+page.ts
index 047a2b965..f4a520b5c 100644
--- a/src/routes/(console)/organization-[organization]/+page.ts
+++ b/src/routes/(console)/organization-[organization]/+page.ts
@@ -19,7 +19,8 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
const search = getSearch(url);
const archivedPageRaw = parseInt(url.searchParams.get('archivedPage') || '1', 10);
- const archivedPage = Number.isFinite(archivedPageRaw) && archivedPageRaw > 0 ? archivedPageRaw : 1;
+ const archivedPage =
+ Number.isFinite(archivedPageRaw) && archivedPageRaw > 0 ? archivedPageRaw : 1;
const archivedOffset = pageToOffset(archivedPage, limit);
const [activeProjects, archivedProjects, activeTotal, archivedTotal] = await Promise.all([
sdk.forConsole.projects.list({
From c7cd36fd42bb10d4b5c9eeca1c723698b602ada1 Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Thu, 30 Oct 2025 10:41:58 +0530
Subject: [PATCH 10/82] feaT: move github button above email-pass signup and
signin
---
.../(public)/(guest)/login/+page.svelte | 14 +++++------
.../(public)/(guest)/register/+page.svelte | 24 +++++++++----------
2 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/src/routes/(public)/(guest)/login/+page.svelte b/src/routes/(public)/(guest)/login/+page.svelte
index e9f8f25b4..0127b8a2f 100644
--- a/src/routes/(public)/(guest)/login/+page.svelte
+++ b/src/routes/(public)/(guest)/login/+page.svelte
@@ -94,6 +94,13 @@
diff --git a/src/routes/(public)/(guest)/register/+page.svelte b/src/routes/(public)/(guest)/register/+page.svelte
index aafbbd2b6..ca7280d05 100644
--- a/src/routes/(public)/(guest)/register/+page.svelte
+++ b/src/routes/(public)/(guest)/register/+page.svelte
@@ -124,6 +124,18 @@
From 625abac21126b57901ad3ab0dfe5f04304c6208c Mon Sep 17 00:00:00 2001
From: Harsh Mahajan
Date: Mon, 10 Nov 2025 18:16:40 +0000
Subject: [PATCH 11/82] added space below gitbutton
---
src/routes/(public)/(guest)/login/+page.svelte | 13 ++++++++++++-
src/routes/(public)/(guest)/register/+page.svelte | 7 +++++++
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/routes/(public)/(guest)/login/+page.svelte b/src/routes/(public)/(guest)/login/+page.svelte
index 0127b8a2f..396b60a18 100644
--- a/src/routes/(public)/(guest)/login/+page.svelte
+++ b/src/routes/(public)/(guest)/login/+page.svelte
@@ -95,7 +95,12 @@
-
+
+
-
- {log.event}
-
+
+ {log.event}
+
-
- {#if log.countryCode !== '--'}
- {log.countryName}
- {:else}
- Unknown
- {/if}
-
+
+ {#if log.countryCode !== '--'}
+ {log.countryName}
+ {:else}
+ Unknown
+ {/if}
+
-
-
-
+
+
+
-
-
-
-
-{/each}
-
-
+
+
+
+
+ {/each}
+
Date: Fri, 2 Jan 2026 14:24:37 +0530
Subject: [PATCH 40/82] add: comment.
---
src/lib/components/multiSelectTable.svelte | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte
index bb985f790..78e6b7388 100644
--- a/src/lib/components/multiSelectTable.svelte
+++ b/src/lib/components/multiSelectTable.svelte
@@ -50,6 +50,10 @@
deleteContentNotice?: Snippet;
onDelete?: (
batchDelete: DeleteOperation,
+ /**
+ * this is useful if you have a custom deletion logic
+ * and don't want to use the batchDelete helper provided!
+ */
selectedRows: string[]
) => Promise | DeleteOperationState;
onCancel?: () => Promise | void;
From 7b74d4210c10e4ca5adb0f995f916991587554c5 Mon Sep 17 00:00:00 2001
From: Darshan
Date: Fri, 2 Jan 2026 14:25:21 +0530
Subject: [PATCH 41/82] add: comment.
---
src/lib/components/multiSelectTable.svelte | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte
index 78e6b7388..79197f2f7 100644
--- a/src/lib/components/multiSelectTable.svelte
+++ b/src/lib/components/multiSelectTable.svelte
@@ -51,8 +51,8 @@
onDelete?: (
batchDelete: DeleteOperation,
/**
- * this is useful if you have a custom deletion logic
- * and don't want to use the batchDelete helper provided!
+ * this is useful when you have a custom deletion logic
+ * and the default `batchDelete` helper doesn't fit the use-case!
*/
selectedRows: string[]
) => Promise | DeleteOperationState;
From 0c65c0d79060215e899a4d656bd035a651b51769 Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Fri, 2 Jan 2026 15:02:07 +0530
Subject: [PATCH 42/82] added limitchanged
---
.../auth/security/passwordPolicies.svelte | 12 ++++++++----
.../auth/security/sessionSecurity.svelte | 14 ++++++++------
2 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/passwordPolicies.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/passwordPolicies.svelte
index 004e772ee..22898b060 100644
--- a/src/routes/(console)/project-[region]-[project]/auth/security/passwordPolicies.svelte
+++ b/src/routes/(console)/project-[region]-[project]/auth/security/passwordPolicies.svelte
@@ -43,11 +43,15 @@
});
const hasChanges = $derived.by(() => {
- const dictChanged = passwordDictionary !== project.authPasswordDictionary;
- const dataCheckChanged = authPersonalDataCheck !== project.authPersonalDataCheck;
- const historyChanged = passwordHistoryEnabled !== (project.authPasswordHistory !== 0);
+ const dictChanged = passwordDictionary !== (project.authPasswordDictionary ?? false);
+ const dataCheckChanged = authPersonalDataCheck !== (project.authPersonalDataCheck ?? false);
+ const historyChanged =
+ passwordHistoryEnabled !== ((project.authPasswordHistory ?? 0) !== 0);
+ const limitChanged =
+ passwordHistoryEnabled &&
+ Number(passwordHistory) !== (project.authPasswordHistory ?? 0);
- return historyChanged || dictChanged || dataCheckChanged;
+ return historyChanged || dictChanged || dataCheckChanged || limitChanged;
});
async function updatePasswordPolicies() {
diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/sessionSecurity.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/sessionSecurity.svelte
index f64fee94e..6babbbecc 100644
--- a/src/routes/(console)/project-[region]-[project]/auth/security/sessionSecurity.svelte
+++ b/src/routes/(console)/project-[region]-[project]/auth/security/sessionSecurity.svelte
@@ -8,22 +8,24 @@
import { sdk } from '$lib/stores/sdk';
import { Typography } from '@appwrite.io/pink-svelte';
import type { Models } from '@appwrite.io/console';
+ import { onMount } from 'svelte';
let { project }: { project: Models.Project } = $props();
let authSessionAlerts = $state(false);
let sessionInvalidation = $state(false);
- // Initialize state from project
- $effect(() => {
+ onMount(() => {
authSessionAlerts = project?.authSessionAlerts ?? false;
sessionInvalidation = project?.authInvalidateSessions ?? false;
});
- const hasChanges = $derived(
- authSessionAlerts !== (project?.authSessionAlerts ?? false) ||
- sessionInvalidation !== (project?.authInvalidateSessions ?? false)
- );
+ const hasChanges = $derived.by(() => {
+ const alertsChanged = authSessionAlerts !== (project?.authSessionAlerts ?? false);
+ const invalidationChanged =
+ sessionInvalidation !== (project?.authInvalidateSessions ?? false);
+ return alertsChanged || invalidationChanged;
+ });
async function updateSessionSecurity() {
try {
From 59900a4ca285534029101285993a91415995e196 Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Fri, 2 Jan 2026 17:21:42 +0530
Subject: [PATCH 43/82] addressed code rabbit
---
src/lib/components/limit.svelte | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/lib/components/limit.svelte b/src/lib/components/limit.svelte
index 8a7c1ef67..4221f7693 100644
--- a/src/lib/components/limit.svelte
+++ b/src/lib/components/limit.svelte
@@ -27,7 +27,9 @@
if (url.searchParams.has(pageParam)) {
const page = Number(url.searchParams.get(pageParam));
- const newPage = Math.floor(((page - 1) * previousLimit) / limit);
+ const prev =
+ Number.isFinite(previousLimit) && previousLimit > 0 ? previousLimit : limit;
+ const newPage = Math.floor(((page - 1) * prev) / limit) + 1;
const safePage = Math.max(1, Number.isFinite(newPage) ? newPage : 1);
if (removeOnFirstPage && safePage === 1) {
url.searchParams.delete(pageParam);
From f8a04a20520798d71a97b63364da2f6c0a6d37d8 Mon Sep 17 00:00:00 2001
From: Darshan
Date: Fri, 2 Jan 2026 18:02:52 +0530
Subject: [PATCH 44/82] address comment.
---
src/lib/components/multiSelectTable.svelte | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte
index 79197f2f7..ce0196e34 100644
--- a/src/lib/components/multiSelectTable.svelte
+++ b/src/lib/components/multiSelectTable.svelte
@@ -93,6 +93,11 @@
const deleted: string[] = [];
let firstError: Error | undefined;
+ // prevent infinite loop
+ if (batchSize !== undefined) {
+ batchSize = Math.max(1, Math.floor(Math.abs(batchSize)));
+ }
+
async function processBatch(batch: string[]) {
// build promises
const results = await Promise.allSettled(batch.map((id) => deleteFn(id)));
From 09884a935aae625d109afff4a837d1dbb709d0c7 Mon Sep 17 00:00:00 2001
From: Darshan
Date: Fri, 2 Jan 2026 19:18:44 +0530
Subject: [PATCH 45/82] fix: payments not updating.
---
.../account/payments/editPaymentModal.svelte | 46 +++++++++----------
.../organization-[organization]/+layout.ts | 15 ++----
.../billing/+page.svelte | 22 ++++-----
.../billing/+page.ts | 37 +++++++++++++--
.../billing/deleteOrgPayment.svelte | 13 +++---
.../billing/paymentMethods.svelte | 43 +++++++----------
.../billing/replaceCard.svelte | 2 +-
7 files changed, 92 insertions(+), 86 deletions(-)
diff --git a/src/routes/(console)/account/payments/editPaymentModal.svelte b/src/routes/(console)/account/payments/editPaymentModal.svelte
index 7850cdea9..fab629996 100644
--- a/src/routes/(console)/account/payments/editPaymentModal.svelte
+++ b/src/routes/(console)/account/payments/editPaymentModal.svelte
@@ -9,13 +9,27 @@
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Alert } from '@appwrite.io/pink-svelte';
- export let show = false;
- export let selectedPaymentMethod: PaymentMethodData;
- export let isLinked = false;
+ let {
+ show = $bindable(false),
+ isLinked = false,
+ selectedPaymentMethod,
+ }: {
+ show: boolean;
+ isLinked?: boolean;
+ selectedPaymentMethod: PaymentMethodData;
+ } = $props()
+
+ let year: number | null = $state(null);
+ let month: string | null = $state(null);
+ let error: string | null = $state(null);
+
const currentYear = new Date().getFullYear();
- let error: string;
- let month: string;
- let year: number;
+ const months = Array.from({ length: 12 }, (_, i) => {
+ const value = String(i + 1).padStart(2, '0');
+ return { value, label: value };
+ });
+
+ const options = $derived(createMonthOptions(year));
async function handleSubmit() {
try {
@@ -24,9 +38,9 @@
month,
year?.toString()
);
- trackEvent(Submit.PaymentMethodUpdate);
- invalidate(Dependencies.PAYMENT_METHODS);
show = false;
+ trackEvent(Submit.PaymentMethodUpdate);
+ await invalidate(Dependencies.PAYMENT_METHODS);
addNotification({
type: 'success',
message: 'Your payment method has been updated'
@@ -38,20 +52,6 @@
}
function createMonthOptions(year: number) {
- const months = [
- { value: '01', label: '01' },
- { value: '02', label: '02' },
- { value: '03', label: '03' },
- { value: '04', label: '04' },
- { value: '05', label: '05' },
- { value: '06', label: '06' },
- { value: '07', label: '07' },
- { value: '08', label: '08' },
- { value: '09', label: '09' },
- { value: '10', label: '10' },
- { value: '11', label: '11' },
- { value: '12', label: '12' }
- ];
if (!year) return months;
if (year === currentYear) {
const currentMonth = new Date().getMonth() + 1;
@@ -60,8 +60,6 @@
return months;
}
}
-
- $: options = createMonthOptions(year);
diff --git a/src/routes/(console)/organization-[organization]/+layout.ts b/src/routes/(console)/organization-[organization]/+layout.ts
index ef77274e8..aa1b03b8d 100644
--- a/src/routes/(console)/organization-[organization]/+layout.ts
+++ b/src/routes/(console)/organization-[organization]/+layout.ts
@@ -24,7 +24,7 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => {
depends(Dependencies.MEMBERS);
depends(Dependencies.PAYMENT_METHODS);
- const requestedOrg = await checkPlatformAndRedirect(params, organizations, prefs);
+ await checkPlatformAndRedirect(params, organizations, prefs);
let roles = isCloud ? [] : defaultRoles;
let scopes = isCloud ? [] : defaultScopes;
@@ -46,13 +46,8 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => {
sdk.forConsole.account.updatePrefs({ prefs: newPrefs });
}
- // fetch org only if we haven't already fetched it for platform check
- const orgPromise: Promise = requestedOrg
- ? Promise.resolve(requestedOrg)
- : (sdk.forConsole.teams.get({ teamId: params.organization }) as Promise);
-
const [org, members, countryList, locale] = await Promise.all([
- orgPromise,
+ sdk.forConsole.teams.get({ teamId: params.organization }) as Promise,
sdk.forConsole.teams.listMemberships({ teamId: params.organization }),
sdk.forConsole.locale.listCountries(),
sdk.forConsole.locale.get(),
@@ -96,7 +91,7 @@ async function checkPlatformAndRedirect(
params: { organization: string },
organizations: { teams: Array<{ $id: string; platform?: string }> },
prefs: Record
-): Promise {
+) {
// check if preloaded
let requestedOrg = organizations.teams.find((team) => team.$id === params.organization) as
| Organization
@@ -154,8 +149,4 @@ async function checkPlatformAndRedirect(
redirect(303, resolve('/(console)'));
}
}
-
- // send the org,
- // if already in the full list so we don't have to make another API request.
- return requestedOrg;
}
diff --git a/src/routes/(console)/organization-[organization]/billing/+page.svelte b/src/routes/(console)/organization-[organization]/billing/+page.svelte
index 99729b8e9..ab69b151b 100644
--- a/src/routes/(console)/organization-[organization]/billing/+page.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/+page.svelte
@@ -9,7 +9,6 @@
import PaymentHistory from './paymentHistory.svelte';
import TaxId from './taxId.svelte';
import { failedInvoice, tierToPlan, upgradeURL, useNewPricingModal } from '$lib/stores/billing';
- import type { PaymentMethodData } from '$lib/sdk/billing';
import { onMount } from 'svelte';
import { page } from '$app/state';
import { confirmPayment } from '$lib/stores/stripe';
@@ -26,18 +25,8 @@
export let data: PageData;
- // Reactive statement to update organization when data changes
$: organization = data.organization;
- // why are these reactive?
- $: defaultPaymentMethod = data?.paymentMethods?.paymentMethods?.find(
- (method: PaymentMethodData) => method.$id === organization?.paymentMethodId
- );
-
- $: backupPaymentMethod = data?.paymentMethods?.paymentMethods?.find(
- (method: PaymentMethodData) => method.$id === organization?.backupPaymentMethodId
- );
-
onMount(async () => {
if (page.url.searchParams.has('type')) {
if (page.url.searchParams.get('type') === 'upgrade') {
@@ -115,7 +104,7 @@
{/if}
{/if}
- {#if defaultPaymentMethod?.failed && !backupPaymentMethod}
+ {#if data.primaryPaymentMethod?.failed && !data.backupPaymentMethod}
@@ -146,7 +135,14 @@
currentInvoice={data?.billingInvoice} />
{/if}
-
+
+
+
{
depends(Dependencies.CREDIT);
depends(Dependencies.INVOICES);
depends(Dependencies.ADDRESS);
- //aggregation reloads on page param changes
+ // aggregation reloads on page param changes
depends(Dependencies.BILLING_AGGREGATION);
const billingAddressId = (organization as Organization)?.billingAddressId;
@@ -82,6 +83,10 @@ export const load: PageLoad = async ({ parent, depends, url, route }) => {
// make number
const credits = availableCredit ? availableCredit.available : null;
+ const {
+ backup,
+ primary
+ } = getOrganizationPaymentMethods(organization, paymentMethods)
return {
paymentMethods,
@@ -98,6 +103,32 @@ export const load: PageLoad = async ({ parent, depends, url, route }) => {
offset: pageToOffset(
getPage(url) || 1,
getLimit(url, route, DEFAULT_BILLING_PROJECTS_LIMIT)
- )
+ ),
+
+ backupPaymentMethod: backup,
+ primaryPaymentMethod: primary
};
};
+
+function getOrganizationPaymentMethods(
+ organization: Organization,
+ paymentMethods: PaymentList
+): {
+ backup: PaymentMethodData | null;
+ primary: PaymentMethodData | null;
+} {
+ let backup: PaymentMethodData | null = null;
+ let primary: PaymentMethodData | null = null;
+
+ for (const paymentMethod of paymentMethods.paymentMethods) {
+ if (paymentMethod.$id === organization.paymentMethodId) {
+ primary = paymentMethod;
+ } else if (paymentMethod.$id === organization.backupPaymentMethodId) {
+ backup = paymentMethod;
+ }
+
+ if (primary && backup) break;
+ }
+
+ return { primary, backup };
+}
diff --git a/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte b/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
index 8f6625179..7290ae055 100644
--- a/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
@@ -25,7 +25,7 @@
message: `The payment method has been removed from ${$organization.name}`
});
trackEvent(Submit.OrganizationPaymentDelete);
- invalidate(Dependencies.ORGANIZATION);
+ await invalidate(Dependencies.PAYMENT_METHODS);
showDelete = false;
} catch (e) {
error = e.message;
@@ -34,7 +34,7 @@
showDelete = false;
}
}
- async function removeBackuptMethod() {
+ async function removeBackupMethod() {
if ($organization?.billingPlan !== BillingPlan.FREE && !hasOtherMethod) return;
showDelete = false;
@@ -45,7 +45,7 @@
message: `The payment method has been removed from ${$organization.name}`
});
trackEvent(Submit.OrganizationBackupPaymentDelete);
- invalidate(Dependencies.ORGANIZATION);
+ await invalidate(Dependencies.PAYMENT_METHODS);
showDelete = false;
} catch (e) {
error = e.message;
@@ -65,10 +65,11 @@
{:else}
+ title="Remove payment method"
+ onSubmit={isBackup ? removeBackupMethod : removeDefaultMethod}
+ >
Are you sure you want to remove the payment method from {$organization?.name}?
diff --git a/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte b/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
index 124518f1e..445c65d10 100644
--- a/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
@@ -34,16 +34,17 @@
IconTrash
} from '@appwrite.io/pink-icons-svelte';
- export let organization: Organization;
export let methods: PaymentList;
+ export let organization: Organization;
+
+ export let backupMethod: PaymentMethodData;
+ export let primaryMethod: PaymentMethodData;
let showPayment = false;
let showEdit = false;
let showDelete = false;
let showReplace = false;
let isSelectedBackup = false;
- let backupPaymentMethod: PaymentMethodData;
- let defaultPaymentMethod: PaymentMethodData;
async function addPaymentMethod(paymentMethodId: string) {
try {
@@ -56,7 +57,7 @@
message: `A new payment method has been added to ${organization.name}`
});
trackEvent(Submit.OrganizationPaymentUpdate);
- invalidate(Dependencies.ORGANIZATION);
+ await invalidate(Dependencies.PAYMENT_METHODS);
} catch (error) {
addNotification({
type: 'error',
@@ -76,7 +77,7 @@
type: 'success',
message: `A new payment method has been added to ${organization.name}`
});
- invalidate(Dependencies.ORGANIZATION);
+ await invalidate(Dependencies.PAYMENT_METHODS);
} catch (error) {
addNotification({
type: 'error',
@@ -86,27 +87,15 @@
}
}
- $: if (organization?.backupPaymentMethodId) {
- sdk.forConsole.billing
- .getOrganizationPaymentMethod(organization.$id, organization.backupPaymentMethodId)
- .then((res) => (backupPaymentMethod = res));
- }
-
- $: if (organization?.paymentMethodId) {
- sdk.forConsole.billing
- .getOrganizationPaymentMethod(organization.$id, organization.paymentMethodId)
- .then((res) => (defaultPaymentMethod = res));
- }
-
$: if (!showReplace) {
isSelectedBackup = false;
}
$: hasPaymentError =
- defaultPaymentMethod?.lastError ||
- defaultPaymentMethod?.expired ||
- backupPaymentMethod?.lastError ||
- backupPaymentMethod?.expired;
+ primaryMethod?.lastError ||
+ primaryMethod?.expired ||
+ backupMethod?.lastError ||
+ backupMethod?.expired;
@@ -132,14 +121,14 @@
-
+
- {#if defaultPaymentMethod?.userId === $user?.$id}
+ {#if primaryMethod?.userId === $user?.$id}
{
@@ -171,14 +160,14 @@
{#if organization?.backupPaymentMethodId}
-
+
- {#if backupPaymentMethod?.userId === $user?.$id}
+ {#if backupMethod?.userId === $user?.$id}
{
@@ -318,8 +307,8 @@
{/if}
{#if showEdit && isCloud && hasStripePublicKey}
+ bind:show={showEdit}
+ selectedPaymentMethod={isSelectedBackup ? backupMethod : primaryMethod} />
{/if}
{#if isCloud && hasStripePublicKey}
diff --git a/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte b/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
index ea4cc7d26..a02dde00b 100644
--- a/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
@@ -73,7 +73,7 @@
type: 'success',
message: `Your ${isBackup ? 'backup' : 'default'} payment method has been updated`
});
- invalidate(Dependencies.ORGANIZATION);
+ await invalidate(Dependencies.PAYMENT_METHODS);
trackEvent(
isBackup ? Submit.OrganizationBackupPaymentDelete : Submit.OrganizationPaymentDelete
);
From 4eda58bb999811b16869604c2eae5c2d849489ad Mon Sep 17 00:00:00 2001
From: Darshan
Date: Fri, 2 Jan 2026 19:28:44 +0530
Subject: [PATCH 46/82] improvements.
---
.../account/payments/editPaymentModal.svelte | 4 +-
.../billing/+page.svelte | 10 +--
.../billing/+page.ts | 5 +-
.../billing/deleteOrgPayment.svelte | 3 +-
.../billing/replaceCard.svelte | 68 ++++++++++---------
5 files changed, 47 insertions(+), 43 deletions(-)
diff --git a/src/routes/(console)/account/payments/editPaymentModal.svelte b/src/routes/(console)/account/payments/editPaymentModal.svelte
index fab629996..24a8fa08f 100644
--- a/src/routes/(console)/account/payments/editPaymentModal.svelte
+++ b/src/routes/(console)/account/payments/editPaymentModal.svelte
@@ -12,12 +12,12 @@
let {
show = $bindable(false),
isLinked = false,
- selectedPaymentMethod,
+ selectedPaymentMethod
}: {
show: boolean;
isLinked?: boolean;
selectedPaymentMethod: PaymentMethodData;
- } = $props()
+ } = $props();
let year: number | null = $state(null);
let month: string | null = $state(null);
diff --git a/src/routes/(console)/organization-[organization]/billing/+page.svelte b/src/routes/(console)/organization-[organization]/billing/+page.svelte
index ab69b151b..51d7fe353 100644
--- a/src/routes/(console)/organization-[organization]/billing/+page.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/+page.svelte
@@ -20,12 +20,15 @@
import { Alert } from '@appwrite.io/pink-svelte';
import { goto, invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
- import { base } from '$app/paths';
import type { PageData } from './$types';
+ import { resolve } from '$app/paths';
export let data: PageData;
$: organization = data.organization;
+ $: baseUrl = resolve('/(console)/organization-[organization]/billing', {
+ organization: organization.$id
+ });
onMount(async () => {
if (page.url.searchParams.has('type')) {
@@ -47,7 +50,7 @@
organization.$id,
invoice.clientSecret,
organization.paymentMethodId,
- `${base}/organization-${organization.$id}/billing?type=validate-invoice&invoice=${invoice.$id}`
+ `${baseUrl}?type=validate-invoice&invoice=${invoice.$id}`
);
}
@@ -140,8 +143,7 @@
methods={data?.paymentMethods}
organization={data?.organization}
backupMethod={data.backupPaymentMethod}
- primaryMethod={data.primaryPaymentMethod}
- />
+ primaryMethod={data.primaryPaymentMethod} />
{
// make number
const credits = availableCredit ? availableCredit.available : null;
- const {
- backup,
- primary
- } = getOrganizationPaymentMethods(organization, paymentMethods)
+ const { backup, primary } = getOrganizationPaymentMethods(organization, paymentMethods);
return {
paymentMethods,
diff --git a/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte b/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
index 7290ae055..1de90ac4a 100644
--- a/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
@@ -68,8 +68,7 @@
bind:error
bind:open={showDelete}
title="Remove payment method"
- onSubmit={isBackup ? removeBackupMethod : removeDefaultMethod}
- >
+ onSubmit={isBackup ? removeBackupMethod : removeDefaultMethod}>
Are you sure you want to remove the payment method from {$organization?.name}?
diff --git a/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte b/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
index a02dde00b..b703f0842 100644
--- a/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
@@ -13,17 +13,29 @@
import { PaymentBoxes } from '$lib/components/billing';
import type { PaymentMethod } from '@stripe/stripe-js';
- export let organization: Organization;
- export let show = false;
- export let isBackup = false;
- export let methods: PaymentList;
+ let {
+ show = false,
+ isBackup = false,
+ methods,
+ organization
+ }: {
+ show?: boolean;
+ isBackup?: boolean;
+ methods: PaymentList;
+ organization: Organization;
+ } = $props();
- let name: string;
- let error: string = null;
- let selectedPaymentMethodId: string;
- let showState: boolean = false;
- let state: string = '';
- let paymentMethod: PaymentMethod | null = null;
+ let name: string | null = $state(null);
+ let error: string | null = $state(null);
+ let showState: boolean = $state(false);
+ let countryState: string | null = $state(null);
+ let paymentMethod: PaymentMethod | null = $state(null);
+ let selectedPaymentMethodId: string | null = $state(null);
+
+ const filteredMethods = $derived(methods?.paymentMethods.filter((method) => !!method?.last4));
+ const submitEvent = $derived(
+ isBackup ? Submit.OrganizationBackupPaymentDelete : Submit.OrganizationPaymentDelete
+ );
onMount(async () => {
if (!organization.paymentMethodId && !organization.backupPaymentMethodId) {
@@ -45,12 +57,12 @@
async function handleSubmit() {
try {
if (selectedPaymentMethodId === '$new') {
- if (showState && !state) {
+ if (showState && !countryState) {
throw Error('Please select a state');
}
let method: PaymentMethodData;
if (showState) {
- method = await setPaymentMethod(paymentMethod.id, name, state);
+ method = await setPaymentMethod(paymentMethod.id, name, countryState);
} else {
const card = await submitStripeCard(name, organization.$id);
if (card && Object.hasOwn(card, 'id')) {
@@ -69,21 +81,17 @@
? await addBackupPaymentMethod(selectedPaymentMethodId)
: await addPaymentMethod(selectedPaymentMethodId);
+ await invalidate(Dependencies.PAYMENT_METHODS);
+
addNotification({
type: 'success',
message: `Your ${isBackup ? 'backup' : 'default'} payment method has been updated`
});
- await invalidate(Dependencies.PAYMENT_METHODS);
- trackEvent(
- isBackup ? Submit.OrganizationBackupPaymentDelete : Submit.OrganizationPaymentDelete
- );
+ trackEvent(submitEvent);
show = false;
- } catch (e) {
- error = e.message;
- trackError(
- e,
- isBackup ? Submit.OrganizationBackupPaymentDelete : Submit.OrganizationPaymentDelete
- );
+ } catch (err) {
+ error = err.message;
+ trackError(err, submitEvent);
}
}
@@ -93,8 +101,8 @@
organization.$id,
paymentMethodId
);
- } catch (e) {
- error = e.message;
+ } catch (err) {
+ error = err.message;
}
}
@@ -104,23 +112,21 @@
organization.$id,
paymentMethodId
);
- } catch (e) {
- error = e.message;
+ } catch (err) {
+ error = err.message;
}
}
-
- $: filteredMethods = methods?.paymentMethods.filter((method) => !!method?.last4);
Replace the existing payment method for your organization.
Date: Fri, 2 Jan 2026 19:34:37 +0530
Subject: [PATCH 47/82] fix: lint.
---
.../organization-[organization]/billing/replaceCard.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte b/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
index b703f0842..4c326488a 100644
--- a/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
@@ -14,7 +14,7 @@
import type { PaymentMethod } from '@stripe/stripe-js';
let {
- show = false,
+ show = $bindable(false),
isBackup = false,
methods,
organization
From fdc94a584f1935faaba2dc3d807198732588e113 Mon Sep 17 00:00:00 2001
From: Darshan
Date: Fri, 2 Jan 2026 19:36:01 +0530
Subject: [PATCH 48/82] fix: events.
---
.../organization-[organization]/billing/replaceCard.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte b/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
index 4c326488a..920f7b474 100644
--- a/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
@@ -34,7 +34,7 @@
const filteredMethods = $derived(methods?.paymentMethods.filter((method) => !!method?.last4));
const submitEvent = $derived(
- isBackup ? Submit.OrganizationBackupPaymentDelete : Submit.OrganizationPaymentDelete
+ isBackup ? Submit.OrganizationBackupPaymentUpdate : Submit.OrganizationPaymentUpdate
);
onMount(async () => {
From fb2d12d33c2cb97cae67f2a9fda217c7e75b8c89 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Fri, 2 Jan 2026 10:36:53 -0800
Subject: [PATCH 49/82] Remove payment mandate setup functionality (#2748)
* Initial plan
* Remove all mandate-related code including setupPaymentMandate, checkForMandate, and paymentMandate component
Co-authored-by: stnguyen90 <1477010+stnguyen90@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: stnguyen90 <1477010+stnguyen90@users.noreply.github.com>
---
.../billing/alerts/paymentMandate.svelte | 26 -------------------
src/lib/sdk/billing.ts | 20 --------------
src/lib/stores/billing.ts | 21 +--------------
src/routes/(console)/+layout.svelte | 2 --
4 files changed, 1 insertion(+), 68 deletions(-)
delete mode 100644 src/lib/components/billing/alerts/paymentMandate.svelte
diff --git a/src/lib/components/billing/alerts/paymentMandate.svelte b/src/lib/components/billing/alerts/paymentMandate.svelte
deleted file mode 100644
index 08d9207d7..000000000
--- a/src/lib/components/billing/alerts/paymentMandate.svelte
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-{#if $paymentMissingMandate && $paymentMissingMandate?.country?.toLowerCase() === 'in' && $paymentMissingMandate.mandateId === null && !hideBillingHeaderRoutes.includes(page.url.pathname)}
-
- The payment method for {$organization.name} needs to be verified.
-
-
-
-
-{/if}
diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts
index 11b5ba519..994506e72 100644
--- a/src/lib/sdk/billing.ts
+++ b/src/lib/sdk/billing.ts
@@ -1300,26 +1300,6 @@ export class Billing {
);
}
- async setupPaymentMandate(
- organizationId: string,
- paymentMethodId: string
- ): Promise {
- const path = `/account/payment-methods/${paymentMethodId}/setup`;
- const params = {
- organizationId,
- paymentMethodId
- };
- const uri = new URL(this.client.config.endpoint + path);
- return await this.client.call(
- 'patch',
- uri,
- {
- 'content-type': 'application/json'
- },
- params
- );
- }
-
async listAddresses(queries: string[] = []): Promise {
const path = `/account/billing-addresses`;
const params = {
diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts
index 695ef874c..df2d29178 100644
--- a/src/lib/stores/billing.ts
+++ b/src/lib/stores/billing.ts
@@ -8,7 +8,7 @@ import MarkedForDeletion from '$lib/components/billing/alerts/markedForDeletion.
import MissingPaymentMethod from '$lib/components/billing/alerts/missingPaymentMethod.svelte';
import newDevUpgradePro from '$lib/components/billing/alerts/newDevUpgradePro.svelte';
import PaymentAuthRequired from '$lib/components/billing/alerts/paymentAuthRequired.svelte';
-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 { type Size, sizeToBytes } from '$lib/helpers/sizeConvertion';
@@ -18,7 +18,6 @@ import type {
Invoice,
InvoiceList,
PaymentList,
- PaymentMethodData,
Plan,
PlansMap
} from '$lib/sdk/billing';
@@ -535,24 +534,6 @@ export function checkForMarkedForDeletion(org: Organization) {
}
}
-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);
- if (paymentMethod?.mandateId === null && paymentMethod?.country.toLowerCase() === 'in') {
- headerAlert.add({
- id: 'paymentMandate',
- component: PaymentMandate,
- show: true,
- importance: 8
- });
- activeHeaderAlert.set(headerAlert.get());
- paymentMissingMandate.set(paymentMethod);
- }
-}
-
export async function checkForMissingPaymentMethod() {
const orgs = await sdk.forConsole.billing.listOrganization([
Query.notEqual('billingPlan', BillingPlan.FREE),
diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte
index d6231de58..ce28dd7c2 100644
--- a/src/routes/(console)/+layout.svelte
+++ b/src/routes/(console)/+layout.svelte
@@ -15,7 +15,6 @@
import {
calculateTrialDay,
checkForEnterpriseTrial,
- checkForMandate,
checkForMarkedForDeletion,
checkForMissingPaymentMethod,
checkForNewDevUpgradePro,
@@ -308,7 +307,6 @@
if (org?.billingPlan !== BillingPlan.FREE) {
await paymentExpired(org);
await checkPaymentAuthorizationRequired(org);
- await checkForMandate(org);
if ($plansInfo.get(org.billingPlan)?.trialDays) {
calculateTrialDay(org);
From 3191f3d89e1f6b5c30fe2eaf726ffca9621ddb3c Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Sun, 4 Jan 2026 14:15:37 +0530
Subject: [PATCH 50/82] fix: improve mobile table display for domain
verification
---
src/lib/components/domains/cnameTable.svelte | 20 +++++++++-----
src/lib/components/domains/recordTable.svelte | 26 ++++++++++++-------
2 files changed, 29 insertions(+), 17 deletions(-)
diff --git a/src/lib/components/domains/cnameTable.svelte b/src/lib/components/domains/cnameTable.svelte
index b198524da..9569ce89f 100644
--- a/src/lib/components/domains/cnameTable.svelte
+++ b/src/lib/components/domains/cnameTable.svelte
@@ -35,16 +35,22 @@
-
+
- Type
- Name
- Value
+ Type
+ Name
+ Value
- CNAME
- {subdomain}
-
+ CNAME
+ {subdomain}
+
-
+
- Type
- Name
- Value
+ Type
+ Name
+ Value
- {variant.toUpperCase()}
- {subdomain || '@'}
-
+ {variant.toUpperCase()}
+ {subdomain || '@'}
+
{#if $regionalConsoleVariables._APP_DOMAIN_TARGET_CAA}
-
+
CAA
- @
-
+ @
+
From 3d1f1632b7495b67dc78c12b300cc4e99062114f Mon Sep 17 00:00:00 2001
From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com>
Date: Mon, 5 Jan 2026 01:15:47 +0530
Subject: [PATCH 51/82] fix: added a tooltip over disabled create database
button
---
.../databases/+page.svelte | 26 ++++++++++++++-----
1 file changed, 20 insertions(+), 6 deletions(-)
diff --git a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte
index e95c01cca..e413399e8 100644
--- a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte
@@ -14,9 +14,11 @@
import Table from './table.svelte';
import { registerCommands } from '$lib/commandCenter';
import { canWriteDatabases } from '$lib/stores/roles';
- import { Icon } from '@appwrite.io/pink-svelte';
+ import { Icon, Tooltip } from '@appwrite.io/pink-svelte';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
import EmptySearch from '$lib/components/emptySearch.svelte';
+ import { isServiceLimited } from '$lib/stores/billing';
+ import { organization } from '$lib/stores/organization';
export let data: PageData;
@@ -30,6 +32,8 @@
);
}
+ $: isLimited = isServiceLimited('databases', $organization?.billingPlan, data.databases.total);
+
$: $registerCommands([
{
label: 'Create database',
@@ -37,7 +41,7 @@
showCreate = true;
},
keys: ['c'],
- disabled: showCreate || !$canWriteDatabases,
+ disabled: showCreate || !$canWriteDatabases || isLimited,
icon: IconPlus,
group: 'databases',
rank: 10
@@ -52,10 +56,20 @@
bind:view={data.view}
searchPlaceholder="Search by name or ID">
{#if $canWriteDatabases}
-
+
+
+
+
+
+ You have reached the maximum number of databases for your plan.
+
+
{/if}
From b721ab3109482c23704d8244574737914b97cdba Mon Sep 17 00:00:00 2001
From: Hemachandar <132386067+hmacr@users.noreply.github.com>
Date: Mon, 5 Jan 2026 15:56:00 +0530
Subject: [PATCH 52/82] Show last checked time for domain verification (#2760)
* Show last checked time for domain verification
* feedback
* better label
---
src/lib/helpers/date.ts | 12 ++++++
.../function-[function]/domains/+page.ts | 1 +
.../function-[function]/domains/store.ts | 7 ++++
.../function-[function]/domains/table.svelte | 32 ++++++++++++++++
.../settings/domains/+page.ts | 6 ++-
.../settings/domains/table.svelte | 38 +++++++++++++++++++
.../sites/site-[site]/domains/+page.ts | 1 +
.../sites/site-[site]/domains/store.ts | 7 ++++
.../sites/site-[site]/domains/table.svelte | 32 ++++++++++++++++
9 files changed, 135 insertions(+), 1 deletion(-)
diff --git a/src/lib/helpers/date.ts b/src/lib/helpers/date.ts
index 2d56c1cb6..29878256a 100644
--- a/src/lib/helpers/date.ts
+++ b/src/lib/helpers/date.ts
@@ -206,6 +206,18 @@ export function timeFromNow(datetime: string): string {
return dayjs().to(dayjs(datetime));
}
+export function timeFromNowShort(datetime: string): string {
+ if (!datetime || !isValidDate(datetime)) {
+ return 'n/a';
+ }
+
+ const timeStr = dayjs().to(dayjs(datetime));
+ return timeStr
+ .replace('second', 'sec') // seconds > secs
+ .replace('minute', 'min') // minutes > mins
+ .replace('hour', 'hr'); // hours > hrs
+}
+
export function hoursToDays(hours: number) {
if (hours > 24) {
return `${Math.floor(hours / 24)} days`;
diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/+page.ts b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/+page.ts
index 4145bf828..5fdc16ceb 100644
--- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/+page.ts
+++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/+page.ts
@@ -29,6 +29,7 @@ export const load: PageLoad = async ({ depends, params, url, route, parent }) =>
Query.limit(limit),
Query.offset(offset),
Query.orderDesc(''),
+ Query.orderDesc('$updatedAt'),
...parsedQueries.values()
],
search: search || undefined
diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/store.ts b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/store.ts
index f741c03ee..5f36c6bc1 100644
--- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/store.ts
+++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/store.ts
@@ -15,5 +15,12 @@ export const columns = writable([
title: 'Target',
type: 'string',
width: { min: 120, max: 400 }
+ },
+
+ {
+ id: 'updated',
+ title: '',
+ type: 'string',
+ width: { min: 160, max: 180 }
}
]);
diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/table.svelte
index 98e29e5ba..83daf1ffe 100644
--- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/table.svelte
@@ -25,6 +25,7 @@
import { regionalProtocol } from '$routes/(console)/project-[region]-[project]/store';
import DnsRecordsAction from '$lib/components/domains/dnsRecordsAction.svelte';
import ViewLogsModal from '$lib/components/domains/viewLogsModal.svelte';
+ import { timeFromNowShort } from '$lib/helpers/date';
let {
proxyRules,
@@ -46,6 +47,28 @@
? 'Deployed from ' + proxy.deploymentVcsProviderBranch
: 'Active deployment';
};
+
+ function updatedLabel(proxyRule: Models.ProxyRule): string {
+ if (proxyRule.status === 'verified') {
+ return '';
+ }
+
+ const timeStr = timeFromNowShort(proxyRule.$updatedAt);
+ if (timeStr === 'n/a') {
+ return '';
+ }
+
+ const prefix =
+ proxyRule.status === 'created'
+ ? 'Checked'
+ : proxyRule.status === 'verifying'
+ ? 'Updated'
+ : proxyRule.status === 'unverified'
+ ? 'Failed'
+ : '';
+
+ return prefix + ' ' + timeStr;
+ }
@@ -117,6 +140,15 @@
{:else if column.id === 'target'}
{proxyTarget(proxyRule)}
+ {:else if column.id === 'updated' && proxyRule.status !== 'verified'}
+
+
+ {updatedLabel(proxyRule)}
+
+
{/if}
{/each}
diff --git a/src/routes/(console)/project-[region]-[project]/settings/domains/+page.ts b/src/routes/(console)/project-[region]-[project]/settings/domains/+page.ts
index 39495486c..bf7570e23 100644
--- a/src/routes/(console)/project-[region]-[project]/settings/domains/+page.ts
+++ b/src/routes/(console)/project-[region]-[project]/settings/domains/+page.ts
@@ -21,7 +21,11 @@ export const load: PageLoad = async ({ depends, url, route, params, parent }) =>
const { organization } = await parent();
const rules = await sdk.forProject(params.region, params.project).proxy.listRules({
- queries: [Query.equal('type', RuleType.API), Query.equal('trigger', RuleTrigger.MANUAL)],
+ queries: [
+ Query.equal('type', RuleType.API),
+ Query.equal('trigger', RuleTrigger.MANUAL),
+ Query.orderDesc('$updatedAt')
+ ],
search: search || undefined
});
diff --git a/src/routes/(console)/project-[region]-[project]/settings/domains/table.svelte b/src/routes/(console)/project-[region]-[project]/settings/domains/table.svelte
index 594988a7f..051a20d3a 100644
--- a/src/routes/(console)/project-[region]-[project]/settings/domains/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/settings/domains/table.svelte
@@ -24,6 +24,7 @@
import { regionalProtocol } from '../../store';
import DnsRecordsAction from '$lib/components/domains/dnsRecordsAction.svelte';
import ViewLogsModal from '$lib/components/domains/viewLogsModal.svelte';
+ import { timeFromNowShort } from '$lib/helpers/date';
let {
domains,
@@ -45,8 +46,36 @@
type: 'string',
format: 'string',
width: { min: 300, max: 550 }
+ },
+ {
+ id: 'updated',
+ title: '',
+ type: 'string',
+ width: { min: 160, max: 180 }
}
];
+
+ function updatedLabel(proxyRule: Models.ProxyRule): string {
+ if (proxyRule.status === 'verified') {
+ return '';
+ }
+
+ const timeStr = timeFromNowShort(proxyRule.$updatedAt);
+ if (timeStr === 'n/a') {
+ return '';
+ }
+
+ const prefix =
+ proxyRule.status === 'created'
+ ? 'Checked'
+ : proxyRule.status === 'verifying'
+ ? 'Updated'
+ : proxyRule.status === 'unverified'
+ ? 'Failed'
+ : '';
+
+ return prefix + ' ' + timeStr;
+ }
@@ -116,6 +145,15 @@
{/if}
+ {:else if column.id === 'updated' && proxyRule.status !== 'verified'}
+
+
+ {updatedLabel(proxyRule)}
+
+
{/if}
{/each}
diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/+page.ts b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/+page.ts
index a942cc3c0..25c021aa6 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/+page.ts
+++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/+page.ts
@@ -28,6 +28,7 @@ export const load = async ({ params, depends, url, route, parent }) => {
Query.limit(limit),
Query.offset(offset),
Query.orderDesc(''),
+ Query.orderDesc('$updatedAt'),
...parsedQueries.values()
],
search: search || undefined
diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/store.ts b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/store.ts
index f741c03ee..5f36c6bc1 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/store.ts
+++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/store.ts
@@ -15,5 +15,12 @@ export const columns = writable([
title: 'Target',
type: 'string',
width: { min: 120, max: 400 }
+ },
+
+ {
+ id: 'updated',
+ title: '',
+ type: 'string',
+ width: { min: 160, max: 180 }
}
]);
diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/table.svelte
index b89528517..a3921aeaa 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/table.svelte
@@ -25,6 +25,7 @@
import { regionalProtocol } from '$routes/(console)/project-[region]-[project]/store';
import DnsRecordsAction from '$lib/components/domains/dnsRecordsAction.svelte';
import ViewLogsModal from '$lib/components/domains/viewLogsModal.svelte';
+ import { timeFromNowShort } from '$lib/helpers/date';
let {
proxyRules,
@@ -46,6 +47,28 @@
? 'Deployed from ' + proxy.deploymentVcsProviderBranch
: 'Active deployment';
};
+
+ function updatedLabel(proxyRule: Models.ProxyRule): string {
+ if (proxyRule.status === 'verified') {
+ return '';
+ }
+
+ const timeStr = timeFromNowShort(proxyRule.$updatedAt);
+ if (timeStr === 'n/a') {
+ return '';
+ }
+
+ const prefix =
+ proxyRule.status === 'created'
+ ? 'Checked'
+ : proxyRule.status === 'verifying'
+ ? 'Updated'
+ : proxyRule.status === 'unverified'
+ ? 'Failed'
+ : '';
+
+ return prefix + ' ' + timeStr;
+ }
@@ -117,6 +140,15 @@
{:else if column.id === 'target'}
{proxyTarget(proxyRule)}
+ {:else if column.id === 'updated' && proxyRule.status !== 'verified'}
+
+
+ {updatedLabel(proxyRule)}
+
+
{/if}
{/each}
From 6003039f1df1df6cf78a1946bbb919fea70c22aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Matej=20Ba=C4=8Do?=
Date: Tue, 6 Jan 2026 15:05:30 +0100
Subject: [PATCH 53/82] Project labels UI
---
package.json | 2 +-
pnpm-lock.yaml | 12 +-
src/lib/actions/analytics.ts | 1 +
.../organization-[organization]/+page.ts | 29 +++--
.../settings/+page.svelte | 4 +-
.../settings/updateLabels.svelte | 103 ++++++++++++++++++
6 files changed, 131 insertions(+), 20 deletions(-)
create mode 100644 src/routes/(console)/project-[region]-[project]/settings/updateLabels.svelte
diff --git a/package.json b/package.json
index a2e1778fe..f460aa71a 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
},
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
- "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f",
+ "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@5372e56",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@865e2fc",
"@appwrite.io/pink-legacy": "^1.0.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9a968c01e..057668c3a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12,8 +12,8 @@ importers:
specifier: ^1.1.24
version: 1.1.24(svelte@5.25.3)(zod@3.24.3)
'@appwrite.io/console':
- specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f
- version: https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f
+ specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@5372e56
+ version: https://pkg.vc/-/@appwrite/@appwrite.io/console@5372e56
'@appwrite.io/pink-icons':
specifier: 0.25.0
version: 0.25.0
@@ -272,9 +272,9 @@ packages:
'@analytics/type-utils@0.6.2':
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
- '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f':
- resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f}
- version: 1.10.0
+ '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@5372e56':
+ resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@5372e56}
+ version: 2.1.0
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1':
resolution: {integrity: sha512-iLFlV55hj8mGuAbmxJGenxN5RaZMmVT4GJb9dv/MP1xBAtYibFq7JvBcxm18qV2KU8c31Rntf+Ub4GL7HwqTYg==}
@@ -3823,7 +3823,7 @@ snapshots:
'@analytics/type-utils@0.6.2': {}
- '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f': {}
+ '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@5372e56': {}
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)':
dependencies:
diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts
index dea8860b5..d5ceb1c3e 100644
--- a/src/lib/actions/analytics.ts
+++ b/src/lib/actions/analytics.ts
@@ -247,6 +247,7 @@ export enum Submit {
ProjectDelete = 'submit_project_delete',
ProjectUpdateName = 'submit_project_update_name',
ProjectUpdateTeam = 'submit_project_update_team',
+ ProjectUpdateLabels = 'submit_project_update_labels',
ProjectService = 'submit_project_service',
ProjectUpdateSMTP = 'submit_project_update_smtp',
MemberCreate = 'submit_member_create',
diff --git a/src/routes/(console)/organization-[organization]/+page.ts b/src/routes/(console)/organization-[organization]/+page.ts
index b236341d9..a0e3ccc3c 100644
--- a/src/routes/(console)/organization-[organization]/+page.ts
+++ b/src/routes/(console)/organization-[organization]/+page.ts
@@ -23,6 +23,11 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
const archivedPage =
Number.isFinite(archivedPageRaw) && archivedPageRaw > 0 ? archivedPageRaw : 1;
const archivedOffset = pageToOffset(archivedPage, limit);
+
+ const searchQueries = search
+ ? [Query.or([Query.search('search', search), Query.contains('labels', search)])]
+ : [];
+
const [activeProjects, archivedProjects, activeTotal, archivedTotal] = await Promise.all([
sdk.forConsole.projects.list({
queries: [
@@ -30,9 +35,9 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
Query.equal('teamId', params.organization),
Query.or([Query.equal('status', 'active'), Query.isNull('status')]),
Query.limit(limit),
- Query.orderDesc('')
- ],
- search: search || undefined
+ Query.orderDesc(''),
+ ...searchQueries
+ ]
}),
sdk.forConsole.projects.list({
queries: [
@@ -40,23 +45,23 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
Query.equal('teamId', params.organization),
Query.equal('status', 'archived'),
Query.limit(limit),
- Query.orderDesc('')
- ],
- search: search || undefined
+ Query.orderDesc(''),
+ ...searchQueries
+ ]
}),
sdk.forConsole.projects.list({
queries: [
Query.equal('teamId', params.organization),
- Query.or([Query.equal('status', 'active'), Query.isNull('status')])
- ],
- search: search || undefined
+ Query.or([Query.equal('status', 'active'), Query.isNull('status')]),
+ ...searchQueries
+ ]
}),
sdk.forConsole.projects.list({
queries: [
Query.equal('teamId', params.organization),
- Query.equal('status', 'archived')
- ],
- search: search || undefined
+ Query.equal('status', 'archived'),
+ ...searchQueries
+ ]
})
]);
diff --git a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte
index edf09b92f..8c427cc23 100644
--- a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte
@@ -15,6 +15,7 @@
import ChangeOrganization from './changeOrganization.svelte';
import UpdateVariables from '../updateVariables.svelte';
import { page } from '$app/state';
+ import UpdateLabels from './updateLabels.svelte';
export let data;
@@ -84,8 +85,9 @@
{#if $project}
-
{#if $canWriteProjects}
+
+
+ import { onMount } from 'svelte';
+ import { invalidate } from '$app/navigation';
+ import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
+ import { CardGrid } from '$lib/components';
+ import { Dependencies } from '$lib/constants';
+ import { Button, Form, InputTags } from '$lib/elements/forms';
+ import { symmetricDifference } from '$lib/helpers/array';
+ import { addNotification } from '$lib/stores/notifications';
+ import { sdk } from '$lib/stores/sdk';
+ import { project } from '../store';
+ import { Tag, Input, Layout, Icon } from '@appwrite.io/pink-svelte';
+ import { IconPlus } from '@appwrite.io/pink-icons-svelte';
+
+ const alphaNumericRegExp = /^[a-zA-Z0-9]+$/;
+ let suggestedLabels = ['live', 'stage', 'internal'];
+ let labels: string[] = [];
+ let error = '';
+
+ onMount(async () => {
+ labels = [...$project.labels];
+ });
+
+ async function updateLabels() {
+ try {
+ await sdk.forConsole.projects.updateLabels({ projectId: $project.$id, labels });
+ await invalidate(Dependencies.PROJECT);
+ await invalidate(Dependencies.ORGANIZATION);
+ isDisabled = true;
+ addNotification({
+ type: 'success',
+ message: 'Project labels have been updated'
+ });
+ trackEvent(Submit.ProjectUpdateLabels);
+ } catch (error) {
+ addNotification({
+ message: error.message,
+ type: 'error'
+ });
+ trackError(error, Submit.ProjectUpdateLabels);
+ }
+ }
+
+ $: isDisabled = !!error || !symmetricDifference(labels, $project.labels).length;
+
+ $: if (labels) {
+ const invalidLabels = [];
+
+ labels.forEach((label) => {
+ if (!alphaNumericRegExp.test(label)) {
+ invalidLabels.push(label);
+ }
+ });
+
+ if (invalidLabels.length) {
+ error = `Invalid labels: ${invalidLabels.join(', ')}`;
+ } else {
+ error = '';
+ }
+ }
+
+
+
From 169373d653b4b8eb61782f4c1c0b5a8cc62edb8e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Matej=20Ba=C4=8Do?=
Date: Tue, 6 Jan 2026 15:10:08 +0100
Subject: [PATCH 54/82] Improve copy
---
src/routes/(console)/organization-[organization]/+page.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte
index 83c40623c..37ef03204 100644
--- a/src/routes/(console)/organization-[organization]/+page.svelte
+++ b/src/routes/(console)/organization-[organization]/+page.svelte
@@ -170,7 +170,7 @@
-
+
{#if $canWriteProjects}
{#if projectCreationDisabled && reachedProjectLimit}
From 63ebeafc82550c6120fc56d0f3d584a1831c85e3 Mon Sep 17 00:00:00 2001
From: Hemachandar <132386067+hmacr@users.noreply.github.com>
Date: Wed, 7 Jan 2026 11:08:50 +0530
Subject: [PATCH 55/82] Improve UX for manual sites & functions deployment
(#2523)
* Improve UX for manual sites & functions deployment
* lint
* feedback
---
src/lib/stores/uploader.ts | 66 ++++++++++++++++++-
.../project-[region]-[project]/+layout.svelte | 2 +-
.../create-function/manual/+page.svelte | 65 +++++++++++-------
.../(modals)/createManual.svelte | 22 +++----
.../sites/create-site/manual/+page.svelte | 49 +++++++++-----
.../createManualDeploymentModal.svelte | 10 ++-
6 files changed, 160 insertions(+), 54 deletions(-)
diff --git a/src/lib/stores/uploader.ts b/src/lib/stores/uploader.ts
index ef848232c..8a04eae8a 100644
--- a/src/lib/stores/uploader.ts
+++ b/src/lib/stores/uploader.ts
@@ -1,4 +1,4 @@
-import { Client, ID, type Models, Sites, Storage } from '@appwrite.io/console';
+import { Client, Functions, ID, type Models, Sites, Storage } from '@appwrite.io/console';
import { writable } from 'svelte/store';
import { getApiEndpoint } from '$lib/stores/sdk';
import { page } from '$app/state';
@@ -32,6 +32,13 @@ const temporarySites = (region: string, projectId: string) => {
return new Sites(clientProject);
};
+const temporaryFunctions = (region: string, projectId: string) => {
+ const clientProject = new Client().setMode('admin');
+ const endpoint = getApiEndpoint(region);
+ clientProject.setEndpoint(endpoint).setProject(projectId);
+ return new Functions(clientProject);
+};
+
const createUploader = () => {
const { subscribe, set, update } = writable({
isOpen: false,
@@ -110,7 +117,19 @@ const createUploader = () => {
newFile.status = 'success';
updateFile(newFile.$id, newFile);
},
- uploadSiteDeployment: async (siteId: string, code: File) => {
+ uploadSiteDeployment: async ({
+ siteId,
+ code,
+ buildCommand,
+ installCommand,
+ outputDirectory
+ }: {
+ siteId: string;
+ code: File;
+ buildCommand?: string;
+ installCommand?: string;
+ outputDirectory?: string;
+ }) => {
const newDeployment: UploaderFile = {
$id: '',
resourceId: siteId,
@@ -132,6 +151,49 @@ const createUploader = () => {
siteId,
code,
activate: true,
+ buildCommand,
+ installCommand,
+ outputDirectory,
+ onProgress: (progress) => {
+ newDeployment.$id = progress.$id;
+ newDeployment.progress = progress.progress;
+ newDeployment.status = progress.progress === 100 ? 'success' : 'pending';
+ updateFile(progress.$id, newDeployment);
+ }
+ });
+ newDeployment.$id = uploadedFile.$id;
+ newDeployment.progress = 100;
+ newDeployment.status = 'success';
+ updateFile(newDeployment.$id, newDeployment);
+ },
+ uploadFunctionDeployment: async ({
+ functionId,
+ code
+ }: {
+ functionId: string;
+ code: File;
+ }) => {
+ const newDeployment: UploaderFile = {
+ $id: '',
+ resourceId: functionId,
+ name: code.name,
+ size: code.size,
+ progress: 0,
+ status: 'pending'
+ };
+ update((n) => {
+ n.isOpen = true;
+ n.isCollapsed = false;
+ n.files.unshift(newDeployment);
+ return n;
+ });
+ const uploadedFile = await temporaryFunctions(
+ page.params.region,
+ page.params.project
+ ).createDeployment({
+ functionId,
+ code,
+ activate: true,
onProgress: (progress) => {
newDeployment.$id = progress.$id;
newDeployment.progress = progress.progress;
diff --git a/src/routes/(console)/project-[region]-[project]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/+layout.svelte
index 426821259..eeeff2dd7 100644
--- a/src/routes/(console)/project-[region]-[project]/+layout.svelte
+++ b/src/routes/(console)/project-[region]-[project]/+layout.svelte
@@ -124,7 +124,7 @@