Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
47fe4f6
Project changes: fetch plan values
lohanidamodar Jun 15, 2025
6de7226
plan summary improvement
lohanidamodar Jun 16, 2025
60b61fe
remove logs
lohanidamodar Jun 16, 2025
449fe5a
fix plan selection
lohanidamodar Jun 16, 2025
42bc0c8
format
lohanidamodar Jun 16, 2025
9b04d01
Merge remote-tracking branch 'origin/main' into feat-project-changes-2
lohanidamodar Jun 18, 2025
6f0c948
Merge remote-tracking branch 'origin/feat-project-changes' into feat-…
lohanidamodar Jun 22, 2025
6cf440c
Merge remote-tracking branch 'origin/feat-project-changes' into feat-…
lohanidamodar Jun 24, 2025
4572379
Fix errors
lohanidamodar Jun 24, 2025
ee293bc
handle addons and project usage
lohanidamodar Jun 25, 2025
10ab144
Merge remote-tracking branch 'origin/main' into feat-project-changes-2
lohanidamodar Jun 26, 2025
644b3ce
Merge remote-tracking branch 'origin/main' into feat-project-changes-2
lohanidamodar Jul 29, 2025
0f0c76b
Merge remote-tracking branch 'origin/main' into feat-project-changes-2
lohanidamodar Aug 7, 2025
4498077
add organization usage limits component for plan downgrades
HarshMN2345 Aug 25, 2025
5cd65a3
new archive projects ui
HarshMN2345 Aug 25, 2025
8a1df95
feat: add detailed project usage breakdown to billing plan summary
HarshMN2345 Aug 25, 2025
00890b9
Merge remote-tracking branch 'origin/main' into feat-project-changes-2
lohanidamodar Aug 26, 2025
31c4e14
Merge branch 'feat-project-changes-2' into feat-SER-205-new-pricing-c…
HarshMN2345 Aug 26, 2025
9317f1b
fix layout.ts
lohanidamodar Aug 26, 2025
80a8632
fix: chnages
HarshMN2345 Aug 26, 2025
25c45e4
fix: fetched usage data
HarshMN2345 Aug 26, 2025
19e6dea
added responsivness and progress bar
HarshMN2345 Aug 26, 2025
f9ee8c2
Merge remote-tracking branch 'origin/main' into feat-project-changes-2
lohanidamodar Aug 27, 2025
475e24a
ynamically fetched from the API and small design improvements
HarshMN2345 Aug 27, 2025
339abe7
Merge branch 'feat-project-changes-2' into feat-SER-204-New-Archive-p…
HarshMN2345 Aug 27, 2025
84c101b
layout chnage
HarshMN2345 Aug 27, 2025
7e11182
Merge branch 'feat-SER-204-New-Archive-projects-ui-change' of https:/…
HarshMN2345 Aug 27, 2025
7270f41
fix: only archived shows, and new project goes to archive automatically.
HarshMN2345 Aug 27, 2025
f90cec4
feat: added responsivness and linked transfer project and unarchiving…
HarshMN2345 Aug 28, 2025
ed1bffd
Update src/lib/components/archiveProject.svelte
HarshMN2345 Aug 28, 2025
fb1a19a
Update src/routes/(console)/organization-[organization]/+page.svelte
HarshMN2345 Aug 28, 2025
3c9ae86
feat: some chnages like using classses instead of style and adding fo…
HarshMN2345 Aug 28, 2025
aa56721
gate new pricing UI by plan flag; optimize billing loads
HarshMN2345 Aug 28, 2025
f0fd49e
feat: resposnivenss for table
HarshMN2345 Aug 28, 2025
dcfd692
Merge pull request #2248 from appwrite/feat-SER-206-Free-Tier-Project…
HarshMN2345 Aug 28, 2025
0f32da1
Merge branch 'feat-SER-204-New-Archive-projects-ui-change' into feat-…
HarshMN2345 Aug 28, 2025
1ac6cd9
Merge pull request #2252 from appwrite/feat-SER-205-new-pricing-compo…
HarshMN2345 Aug 28, 2025
2b937ca
fix: version bump
HarshMN2345 Aug 28, 2025
4e92e3b
fix: code improvements suggested by code rabbit
HarshMN2345 Aug 29, 2025
5e134e2
fixed: tooltip issue
HarshMN2345 Aug 29, 2025
a8d3f02
fixed: progressbar for bandwidth
HarshMN2345 Aug 29, 2025
49ad425
feat: implement unlimited seats for Pro and Scale plans
HarshMN2345 Aug 29, 2025
ceed8f2
feat: chnaged logic for calculation fethcing from backend
HarshMN2345 Aug 29, 2025
1228ee2
fix: project selection for archive fix
HarshMN2345 Aug 29, 2025
2530009
fix: fixed colors
HarshMN2345 Aug 31, 2025
637de1c
fix: removed usage tab and estimated usage button
HarshMN2345 Aug 31, 2025
7b68969
fix: fix logic and added cost of additional projects
HarshMN2345 Aug 31, 2025
4232152
update console SDK
lohanidamodar Aug 31, 2025
f3c4246
simplify plan summary
lohanidamodar Aug 31, 2025
7b5e65d
fix issues
lohanidamodar Aug 31, 2025
6e23bb5
fix typo
lohanidamodar Aug 31, 2025
f03aa84
display credits
lohanidamodar Aug 31, 2025
333ad68
remove unused import
lohanidamodar Aug 31, 2025
fce7e92
Merge remote-tracking branch 'origin/main' into feat-SER-204-New-Arch…
lohanidamodar Aug 31, 2025
b0625be
remove unused code
lohanidamodar Aug 31, 2025
e3272cc
fix: small fixes
HarshMN2345 Sep 1, 2025
bed7916
fix: show modal and not the wizard.
ItzNotABug Sep 1, 2025
2477164
Merge remote-tracking branch 'origin/feat-SER-204-New-Archive-project…
ItzNotABug Sep 1, 2025
53a2289
fix: small fix in alert
HarshMN2345 Sep 1, 2025
2df7df2
Merge branch 'feat-SER-204-New-Archive-projects-ui-change' of https:/…
HarshMN2345 Sep 1, 2025
6982ae6
fix: small ui fixes
HarshMN2345 Sep 1, 2025
c0618e4
fix: archive functionality fix
HarshMN2345 Sep 1, 2025
fde80f4
fix: lint errors
HarshMN2345 Sep 1, 2025
d0a1b8c
Merge branch 'main' into feat-SER-204-New-Archive-projects-ui-change
ItzNotABug Sep 1, 2025
c321055
fix: update credits applied calculation in billing plan summary
lohanidamodar Sep 1, 2025
1e45894
fix: show modal and not the wizard.
ItzNotABug Sep 1, 2025
215dcc8
Merge remote-tracking branch 'origin/feat-SER-204-New-Archive-project…
ItzNotABug Sep 1, 2025
b641df1
fix: show modal and not the wizard.
ItzNotABug Sep 1, 2025
ce06db5
fix: messages and bug.
ItzNotABug Sep 1, 2025
5cedece
fix when downgrade
lohanidamodar Sep 1, 2025
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
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
},
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2428",
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "^2.0.0-RC.1",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@2cf27e0",
"@appwrite.io/pink-legacy": "^1.0.3",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@18188b7",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@2cf27e0",
"@faker-js/faker": "^9.9.0",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
Expand Down
30 changes: 15 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

