Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
Icon,
Layout,
Divider,
Badge
Badge,
Tooltip
} from '@appwrite.io/pink-svelte';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { formatNum } from '$lib/helpers/string';
Expand Down Expand Up @@ -72,6 +73,7 @@
const baseAmount = $derived(currentAggregation?.amount ?? currentPlan?.price ?? 0);
const creditsApplied = $derived(Math.min(baseAmount, availableCredit ?? 0));
const totalAmount = $derived(Math.max(baseAmount - creditsApplied, 0));
const isUpgrading = $derived($organization?.status === 'upgrading');

function formatHumanSize(bytes: number): string {
const size = humanFileSize(bytes || 0);
Expand Down Expand Up @@ -588,62 +590,61 @@

<!-- Actions -->
<div class="actions-container">
{#if !currentPlan.requiresPaymentMethod}
<Layout.Stack
direction="row"
alignItems="center"
justifyContent="flex-end"
gap="s"
wrap="wrap"
class="u-width-full-line actions-mobile">
{#if !currentPlan?.usagePerProject}
<Button text href={`${base}/organization-${$organization?.$id}/usage`}>
View estimated usage
</Button>
{/if}
<Layout.Stack
direction="row"
alignItems="center"
justifyContent="flex-end"
gap="s"
wrap="wrap"
class="u-width-full-line actions-mobile">
<Tooltip disabled={!isUpgrading}>
<div>
Copy link
Member

Choose a reason for hiding this comment

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

instead of usual label in button, may be the label should also change to Change in progress or Payment processing or Upgrade is progress ?

{#if !currentPlan.requiresPaymentMethod}
<Button
text
disabled={$organization?.markedForDeletion || isUpgrading}
href={getChangePlanUrl($organization?.$id)}
on:click={() =>
trackEvent(Click.OrganizationClickUpgrade, {
from: 'button',
source: 'billing_tab'
})}>
Upgrade
</Button>
{:else if $organization?.billingPlanDowngrade !== null}
<Button
text
disabled={isUpgrading}
on:click={() => (showCancel = true)}>
Cancel change
</Button>
Comment on lines +614 to +620
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find the file and check its size
wc -l src/routes/'(console)'/organization-'[organization]'/billing/planSummary.svelte

Repository: appwrite/console

Length of output: 139


🏁 Script executed:

# Get the relevant section around lines 614-620
sed -n '590,650p' src/routes/'(console)'/organization-'[organization]'/billing/planSummary.svelte

Repository: appwrite/console

Length of output: 3119


🏁 Script executed:

# Search for billingPlanDowngrade usage across the codebase
rg -n 'billingPlanDowngrade' --type=svelte --type=ts -C2

Repository: appwrite/console

Length of output: 90


🏁 Script executed:

# Search for markedForDeletion checks in the same file and context
rg -n 'markedForDeletion' src/routes/'(console)'/organization-'[organization]'/billing/planSummary.svelte -C2

Repository: appwrite/console

Length of output: 718


🏁 Script executed:

# Check organization type definition to understand billingPlanDowngrade field
fd -e ts -e d.ts | xargs rg -l 'billingPlanDowngrade' | head -5

Repository: appwrite/console

Length of output: 125


🏁 Script executed:

# Search for organization type definition
fd -type f \( -name "*.d.ts" -o -name "*.ts" \) | xargs rg -l "billingPlanDowngrade" | head -10

Repository: appwrite/console

Length of output: 623


🏁 Script executed:

# Also check for types in store definitions or models
fd -type f -path "*/models/*" -o -path "*/types/*" | xargs rg -l "billingPlanDowngrade" 2>/dev/null | head -10

Repository: appwrite/console

Length of output: 623


🏁 Script executed:

# Look at the +page.ts file for this route to understand the data structure
cat -n src/routes/'(console)'/organization-'[organization]'/billing/+page.ts | head -100

Repository: appwrite/console

Length of output: 4432


🏁 Script executed:

# Search for where billingPlanDowngrade comes from (API/store)
rg -n "billingPlanDowngrade.*=" --type ts -A2 | head -50

Repository: appwrite/console

Length of output: 448


🏁 Script executed:

# Look for organization type definition in `@appwrite.io/console`
find . -name "*.d.ts" -o -name "*.ts" | xargs grep -l "interface.*Organization\|type.*Organization" | grep -E "(models|types)" | head -5

Repository: appwrite/console

Length of output: 871


🏁 Script executed:

# Check the Models import to understand organization structure
rg -n "Models\.Organization" src/routes/'(console)'/organization-'[organization]'/billing/+page.ts -A5 -B5

Repository: appwrite/console

Length of output: 1762


🏁 Script executed:

# Look at how billingPlanDowngrade is used in the template - get more context from planSummary.svelte
sed -n '600,630p' src/routes/'(console)'/organization-'[organization]'/billing/planSummary.svelte

Repository: appwrite/console

Length of output: 1852


🏁 Script executed:

# Check if there are any type assertions or optional chaining patterns for billingPlanDowngrade
rg -n "billingPlanDowngrade" src/routes/'(console)'/organization-'[organization]'/billing/ -B2 -A2

Repository: appwrite/console

Length of output: 4911


🏁 Script executed:

# Check what Models.Organization contains - look at node_modules
find node_modules -path "*console*" -name "*.d.ts" | xargs grep -l "billingPlanDowngrade" 2>/dev/null | head -3

Repository: appwrite/console

Length of output: 42


🏁 Script executed:

# Look at cancelDowngradeModal to see how billingPlanDowngrade is used there
sed -n '1,50p' src/routes/'(console)'/organization-'[organization]'/billing/cancelDowngradeModal.svelte

Repository: appwrite/console

Length of output: 1788


🏁 Script executed:

# Check if there's any validation or type narrowing for billingPlanDowngrade in the component
sed -n '1,100p' src/routes/'(console)'/organization-'[organization]'/billing/planSummary.svelte | grep -A5 -B5 "script"

Repository: appwrite/console

Length of output: 369


🏁 Script executed:

# Let's verify the actual data type/pattern by checking how billingPlanDowngrade is set in organization store
rg -n "billingPlanDowngrade" src/lib/ -B2 -A2 | head -50

Repository: appwrite/console

Length of output: 42


🏁 Script executed:

# Check the actual API contract - look for models in console package
find node_modules -path "*@appwrite.io/console*" -name "index.d.ts" 2>/dev/null | head -1 | xargs cat | grep -A 20 "billingPlanDowngrade" | head -30

Repository: appwrite/console

Length of output: 42


🏁 Script executed:

# Let's look at the entire planSummary.svelte script section to understand the data flow
sed -n '1,150p' src/routes/'(console)'/organization-'[organization]'/billing/planSummary.svelte

Repository: appwrite/console

Length of output: 5860


🏁 Script executed:

# Check if there's documentation on Organization type or if we can find the pattern
rg -n "organization\?\.billingPlan" src/routes/'(console)'/organization-'[organization]'/billing/ | head -20

Repository: appwrite/console

Length of output: 1019


Add markedForDeletion guard and use truthy check for billingPlanDowngrade.

Two bugs in this branch:

  1. undefined !== null evaluates to true, causing the "Cancel change" button to display incorrectly if the field is absent from the API response. The codebase elsewhere (e.g., +page.svelte) uses a truthy check instead: {#if organization?.billingPlanDowngrade}.

  2. Missing markedForDeletion guard — the other two button branches check $organization?.markedForDeletion, but this one does not, leaving the button enabled during organization deletion.

Proposed fix
-                            {:else if $organization?.billingPlanDowngrade !== null}
+                            {:else if $organization?.billingPlanDowngrade}
                                 <Button
                                     text
-                                    disabled={isUpgrading}
+                                    disabled={$organization?.markedForDeletion || isUpgrading}
                                     on:click={() => (showCancel = true)}>
                                     Cancel change
                                 </Button>
📝 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
{:else if $organization?.billingPlanDowngrade !== null}
<Button
text
disabled={isUpgrading}
on:click={() => (showCancel = true)}>
Cancel change
</Button>
{:else if $organization?.billingPlanDowngrade}
<Button
text
disabled={$organization?.markedForDeletion || isUpgrading}
on:click={() => (showCancel = true)}>
Cancel change
</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/`(console)/organization-[organization]/billing/planSummary.svelte
around lines 614 - 620, Replace the exact branch condition that currently checks
"$organization?.billingPlanDowngrade !== null" with a truthy guard and deletion
guard so the button only renders when billingPlanDowngrade is present and the
org is not marked for deletion (use "$organization?.billingPlanDowngrade &&
!$organization?.markedForDeletion"); also ensure the Button's disabled prop
includes the deletion guard (e.g., disabled={isUpgrading ||
$organization?.markedForDeletion}) and keep the on:click handler (showCancel)
as-is.

{:else}
<Button
text
disabled={$organization?.markedForDeletion || isUpgrading}
href={getChangePlanUrl($organization?.$id)}
on:click={() =>
trackEvent(Click.OrganizationClickUpgrade, {
from: 'button',
source: 'billing_tab'
})}>
Change plan
</Button>
{/if}
</div>
<svelte:fragment slot="tooltip">
Your payment is still being processed, check with your payment provider.
</svelte:fragment>
</Tooltip>
{#if !currentPlan?.usagePerProject}
<Button
disabled={$organization?.markedForDeletion}
href={getChangePlanUrl($organization?.$id)}
on:click={() =>
trackEvent(Click.OrganizationClickUpgrade, {
from: 'button',
source: 'billing_tab'
})}>
Upgrade
text={!currentPlan.requiresPaymentMethod}
secondary={currentPlan.requiresPaymentMethod}
href={`${base}/organization-${$organization?.$id}/usage`}>
View estimated usage
</Button>
</Layout.Stack>
{:else}
<Layout.Stack
direction="row"
alignItems="center"
justifyContent="flex-end"
gap="s"
wrap="wrap"
class="u-width-full-line actions-mobile">
{#if $organization?.billingPlanDowngrade !== null}
<Button text on:click={() => (showCancel = true)}>Cancel change</Button>
{:else}
<Button
text
disabled={$organization?.markedForDeletion}
href={getChangePlanUrl($organization?.$id)}
on:click={() =>
trackEvent(Click.OrganizationClickUpgrade, {
from: 'button',
source: 'billing_tab'
})}>
Change plan
</Button>
{/if}
{#if !currentPlan?.usagePerProject}
<Button
secondary
href={`${base}/organization-${$organization?.$id}/usage`}>
View estimated usage
</Button>
{/if}
</Layout.Stack>
{/if}
{/if}
</Layout.Stack>
</div>
</EstimatedCard>
{/key}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
const isTrial =
new Date($organization?.billingStartDate).getTime() - today.getTime() > 0 &&
$organization?.billingTrialDays; /* number of trial days. */
$: isUpgrading = $organization?.status === 'upgrading';

const extraUsage = currentInvoice ? currentInvoice.amount - currentPlan?.price : 0;
</script>
Expand All @@ -41,9 +42,11 @@
A breakdown of your estimated upcoming payment for the current billing period. Totals displayed
exclude accumulated credits and applicable taxes.
<svelte:fragment slot="aside">
<p class="text u-bold">
Due at: {toLocaleDate($organization?.billingNextInvoiceDate)}
</p>
<Layout.Stack gap="xs">
<p class="text u-bold">
Due at: {toLocaleDate($organization?.billingNextInvoiceDate)}
</p>
</Layout.Stack>
<Card.Base variant="secondary" padding="s">
<Layout.Stack>
<Layout.Stack direction="row" justifyContent="space-between">
Expand Down Expand Up @@ -165,50 +168,55 @@
</Card.Base>
</svelte:fragment>
<svelte:fragment slot="actions">
{#if !currentPlan.requiresPaymentMethod}
<div
class="u-flex u-flex-vertical-mobile u-cross-center u-gap-16 u-flex-wrap u-width-full-line u-main-end">
{#if !currentPlan?.usagePerProject}
<Button text href={`${base}/organization-${$organization?.$id}/usage`}>
View estimated usage
</Button>
{/if}
<div
class="u-flex u-flex-vertical-mobile u-cross-center u-gap-16 u-flex-wrap u-width-full-line u-main-end">
<Tooltip disabled={!isUpgrading}>
<div>
{#if !currentPlan.requiresPaymentMethod}
<Button
disabled={$organization?.markedForDeletion || isUpgrading}
href={getChangePlanUrl($organization?.$id)}
on:click={() =>
trackEvent(Click.OrganizationClickUpgrade, {
from: 'button',
source: 'billing_tab'
})}>
Upgrade
</Button>
{:else if $organization?.billingPlanDowngrade !== null}
<Button
text
disabled={isUpgrading}
on:click={() => (showCancel = true)}>
Cancel change
</Button>
Comment on lines +186 to +192
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check type definitions and usage of billingPlanDowngrade
echo "=== SDK type definition for billingPlanDowngrade ==="
rg -n 'billingPlanDowngrade' --type=ts -C3

echo ""
echo "=== Usage in Svelte files ==="
rg -n 'billingPlanDowngrade' --glob '*.svelte' -C3

echo ""
echo "=== Check in models/types files ==="
fd -e ts -e d.ts | xargs rg -l 'billingPlanDowngrade' | head -20

Repository: appwrite/console

Length of output: 6644


🏁 Script executed:

#!/bin/bash
# Look for type definitions and models
echo "=== Search for organization type definition ==="
fd -e d.ts -e ts | xargs rg -l 'billingPlanDowngrade' | grep -E '(types|models|schema)' | head -20

echo ""
echo "=== Check store initialization ==="
rg -n 'organization.*=.*store' --type=ts -C2 | head -30

echo ""
echo "=== Look for organization model/interface definition ==="
rg -n 'interface.*Organization|type.*Organization' --type=ts -C3 | grep -A5 -B5 'billingPlan' | head -40

echo ""
echo "=== Check how organization store is populated ==="
rg -n 'billingPlanDowngrade' src -C5 | grep -E '^\s*(export|const|let|interface|type)' | head -20

Repository: appwrite/console

Length of output: 252


🏁 Script executed:

#!/bin/bash
# Search for stores and organization definition
echo "=== Look for stores directory ==="
find . -name '*store*' -type f | grep -E '\.(ts|js)$' | head -20

echo ""
echo "=== Look for organization in lib files ==="
rg -n 'export.*organization' --type=ts src/lib | head -20

echo ""
echo "=== Check the actual SDK types from Appwrite ==="
rg -n 'Organization' --type=ts src | grep -E 'import|from.*appwrite' | head -20

echo ""
echo "=== Look at layout files to see how organization is passed ==="
rg -n 'organization' src/routes/\(console\)/layout.ts -C5 | head -50

Repository: appwrite/console

Length of output: 3966


🏁 Script executed:

#!/bin/bash
# Read the organization store definition
echo "=== src/lib/stores/organization.ts ==="
cat -n src/lib/stores/organization.ts

echo ""
echo "=== src/routes/(console)/organization-[organization]/billing/store.ts ==="
cat -n src/routes/\(console\)/organization-\[organization\]/billing/store.ts

echo ""
echo "=== Check Models.Organization import ==="
rg -n 'Models\.Organization|from.*models|from.*appwrite' src/lib/stores/organization.ts src/lib/stores/billing.ts | head -20

Repository: appwrite/console

Length of output: 5232


🏁 Script executed:

#!/bin/bash
# Check if other code uses strict !== null or loose checks for similar fields
echo "=== Check null checking patterns in billing files ==="
rg -n '!== null|== null' src/routes/\(console\)/organization-\[organization\]/billing/ --type=svelte | head -20

echo ""
echo "=== Check how billingPlanDowngrade is used after the check ==="
rg -n 'billingPlanDowngrade' src/routes/\(console\)/organization-\[organization\]/billing/planSummaryOld.svelte -A5 -B5

echo ""
echo "=== Look at how the field is accessed elsewhere ==="
rg -n 'billingPlanDowngrade' src/routes/\(console\)/organization-\[organization\]/billing/+page.svelte -A3 -B3

Repository: appwrite/console

Length of output: 1392


🏁 Script executed:

#!/bin/bash
# Check how billingPlanDowngrade is checked in planSummary.svelte as well
echo "=== planSummary.svelte usage ==="
grep -n 'billingPlanDowngrade' src/routes/\(console\)/organization-\[organization\]/billing/planSummary.svelte | head -20

echo ""
echo "=== Check cancelDowngradeModal.svelte ==="
head -50 src/routes/\(console\)/organization-\[organization\]/billing/cancelDowngradeModal.svelte

echo ""
echo "=== Check if there's type definition or comments about billingPlanDowngrade ==="
rg -n 'billingPlanDowngrade' src -C2 | grep -E '(\/\/|\/\*|\*\/|type|interface|optional)' | head -20

echo ""
echo "=== Understand the ternary logic in +page.ts more closely ==="
sed -n '80,90p' src/routes/\(console\)/organization-\[organization\]/billing/+page.ts

Repository: appwrite/console

Length of output: 2433


Use loose equality != null to match the intent of other billing components.

The strict check !== null incorrectly treats undefined as a valid downgrade state. Elsewhere in the billing section (+page.ts line 84, +page.svelte line 132), truthy checks are used consistently. Align this check with that pattern by using loose equality, which treats both null and undefined as falsy:

Recommended fix
-                       {:else if $organization?.billingPlanDowngrade !== null}
+                       {:else if $organization?.billingPlanDowngrade != null}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/routes/`(console)/organization-[organization]/billing/planSummaryOld.svelte
around lines 186 - 192, The conditional currently uses a strict null check
($organization?.billingPlanDowngrade !== null) which treats undefined as valid;
change it to a loose null check ($organization?.billingPlanDowngrade != null) so
both null and undefined are treated the same as elsewhere in billing; update the
branch that renders the Cancel change Button (references:
$organization?.billingPlanDowngrade, isUpgrading, showCancel) to use != null for
consistency with +page.ts/+page.svelte checks.

{:else}
<Button
text
disabled={$organization?.markedForDeletion || isUpgrading}
href={getChangePlanUrl($organization?.$id)}
on:click={() =>
trackEvent(Click.OrganizationClickUpgrade, {
from: 'button',
source: 'billing_tab'
})}>
Change plan
</Button>
{/if}
</div>
<svelte:fragment slot="tooltip">
Your payment is still being processed, check with your payment provider.
</svelte:fragment>
</Tooltip>
{#if !currentPlan?.usagePerProject}
<Button
disabled={$organization?.markedForDeletion}
href={getChangePlanUrl($organization?.$id)}
on:click={() =>
trackEvent(Click.OrganizationClickUpgrade, {
from: 'button',
source: 'billing_tab'
})}>
Upgrade
text={!currentPlan.requiresPaymentMethod}
secondary={currentPlan.requiresPaymentMethod}
href={`${base}/organization-${$organization?.$id}/usage`}>
View estimated usage
</Button>
</div>
{:else}
<div
class="u-flex u-flex-vertical-mobile u-cross-center u-gap-16 u-flex-wrap u-width-full-line u-main-end">
{#if $organization?.billingPlanDowngrade !== null}
<Button text on:click={() => (showCancel = true)}>Cancel change</Button>
{:else}
<Button
text
disabled={$organization?.markedForDeletion}
href={getChangePlanUrl($organization?.$id)}
on:click={() =>
trackEvent(Click.OrganizationClickUpgrade, {
from: 'button',
source: 'billing_tab'
})}>
Change plan
</Button>
{/if}
{#if !currentPlan?.usagePerProject}
<Button secondary href={`${base}/organization-${$organization?.$id}/usage`}>
View estimated usage
</Button>
{/if}
</div>
{/if}
{/if}
</div>
</svelte:fragment>
</CardGrid>
{/if}
Expand Down