diff --git a/src/lib/components/billing/alerts/projectsLimit.svelte b/src/lib/components/billing/alerts/projectsLimit.svelte index cd3a0a3ee..8d7b10b33 100644 --- a/src/lib/components/billing/alerts/projectsLimit.svelte +++ b/src/lib/components/billing/alerts/projectsLimit.svelte @@ -9,17 +9,22 @@ upgradeURL } from '$lib/stores/billing'; import { currentPlan } from '$lib/stores/organization'; - import { onMount } from 'svelte'; import SelectProjectCloud from './selectProjectCloud.svelte'; import { toLocaleDate } from '$lib/helpers/date'; - let showSelectProject: boolean = $state(false); + let selectedProjects: string[] = $state([]); - onMount(() => { - selectedProjects = page.data.organization?.projects || []; + let showSelectProject: boolean = $state(false); + + const organizationId = $derived(page.data.organization?.$id); + + $effect(() => { + if (organizationId) { + selectedProjects = page.data.organization?.projects || []; + } }); - + {#if $currentPlan && $currentPlan.projects > 0 && !hideBillingHeaderRoutes.includes(page.url.pathname)} - import { type Models } from '@appwrite.io/console'; - import { Alert, Button, Table } from '@appwrite.io/pink-svelte'; + import { type Models, Query } from '@appwrite.io/console'; + import { Alert, Button, Skeleton, Table } from '@appwrite.io/pink-svelte'; import { Modal } from '$lib/components'; - import { onMount } from 'svelte'; import { sdk } from '$lib/stores/sdk'; import { addNotification } from '$lib/stores/notifications'; import { invalidate } from '$app/navigation'; import { Dependencies } from '$lib/constants'; import { billingProjectsLimitDate } from '$lib/stores/billing'; - import { page } from '$app/state'; import { toLocaleDate, toLocaleDateTime } from '$lib/helpers/date'; import { currentPlan } from '$lib/stores/organization'; let { showSelectProject = $bindable(false), - selectedProjects = $bindable([]) + selectedProjects = $bindable([]), + organizationId }: { showSelectProject: boolean; selectedProjects: string[]; + organizationId: string; } = $props(); - let projects = $state>([]); - let error = $state(null); + let loading = $state(false); + let projectsLoadingError = $state(null); - onMount(() => { - const currentOrgId = page.data.organization?.$id; - projects = - page.data.currentOrgId === currentOrgId ? page.data.allProjects?.projects || [] : []; - }); + let error = $state(null); + let projects = $state>([]); let projectsToArchive = $derived( projects.filter((project) => !selectedProjects.includes(project.$id)) @@ -39,6 +36,7 @@ projects[0].teamId, selectedProjects ); + showSelectProject = false; invalidate(Dependencies.ORGANIZATION); addNotification({ @@ -69,47 +67,95 @@ return result; } + + $effect(() => { + if (!showSelectProject) return; + + const areProjectsLoaded = projects.length > 0; + const teamIdInLoadedProjects = areProjectsLoaded ? projects[0].teamId : null; + + if (organizationId != teamIdInLoadedProjects) { + loading = true; + + sdk.forConsole.projects + .list([ + Query.equal('teamId', organizationId), + Query.limit(1000) // Get all projects for organization + ]) + .then((loadedProjects) => (projects = loadedProjects.projects)) + .catch((err) => (projectsLoadingError = err.message)) + .finally(() => (loading = false)); + } + }); - + Choose which {$currentPlan?.projects || 2} projects to keep. Projects over the limit will be blocked after this date. - {#if error} - {error} - {/if} - - - Project Name - Created - - {#each projects as project} - - {project.name} - {toLocaleDateTime(project.$createdAt)} - - {/each} - - {#if selectedProjects.length > $currentPlan?.projects} -
- You can only select {$currentPlan?.projects} projects. Please deselect others to continue. + + {#if loading} +
+ + + Project Name + Created + + + {#each Array.from({ length: 5 }) as _} + + + + + + + + + {/each} +
- {/if} - {#if selectedProjects.length === $currentPlan?.projects} - - - {@html formatProjectsToArchive()} - will be archived. - - + {:else if projectsLoadingError} + {projectsLoadingError} + {:else} + {#if error} + {error} + {/if} + + + + Project Name + Created + + {#each projects as project} + + {project.name} + {toLocaleDateTime(project.$createdAt)} + + {/each} + + + {#if selectedProjects.length > $currentPlan?.projects} +
+ You can only select {$currentPlan?.projects} projects. Please deselect others to continue. +
+ {/if} + + {#if selectedProjects.length === $currentPlan?.projects} + + + {@html formatProjectsToArchive()} + will be archived. + + + {/if} {/if} (showSelectProject = false)} @@ -118,3 +164,13 @@ >Save + + diff --git a/src/lib/components/breadcrumbs.svelte b/src/lib/components/breadcrumbs.svelte index da4bfd5c0..e2ecda32f 100644 --- a/src/lib/components/breadcrumbs.svelte +++ b/src/lib/components/breadcrumbs.svelte @@ -22,7 +22,9 @@ import { base } from '$app/paths'; import { currentPlan, newOrgModal } from '$lib/stores/organization'; import { Click, trackEvent } from '$lib/actions/analytics'; - import type { Models } from '@appwrite.io/console'; + import { type Models, Query } from '@appwrite.io/console'; + import { sdk } from '$lib/stores/sdk'; + import { page } from '$app/state'; type Organization = { name: string; @@ -63,15 +65,15 @@ } } = createMenu(); - let isLoadingProjects = true; + let isLoadingProjects = false; let loadedProjects: Models.ProjectList = { total: 0, projects: [] }; export let organizations: Organization[] = []; export let currentProject: Models.Project | null = null; - export let projects: Promise = Promise.resolve(loadedProjects); let projectsBottomSheetOpen = false; let organisationBottomSheetOpen = false; + let projectsBottomSheet: Promise = null; function createOrg() { trackEvent(Click.OrganizationClickCreate, { source: 'breadcrumbs' }); @@ -99,13 +101,25 @@ } }; - async function createProjectsBottomSheet(organization: Organization): Promise { + async function createProjectsBottomSheet( + organization: Organization + ): Promise { + if (!isOnProjects) return null; + isLoadingProjects = true; + // null on non-org/project path like `onboarding`. - loadedProjects = (await projects) ?? loadedProjects; + loadedProjects = + (await sdk.forConsole.projects.list([ + Query.equal('teamId', organizationId ?? page.data.currentOrgId), + Query.limit(5), + Query.orderDesc('$updatedAt') + ])) ?? loadedProjects; + for (const project of loadedProjects.projects) { project.region ??= 'default'; } + isLoadingProjects = false; const createProjectItem = { @@ -185,8 +199,6 @@ $: selectedOrg = organizations.find((org) => org.isSelected); - $: projectsBottomSheet = createProjectsBottomSheet(selectedOrg); - $: organizationsBottomSheet = createOrganizationBottomSheet(selectedOrg); $: correctPlanName = @@ -198,6 +210,20 @@ : selectedOrg?.tierName; // fallback $: derivedKey = `${selectedOrg?.$id}-${currentProject?.$id}`; + + $: organizationId = currentProject?.teamId; + + $: isOnProjects = page.route.id.includes('project-[region]-[project]'); + + $: shouldReloadProjects = isLoadingProjects + ? false + : currentProject && loadedProjects.projects.length + ? loadedProjects.projects[0].teamId != currentProject.teamId + : !loadedProjects.projects.length; + + $: if (shouldReloadProjects) { + projectsBottomSheet = createProjectsBottomSheet(selectedOrg); + } @@ -380,7 +406,9 @@ {#await projectsBottomSheet then menu} - + {#if menu} + + {/if} {/await} {/key} diff --git a/src/lib/components/navbar.svelte b/src/lib/components/navbar.svelte index c87845b4e..51ed33e43 100644 --- a/src/lib/components/navbar.svelte +++ b/src/lib/components/navbar.svelte @@ -1,15 +1,6 @@ - +
diff --git a/src/routes/(console)/organization-[organization]/createProjectCloud.svelte b/src/routes/(console)/organization-[organization]/createProjectCloud.svelte index a0236076f..408c06982 100644 --- a/src/routes/(console)/organization-[organization]/createProjectCloud.svelte +++ b/src/routes/(console)/organization-[organization]/createProjectCloud.svelte @@ -12,8 +12,8 @@ import { Dependencies } from '$lib/constants'; export let teamId: string; - export let showCreateProjectCloud: boolean; export let projects: number; + export let showCreateProjectCloud: boolean; export let regions: Array = []; let id: string = null;