292 changes: 292 additions & 0 deletions src/lib/components/archiveProject.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
<script lang="ts">
import { Button } from '$lib/elements/forms';
import { DropList, GridItem1, CardContainer } from '$lib/components';
import {
Badge,
Icon,
Typography,
Tag,
Accordion,
ActionMenu,
Popover,
Layout
} from '@appwrite.io/pink-svelte';
import {
IconAndroid,
IconApple,
IconCode,
IconFlutter,
IconReact,
IconUnity,
IconInfo,
IconDotsHorizontal,
IconInboxIn,
IconSwitchHorizontal
} from '@appwrite.io/pink-icons-svelte';
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';
import { addNotification } from '$lib/stores/notifications';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { Modal } from '$lib/components';
import { isSmallViewport } from '$lib/stores/viewport';
import { isCloud } from '$lib/system';
import { regions as regionsStore } from '$lib/stores/organization';
import type { Organization } from '$lib/stores/organization';
import type { Plan } from '$lib/sdk/billing';

// props
interface Props {
projectsToArchive: Models.Project[];
organization: Organization;
currentPlan: Plan;
}

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

// Track Read-only info droplist per archived project
let readOnlyInfoOpen = $state<Record<string, boolean>>({});
let showUnarchiveModal = $state(false);
let projectToUnarchive = $state<Models.Project | null>(null);

