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] 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 = ''; + } + } + + +
+ + Labels + Categorize and manage your projects for easy searching based on specific criteria by assigning + them customizable labels. + + + {#key labels.length} + + {/key} + + {#each suggestedLabels as suggestedLabel} + { + if (!labels.includes(suggestedLabel)) { + labels = [...labels, suggestedLabel]; + } else { + labels = labels.filter((e) => e !== suggestedLabel); + } + }}> + + {suggestedLabel} + + {/each} + + {error ? error : 'Only alphanumeric characters are allowed'} + + + + + + + +