Skip to content

Project changes: fetch plan values #2026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 41 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4a8420f
Project changes
lohanidamodar May 5, 2025
9ee54f2
default value
lohanidamodar May 5, 2025
0f66b60
fix remove
lohanidamodar May 5, 2025
084ea10
Merge branch 'main' into feat-project-changes
lohanidamodar May 26, 2025
faa4722
Projects limit banner
lohanidamodar May 26, 2025
05d256a
remove log
lohanidamodar May 26, 2025
df73f85
modal to select project
lohanidamodar May 26, 2025
e717c64
save selected projects
lohanidamodar May 26, 2025
5c752ea
Merge remote-tracking branch 'origin/main' into feat-project-changes
lohanidamodar Jun 9, 2025
24c0378
Merge remote-tracking branch 'origin/main' into feat-project-changes
lohanidamodar Jun 12, 2025
8b23355
manage projects to keep
lohanidamodar Jun 12, 2025
49780a6
show tag
lohanidamodar Jun 12, 2025
b57d0fe
format
lohanidamodar Jun 12, 2025
57c79cb
Fix review comments
lohanidamodar Jun 12, 2025
8de6856
remove unused export
lohanidamodar Jun 12, 2025
778e0fa
get projects from page data
lohanidamodar Jun 12, 2025
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
dd82d7d
Merge remote-tracking branch 'origin/main' into feat-project-changes
lohanidamodar Jun 17, 2025
9d46815
refactor date to constant
lohanidamodar Jun 17, 2025
b527bd1
format
lohanidamodar Jun 17, 2025
cd29f5d
disable button
lohanidamodar Jun 17, 2025
b742423
Merge remote-tracking branch 'origin/main' into feat-project-changes
lohanidamodar Jun 18, 2025
e760441
Fix warning showing for wrong tier
lohanidamodar Jun 18, 2025
b042ed1
more fixes
lohanidamodar Jun 18, 2025
46da151
status tag alignment
lohanidamodar Jun 18, 2025
016a05e
truncate title
lohanidamodar Jun 18, 2025
53c1a54
fix date formatting
lohanidamodar Jun 18, 2025
9b04d01
Merge remote-tracking branch 'origin/main' into feat-project-changes-2
lohanidamodar Jun 18, 2025
be2db8a
Merge remote-tracking branch 'origin/main' into feat-project-changes
lohanidamodar Jun 18, 2025
6a63d23
fix compact
lohanidamodar Jun 19, 2025
b1710aa
Merge remote-tracking branch 'origin/main' into feat-project-changes
lohanidamodar Jun 22, 2025
6f0c948
Merge remote-tracking branch 'origin/feat-project-changes' into feat-…
lohanidamodar Jun 22, 2025
847b645
Merge remote-tracking branch 'origin/main' into feat-project-changes
lohanidamodar Jun 24, 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
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
43 changes: 43 additions & 0 deletions src/lib/components/billing/alerts/projectsLimit.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script lang="ts">
import { page } from '$app/state';
import { Click } from '$lib/actions/analytics';
import { Button } from '$lib/elements/forms';
import { HeaderAlert } from '$lib/layout';
import {
billingProjectsLimitDate,
hideBillingHeaderRoutes,
upgradeURL
} from '$lib/stores/billing';
import { currentPlan } from '$lib/stores/organization';
import SelectProjectCloud from '$lib/components/billing/alerts/selectProjectCloud.svelte';
let showSelectProject = false;
</script>

<SelectProjectCloud bind:showSelectProject />