function filterPlatforms(platforms: { name: string; icon: string }[]) {
return platforms.filter(
(value, index, self) => index === self.findIndex((t) => t.name === value.name)
);
}

function getIconForPlatform(platform: string): ComponentType {
switch (platform) {
case 'code':
return IconCode;
case 'flutter':
return IconFlutter;
case 'apple':
return IconApple;
case 'android':
return IconAndroid;
case 'react-native':
return IconReact;
case 'unity':
return IconUnity;
default:
return IconCode;
}
}

// Check if unarchive should be disabled
function isUnarchiveDisabled(): boolean {
if (!organization || !currentPlan) return true;

if (organization.billingPlan === BillingPlan.FREE) {
const currentProjectCount = organization.projects?.length || 0;
const projectLimit = currentPlan.projects || 0;

return currentProjectCount >= projectLimit;
}

return false;
}

function handleMigrateProject(project: Models.Project) {
goto(`${base}/project-${project.region}-${project.$id}/settings/migrations`);
}

// Handle unarchive project action
async function handleUnarchiveProject(project: Models.Project) {
projectToUnarchive = project;
showUnarchiveModal = true;
}

// Confirm unarchive action
async function confirmUnarchive() {
if (!projectToUnarchive) return;

try {
if (!organization) {
addNotification({
type: 'error',
message: 'Organization not found'
});
return;
}

await sdk.forConsole.projects.updateStatus(projectToUnarchive.$id, Status.Active);

await invalidate(Dependencies.ORGANIZATION);

addNotification({
type: 'success',
message: `${projectToUnarchive.name} has been unarchived`
});

showUnarchiveModal = false;
projectToUnarchive = null;
} catch (error) {
const msg =
error && typeof error === 'object' && 'message' in error
? String((error as { message: string }).message)
: 'Failed to unarchive project';
addNotification({ type: 'error', message: msg });
}
}

function cancelUnarchive() {
showUnarchiveModal = false;
projectToUnarchive = null;
}

function findRegion(project: Models.Project) {
return $regionsStore.regions.find((region) => region.$id === project.region);
}

import { formatName as formatNameHelper } from '$lib/helpers/string';
function formatName(name: string, limit: number = 19) {
return formatNameHelper(name, limit, $isSmallViewport);
}
</script>

