Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions src/lib/components/archiveProject.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import PaginationWithLimit from './paginationWithLimit.svelte';
import { Button, InputText } from '$lib/elements/forms';
import { GridItem1, CardContainer, Modal } from '$lib/components';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
Expand Down Expand Up @@ -45,9 +46,19 @@
projectsToArchive: Models.Project[];
organization: Organization;
currentPlan: Plan;
archivedTotalOverall: number;
archivedOffset: number;
limit: number;
}

let { projectsToArchive, organization, currentPlan }: Props = $props();
let {
projectsToArchive,
organization,
currentPlan,
archivedTotalOverall,
archivedOffset,
limit
}: Props = $props();

// Check if current plan order is less than Pro (order < 1 means FREE plan)
let isPlanBelowPro = $derived(currentPlan?.order < 1);
Expand Down Expand Up @@ -187,7 +198,7 @@
}

import { formatName as formatNameHelper } from '$lib/helpers/string';
function formatName(name: string, limit: number = 19) {
function formatName(name: string, limit: number = 16) {
return formatNameHelper(name, limit, $isSmallViewport);
}
</script>
Expand All @@ -196,7 +207,7 @@
<div class="archive-projects-margin-top">
<Accordion
title={isPlanBelowPro ? 'Archived projects' : 'Pending archive'}
badge={`${projectsToArchive.length}`}>
badge={`${archivedTotalOverall}`}>
<Typography.Text tag="p" size="s">
{#if isPlanBelowPro}
These projects are archived and require a plan upgrade to restore access.
Expand All @@ -206,7 +217,7 @@
</Typography.Text>

<div class="archive-projects-margin">
<CardContainer disableEmpty={true} total={projectsToArchive.length}>
<CardContainer disableEmpty={true} total={archivedTotalOverall}>
{#each projectsToArchive as project}
{@const platforms = filterPlatforms(
project.platforms.map((platform) => getPlatformInfo(platform.type))
Expand Down Expand Up @@ -266,7 +277,7 @@
</Badge>
{/each}

{#if platforms.length > 3}
{#if platforms.length > 2}
<Badge
variant="secondary"
content={`+${platforms.length - 2}`}
Expand All @@ -282,6 +293,15 @@
</GridItem1>
{/each}
</CardContainer>

<PaginationWithLimit
name="Archived Projects"
{limit}
offset={archivedOffset}
total={archivedTotalOverall}
pageParam="archivedPage"
removeOnFirstPage
class="pagination-container" />
</div>
</Accordion>
</div>
Expand Down Expand Up @@ -355,4 +375,7 @@
align-items: center;
gap: 8px;
}
:global(.pagination-container) {
margin-top: 16px;
}
</style>
17 changes: 13 additions & 4 deletions src/lib/components/limit.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -23,10 +25,17 @@
url.searchParams.set('limit', limit.toString());
await preferences.setLimit(limit);

if (url.searchParams.has('page')) {
const page = Number(url.searchParams.get('page'));
const newPage = Math.floor(((page - 1) * previousLimit) / limit);
url.searchParams.set('page', newPage.toString());
if (url.searchParams.has(pageParam)) {
const page = Number(url.searchParams.get(pageParam));
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);
} else {
url.searchParams.set(pageParam, safePage.toString());
}
}

await goto(url.toString());
Expand Down
10 changes: 8 additions & 2 deletions src/lib/components/pagination.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
21 changes: 17 additions & 4 deletions src/lib/components/paginationWithLimit.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,37 @@
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]: unknown;
} = $props();

const showLimit = $derived(!!useCreateLink);
const direction = $derived(showLimit ? 'row' : 'column');
const alignItems = $derived(showLimit ? 'center' : 'flex-end');
</script>

<Layout.Stack wrap="wrap" {direction} {alignItems} justifyContent="space-between">
<Layout.Stack wrap="wrap" {direction} {alignItems} justifyContent="space-between" {...restProps}>
{#if showLimit}
<Limit {limit} sum={total} {name} />
<Limit {limit} sum={total} {name} {pageParam} {removeOnFirstPage} />
{/if}

<Pagination on:page {limit} {offset} sum={total} {useCreateLink} />
<Pagination
on:page
{limit}
{offset}
sum={total}
{useCreateLink}
{pageParam}
{removeOnFirstPage} />
</Layout.Stack>
6 changes: 4 additions & 2 deletions src/lib/components/paginator.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
name = 'items',
gap = 's',
offset = $bindable(0),
children
children,
...restProps
}: {
items: T[];
limit?: number;
Expand All @@ -26,14 +27,15 @@
| undefined;
offset?: number;
children: Snippet<[T[], number]>;
[key: string]: unknown;
} = $props();

let total = $derived(items.length);

let paginatedItems = $derived(items.slice(offset, offset + limit));
</script>

<Layout.Stack {gap}>
<Layout.Stack {gap} {...restProps}>
{@render children(paginatedItems, limit)}

{#if !hideFooter}
Expand Down
28 changes: 19 additions & 9 deletions src/routes/(console)/organization-[organization]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,6 @@
return getServiceLimit('projects', null, data.currentPlan);
});

const projectsToArchive = $derived.by(() => {
return isCloud
? data.projects.projects.filter((project) => project.status === 'archived')
: [];
});

function filterPlatforms(platforms: { name: string; icon: string }[]) {
return platforms.filter(
(value, index, self) => index === self.findIndex((t) => t.name === value.name)
Expand Down Expand Up @@ -136,6 +130,19 @@
return project.status === 'archived';
}

const projectsToArchive = $derived(
(data.archivedProjectsPage ?? data.projects.projects).filter(
(project) => project.status === 'archived'
)
);

const activeTotalOverall = $derived(
data?.activeTotalOverall ??
data?.organization?.projects?.length ??
data?.projects?.total ??
0
);

function clearSearch() {
searchQuery?.clearInput();
}
Expand Down Expand Up @@ -238,7 +245,7 @@
{#if data.projects.total > 0}
<CardContainer
disableEmpty={!$canWriteProjects}
total={data.projects.total}
total={activeTotalOverall}
offset={data.offset}
on:click={handleCreateProject}>
{#each data.projects.projects as project}
Expand Down Expand Up @@ -323,13 +330,16 @@
name="Projects"
limit={data.limit}
offset={data.offset}
total={data.projects.total} />
total={activeTotalOverall} />

<!-- Archived Projects Section -->
<ArchiveProject
{projectsToArchive}
organization={data.organization}
currentPlan={$currentPlan} />
currentPlan={$currentPlan}
archivedTotalOverall={data.archivedTotalOverall}
archivedOffset={data.archivedOffset}
limit={data.limit} />
</Container>
<CreateOrganization bind:show={addOrganization} />
<CreateProject bind:show={showCreate} teamId={page.params.organization} />
Expand Down
68 changes: 55 additions & 13 deletions src/routes/(console)/organization-[organization]/+page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { CARD_LIMIT, Dependencies } from '$lib/constants';
import type { PageLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import { base } from '$app/paths';
import { isCloud } from '$lib/system';

export const load: PageLoad = async ({ params, url, route, depends, parent }) => {
const { scopes } = await parent();
Expand All @@ -20,26 +19,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({
queries: [
Query.offset(offset),
Query.equal('teamId', params.organization),
Query.limit(limit),
Query.orderDesc(''),
Query.select(['$id', 'name', 'platforms', 'region', ...(isCloud ? ['status'] : [])])
],
search: search || undefined
});
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: [
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
})
]);
Comment on lines +26 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Optional: verify Query.orderDesc('') usage.

Empty field name may be ignored or error depending on SDK version. Confirm intended default ordering or set an explicit field (e.g., createdAt/updatedAt).


🌐 Web query:

Is an empty string a valid field for Appwrite `Query.orderDesc('')`? What field should be used to order projects by most recently created in the Appwrite Console SDK?

💡 Result:

No — an empty string is not a valid field for Query.orderDesc(''). To sort by most-recently-created use the created timestamp field (Appwrite exposes $createdAt). Example: Query.orderDesc('$createdAt') — (escape the $ if your language/string parser requires, e.g. '$createdAt' in Flutter). You can also use $sequence for insertion order if preferred. [1][2][3]


Use explicit timestamp field in Query.orderDesc
Replace both instances of Query.orderDesc('') with Query.orderDesc('$createdAt') to ensure projects are sorted by most-recently-created timestamp.

🤖 Prompt for AI Agents
In src/routes/(console)/organization-[organization]/+page.ts around lines 24 to
59, the two calls using Query.orderDesc('') should explicitly sort by creation
timestamp; replace both Query.orderDesc('') occurrences with
Query.orderDesc('$createdAt') so projects are ordered by most-recently-created
items.


// set `default` if no region!
for (const project of projects.projects) {
for (const project of activeProjects.projects) {
project.region ??= 'default';
}
for (const project of archivedProjects.projects) {
project.region ??= 'default';
}

return {
offset,
limit,
projects,
projects: {
...activeProjects,
projects: activeProjects.projects,
total: activeTotal.total
},
activeProjectsPage: activeProjects.projects,
archivedProjectsPage: archivedProjects.projects,
activeTotalOverall: activeTotal.total,
archivedTotalOverall: archivedTotal.total,
archivedOffset,
archivedPage,
search
};
};