Differentiate archived project behavior by plan tier (#2549)

Prior to this, the projects page had two problems:

1. After submitting a downgrade request but still on a Pro plan before the end of the billing period, projects showed as archived
2. After downgraded, archived projects showed as read only and gave options to migrate data even though access to the archived projects were blocked.

This PR clarifies the behavior for archived projects by:

- showing projects as pending archive if downgraded but still on Pro
- removing the read only tag for downgraded orgs
- preventing opening projects for downgraded orgs since the user has no access 
- making the unarchive and migrate buttons disabled for downgraded orgs
This commit is contained in:
Copilot
2025-11-22 21:52:19 -08:00
committed by GitHub
parent 78f19500a7
commit 9cbf8d2c27
2 changed files with 16 additions and 42 deletions
+14 -40
View File
@@ -1,12 +1,11 @@
<script lang="ts">
import { Button, InputText } from '$lib/elements/forms';
import { DropList, GridItem1, CardContainer, Modal } from '$lib/components';
import { GridItem1, CardContainer, Modal } from '$lib/components';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import {
Badge,
Icon,
Typography,
Tag,
Accordion,
ActionMenu,
Popover,
@@ -20,7 +19,6 @@
IconFlutter,
IconReact,
IconUnity,
IconInfo,
IconDotsHorizontal,
IconInboxIn,
IconSwitchHorizontal,
@@ -29,7 +27,6 @@
import { getPlatformInfo } from '$lib/helpers/platform';
import { Status, type Models } from '@appwrite.io/console';
import type { ComponentType } from 'svelte';
import { BillingPlan } from '$lib/constants';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { sdk } from '$lib/stores/sdk';
@@ -52,8 +49,9 @@
let { projectsToArchive, organization, currentPlan }: Props = $props();
// Track Read-only info droplist per archived project
let readOnlyInfoOpen = $state<Record<string, boolean>>({});
// Check if current plan order is less than Pro (order < 1 means FREE plan)
let isPlanBelowPro = $derived(currentPlan?.order < 1);
let showUnarchiveModal = $state(false);
let projectToUnarchive = $state<Models.Project | null>(null);
let showDeleteModal = $state(false);
@@ -97,7 +95,7 @@
function isUnarchiveDisabled(): boolean {
if (!organization || !currentPlan) return true;
if (organization.billingPlan === BillingPlan.FREE) {
if (isPlanBelowPro) {
const currentProjectCount = organization.projects?.length || 0;
const projectLimit = currentPlan.projects || 0;
@@ -196,10 +194,15 @@
{#if projectsToArchive.length > 0}
<div class="archive-projects-margin-top">
<Accordion title="Archived projects" badge={`${projectsToArchive.length}`}>
<Accordion
title={isPlanBelowPro ? 'Archived projects' : 'Pending archive'}
badge={`${projectsToArchive.length}`}>
<Typography.Text tag="p" size="s">
These projects have been archived and are read-only. You can view and migrate their
data.
{#if isPlanBelowPro}
These projects are archived and require a plan upgrade to restore access.
{:else}
These projects will be archived at the end of your billing cycle.
{/if}
</Typography.Text>
<div class="archive-projects-margin">
@@ -216,36 +219,6 @@
<svelte:fragment slot="title">{formatted}</svelte:fragment>
<svelte:fragment slot="status">
<div class="status-container">
<DropList
bind:show={readOnlyInfoOpen[project.$id]}
placement="bottom-start"
noArrow>
<Tag
size="s"
style="white-space: nowrap;"
on:click={(e) => {
e.preventDefault();
e.stopPropagation();
readOnlyInfoOpen = {
...readOnlyInfoOpen,
[project.$id]: !readOnlyInfoOpen[project.$id]
};
}}>
<Icon icon={IconInfo} size="s" />
<span>Read only</span>
</Tag>
<svelte:fragment slot="list">
<li
class="drop-list-item u-width-250"
style="padding: var(--space-5, 12px) var(--space-6, 16px)">
<span class="u-block u-mb-8">
Archived projects are read-only. You can view
and migrate their data, but they no longer
accept edits or requests.
</span>
</li>
</svelte:fragment>
</DropList>
<Popover let:toggle padding="none" placement="bottom-end">
<Button
text
@@ -267,6 +240,7 @@
>Unarchive project</ActionMenu.Item.Button>
<ActionMenu.Item.Button
leadingIcon={IconSwitchHorizontal}
disabled={isUnarchiveDisabled()}
on:click={() => handleMigrateProject(project)}
>Migrate project</ActionMenu.Item.Button>
<div class="action-menu-divider">
@@ -224,7 +224,7 @@
{#if activeProjects.length > 0}
<CardContainer
disableEmpty={!$canWriteProjects}
total={data.projects.total}
total={activeProjects.length}
offset={data.offset}
on:click={handleCreateProject}>
{#each activeProjects as project}
@@ -309,7 +309,7 @@
name="Projects"
limit={data.limit}
offset={data.offset}
total={data.projects.total} />
total={activeProjects.length} />
<!-- Archived Projects Section -->
<ArchiveProject