{#if projectsToArchive.length > 0}
<div class="archive-projects-margin-top">
<Accordion title="Archived projects" 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.
</Typography.Text>

<div class="archive-projects-margin">
<CardContainer disableEmpty={true} total={projectsToArchive.length}>
{#each projectsToArchive as project}
{@const platforms = filterPlatforms(
project.platforms.map((platform) => getPlatformInfo(platform.type))
)}
{@const formatted = formatName(project.name)}
Comment on lines +165 to +168
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard against undefined project.platforms before mapping

project.platforms may be undefined; calling .map() will throw at runtime. Use nullish-coalescing before mapping.

-                        {@const platforms = filterPlatforms(
-                            project.platforms.map((platform) => getPlatformInfo(platform.type))
-                        )}
+                        {@const platforms = filterPlatforms(
+                            (project.platforms ?? [])
+                                .map((platform) => getPlatformInfo(platform.type))
+                        )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{@const platforms = filterPlatforms(
project.platforms.map((platform) => getPlatformInfo(platform.type))
)}
{@const formatted = formatName(project.name)}
{@const platforms = filterPlatforms(
(project.platforms ?? [])
.map((platform) => getPlatformInfo(platform.type))
)}
{@const formatted = formatName(project.name)}
🤖 Prompt for AI Agents
In src/lib/components/archiveProject.svelte around lines 170 to 173,
project.platforms may be undefined so calling .map() can throw; change the
mapping to operate on a safe default (e.g., project.platforms ?? []) before
calling .map(), then pass that result into filterPlatforms, and keep the
formatted name line as-is.

<GridItem1>
<svelte:fragment slot="eyebrow">
{project?.platforms?.length ? project?.platforms?.length : 'No'} apps
</svelte:fragment>
<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
icon
size="s"
ariaLabel="more options"
on:click={(e) => {
e.preventDefault();
e.stopPropagation();
toggle(e);
}}>
<Icon icon={IconDotsHorizontal} size="s" />
</Button>
<ActionMenu.Root slot="tooltip">
<ActionMenu.Item.Button
leadingIcon={IconInboxIn}
disabled={isUnarchiveDisabled()}
on:click={() => handleUnarchiveProject(project)}
>Unarchive project</ActionMenu.Item.Button>
<ActionMenu.Item.Button
leadingIcon={IconSwitchHorizontal}
on:click={() => handleMigrateProject(project)}
>Migrate project</ActionMenu.Item.Button>
</ActionMenu.Root>
</Popover>
</div>
</svelte:fragment>

{#each platforms.slice(0, 2) as platform}
{@const icon = getIconForPlatform(platform.icon)}
<Badge
variant="secondary"
content={platform.name}
style="width: max-content;">
<Icon {icon} size="s" slot="start" />
</Badge>
{/each}

{#if platforms.length > 3}
<Badge
variant="secondary"
content={`+${platforms.length - 2}`}
style="width: max-content;" />
{/if}
Comment on lines +244 to +249
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Off-by-one: "+N" badge hidden when there are exactly 3 platforms

You render two badges, so the overflow badge should show when length > 2, not > 3.

-                            {#if platforms.length > 3}
+                            {#if platforms.length > 2}
                                 <Badge
                                     variant="secondary"
                                     content={`+${platforms.length - 2}`}
                                     style="width: max-content;" />
                             {/if}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{#if platforms.length > 3}
<Badge
variant="secondary"
content={`+${platforms.length - 2}`}
style="width: max-content;" />
{/if}
{#if platforms.length > 2}
<Badge
variant="secondary"
content={`+${platforms.length - 2}`}
style="width: max-content;" />
{/if}
🤖 Prompt for AI Agents
In src/lib/components/archiveProject.svelte around lines 249 to 254, the
overflow badge condition is off-by-one: it currently uses platforms.length > 3
so the "+N" badge is hidden when there are exactly 3 platforms; change the
conditional to platforms.length > 2 so the badge appears when more than two
platforms are present (keep the content calculation
content={`+${platforms.length - 2}`} as-is).


<svelte:fragment slot="icons">
{#if isCloud && $regionsStore?.regions}
{@const region = findRegion(project)}
<Typography.Text>{region?.name}</Typography.Text>
{/if}
</svelte:fragment>
</GridItem1>
{/each}
</CardContainer>
</div>
</Accordion>
</div>
{/if}

<!-- Unarchive Confirmation Modal -->
<Modal bind:show={showUnarchiveModal} title="Unarchive project" size="s">
<p>Are you sure you want to unarchive <strong>{projectToUnarchive?.name}</strong>?</p>
<p>This will move the project back to your active projects list.</p>

<svelte:fragment slot="footer">
<Layout.Stack direction="row" gap="s" justifyContent="flex-end">
<Button secondary on:click={cancelUnarchive}>Cancel</Button>
<Button on:click={confirmUnarchive}>Unarchive</Button>
</Layout.Stack>
</svelte:fragment>
</Modal>

<style>
.archive-projects-margin-top {
margin-top: 36px;
}

.archive-projects-margin {
margin-top: 16px;
margin-bottom: 36px;
}
.status-container {
display: flex;
align-items: center;
gap: 8px;
}
</style>
Loading