{#if $currentPlan && $currentPlan.projects > 0 && !hideBillingHeaderRoutes.includes(page.url.pathname)}
<HeaderAlert type="warning" title="Action required: You have more than {$currentPlan.projects} projects.">
<svelte:fragment>
Choose which projects to keep before {billingProjectsLimitDate} or upgrade to Pro. Projects
over the limit will be blocked after this date.
</svelte:fragment>
<svelte:fragment slot="buttons">
<Button
compact
on:click={() => {
showSelectProject = true;
}}>Manage projects</Button>
<Button
href={$upgradeURL}
event={Click.OrganizationClickUpgrade}
eventData={{
from: 'button',
source: 'projects_limit_banner'
}}
secondary
fullWidthMobile>
<span class="text">Upgrade</span>
</Button>
</svelte:fragment>
</HeaderAlert>
{/if}
96 changes: 96 additions & 0 deletions src/lib/components/billing/alerts/selectProjectCloud.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<script lang="ts">
import { type Models } from '@appwrite.io/console';
import { Alert, Button, 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 { toLocaleDateTime } from '$lib/helpers/date';

export let showSelectProject: boolean;
export let selectedProjects: Array<string> = [];

let projects: Array<Models.Project> = [];
let error: string | null = null;

onMount(() => {
projects = page.data.projects?.projects || [];
});

let projectsToArchive: Array<Models.Project> = [];

$: projectsToArchive = projects.filter((project) => !selectedProjects.includes(project.$id));

async function updateSelected() {
try {
await sdk.forConsole.billing.updateSelectedProjects(
projects[0].teamId,
selectedProjects
);
showSelectProject = false;
invalidate(Dependencies.ORGANIZATION);
addNotification({
type: 'success',
message: `Projects updated for archiving`
});
} catch (e) {
error = e.message;
}
}
</script>

<Modal bind:show={showSelectProject} title={'Manage projects'} onSubmit={updateSelected}>
<svelte:fragment slot="description">
Choose which two projects to keep. Projects over the limit will be blocked after this date.
</svelte:fragment>
{#if error}
<Alert.Inline status="error" title="Error">{error}</Alert.Inline>
{/if}
<Table.Root
let:root
allowSelection
bind:selectedRows={selectedProjects}
columns={[{ id: 'name' }, { id: 'created' }]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="name" {root}>Project Name</Table.Header.Cell>
<Table.Header.Cell column="created" {root}>Created</Table.Header.Cell>
</svelte:fragment>
{#each projects as project}
<Table.Row.Base {root} id={project.$id}>
<Table.Cell column="name" {root}>{project.name}</Table.Cell>
<Table.Cell column="created" {root}
>{toLocaleDateTime(project.$createdAt)}</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{#if selectedProjects.length > 2}
<div class="u-text-warning u-mb-4">
You can only select two projects. Please deselect others to continue.
</div>
{/if}
{#if selectedProjects.length === 2}
<Alert.Inline
status="warning"
title={`${projects.length - selectedProjects.length} projects will be archived on ${billingProjectsLimitDate}`}>
<span>
{#each projectsToArchive as project, index}
{@const text = `${index === 0 ? '' : ' '}<b>${project.name}</b> `}
{@html text}{#if index < projectsToArchive.length - 1}{#if index == projectsToArchive.length - 2}
and
{/if}{#if index < projectsToArchive.length - 2},
{/if}{/if}
{/each}
will be archived.
</span>
</Alert.Inline>
{/if}
<svelte:fragment slot="footer">
<Button.Button size="s" variant="secondary" on:click={() => (showSelectProject = false)}
>Cancel</Button.Button>
<Button.Button size="s" disabled={selectedProjects.length !== 2}>Save</Button.Button>
</svelte:fragment>
</Modal>
93 changes: 30 additions & 63 deletions src/lib/components/billing/planSelection.svelte
Original file line number Diff line number Diff line change
@@ -1,80 +1,47 @@
<script lang="ts">
import { BASE_BILLING_PLANS, BillingPlan } from '$lib/constants';
import { BillingPlan } from '$lib/constants';
import { formatCurrency } from '$lib/helpers/numbers';
import { plansInfo, type Tier, tierFree, tierPro, tierScale } from '$lib/stores/billing';
import { type Tier } from '$lib/stores/billing';
import { currentPlan, organization } from '$lib/stores/organization';
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
import { LabelCard } from '..';
import type { Plan } from '$lib/sdk/billing';
import { page } from '$app/state';

export let billingPlan: Tier;
export let anyOrgFree = false;
export let isNewOrg = false;
export let selfService = true;

$: freePlan = $plansInfo.get(BillingPlan.FREE);
$: proPlan = $plansInfo.get(BillingPlan.PRO);
$: scalePlan = $plansInfo.get(BillingPlan.SCALE);

$: isBasePlan = BASE_BILLING_PLANS.includes($currentPlan?.$id);
$: plans = Object.values(page.data.plans.plans) as Plan[];
$: currentPlanInList = plans.filter((plan) => plan.$id === $currentPlan?.$id).length > 0;
</script>

<Layout.Stack>
<LabelCard
name="plan"
bind:group={billingPlan}
disabled={anyOrgFree || !selfService}
value={BillingPlan.FREE}
tooltipShow={anyOrgFree}
title={tierFree.name}
tooltipText="You are limited to 1 Free organization per account.">
<svelte:fragment slot="action">
{#if $organization?.billingPlan === BillingPlan.FREE && !isNewOrg}
<Badge variant="secondary" size="xs" content="Current plan" />
{/if}
</svelte:fragment>
<Typography.Caption variant="400">
{tierFree.description}
</Typography.Caption>
<Typography.Text>
{formatCurrency(freePlan?.price ?? 0)}
</Typography.Text>
</LabelCard>
<LabelCard
name="plan"
disabled={!selfService}
bind:group={billingPlan}
value={BillingPlan.PRO}
title={tierPro.name}>
<svelte:fragment slot="action">
{#if $organization?.billingPlan === BillingPlan.PRO && !isNewOrg}
<Badge variant="secondary" size="xs" content="Current plan" />
{/if}
</svelte:fragment>
<Typography.Caption variant="400">
{tierPro.description}
</Typography.Caption>
<Typography.Text>
{formatCurrency(proPlan?.price ?? 0)} per month + usage
</Typography.Text>
</LabelCard>
<LabelCard
name="plan"
bind:group={billingPlan}
value={BillingPlan.SCALE}
title={tierScale.name}>
<svelte:fragment slot="action">
{#if $organization?.billingPlan === BillingPlan.SCALE && !isNewOrg}
<Badge variant="secondary" size="xs" content="Current plan" />
{/if}
</svelte:fragment>
<Typography.Caption variant="400">
{tierScale.description}
</Typography.Caption>
<Typography.Text>
{formatCurrency(scalePlan?.price ?? 0)} per month + usage
</Typography.Text>
</LabelCard>
{#if $currentPlan && !isBasePlan}
{#each plans as plan}
<LabelCard
name="plan"
bind:group={billingPlan}
disabled={!selfService || (plan.$id === BillingPlan.FREE && anyOrgFree)}
tooltipShow={plan.$id === BillingPlan.FREE && anyOrgFree}
value={plan.$id}
title={plan.name}>
<svelte:fragment slot="action">
{#if $organization?.billingPlan === plan.$id && !isNewOrg}
<Badge variant="secondary" size="xs" content="Current plan" />
{/if}
</svelte:fragment>
<Typography.Caption variant="400">
{plan.desc}
</Typography.Caption>
<Typography.Text>
{@const isZeroPrice = (plan.price ?? 0) <= 0}
{@const price = formatCurrency(plan.price ?? 0)}
{isZeroPrice ? price : `${price} per month + usage`}
</Typography.Text>
</LabelCard>
{/each}
{#if $currentPlan && !currentPlanInList}
<LabelCard
name="plan"
bind:group={billingPlan}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/gridItem1.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<slot name="subtitle" />
</div>
</Layout.Stack>
<Layout.Stack direction="row" justifyContent="flex-end" alignItems="center">
<Layout.Stack direction="row" justifyContent="flex-end" alignItems="flex-start">
<slot name="status" />
</Layout.Stack>
</Layout.Stack>
Expand Down
23 changes: 22 additions & 1 deletion src/lib/layout/createProject.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<script lang="ts">
import { Layout, Typography, Input, Tag, Icon } from '@appwrite.io/pink-svelte';
import { Layout, Typography, Input, Tag, Icon, Alert } from '@appwrite.io/pink-svelte';
import { IconPencil } from '@appwrite.io/pink-icons-svelte';
import { CustomId } from '$lib/components/index.js';
import { getFlagUrl } from '$lib/helpers/flag';
import { isCloud } from '$lib/system.js';
import { currentPlan } from '$lib/stores/organization';
import { Button } from '$lib/elements/forms';
import { base } from '$app/paths';
import { page } from '$app/state';
import type { Models } from '@appwrite.io/console';
import { filterRegions } from '$lib/helpers/regions';

Expand All @@ -12,8 +16,10 @@
export let regions: Array<Models.ConsoleRegion> = [];
export let region: string;
export let showTitle = true;
export let projects: number = undefined;

let showCustomId = false;
$: projectsLimited = $currentPlan?.projects > 0 && projects && projects >= $currentPlan?.projects;
</script>

<svelte:head>
Expand All @@ -27,10 +33,24 @@
{#if showTitle}
<Typography.Title size="l">Create your project</Typography.Title>
{/if}
{#if projectsLimited}
<Alert.Inline status="warning" title="You’ve reached your limit of 2 projects">
Extra projects are available on paid plans for an additional fee
<svelte:fragment slot="actions">
<Button
compact
size="s"
href={`${base}/organization-${page.params.organization}/billing`}
external
text>Upgrade</Button>
</svelte:fragment>
</Alert.Inline>
{/if}
<Layout.Stack direction="column" gap="xxl">
<Layout.Stack direction="column" gap="xxl">
<Layout.Stack direction="column" gap="s">
<Input.Text
disabled={projectsLimited}
label="Name"
placeholder="Project name"
required
Expand All @@ -49,6 +69,7 @@
{#if isCloud && regions.length > 0}
<Layout.Stack gap="xs">
<Input.Select
disabled={projectsLimited}
required
bind:value={region}
placeholder="Select a region"
Expand Down
Loading
Loading