mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'feat-profiles' of https://github.com/appwrite/console into feat-profiles-polishing
This commit is contained in:
@@ -57,3 +57,8 @@ EXPOSE 80
|
||||
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/build /usr/share/nginx/html/console
|
||||
|
||||
# feat-profiles
|
||||
RUN mkdir -p /app
|
||||
COPY docker/generate-env.sh /app/generate-env.sh
|
||||
RUN chmod +x /app/generate-env.sh
|
||||
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Regenerate env.js from runtime PUBLIC_ environment variables
|
||||
|
||||
echo "========================================="
|
||||
echo "Regenerating env.js from environment variables..."
|
||||
echo "========================================="
|
||||
|
||||
# Check if any PUBLIC_ env vars exist
|
||||
public_vars=$(printenv | grep '^PUBLIC_' | awk -F= '{print $1}' | sort)
|
||||
|
||||
if [ -z "$public_vars" ]; then
|
||||
echo "⚠ WARNING: No PUBLIC_ environment variables found!"
|
||||
echo "The application may not work correctly."
|
||||
echo "Expected variables like:"
|
||||
echo " - PUBLIC_APPWRITE_ENDPOINT"
|
||||
echo " - PUBLIC_CONSOLE_MODE"
|
||||
echo " - PUBLIC_CONSOLE_PROFILE"
|
||||
echo "========================================="
|
||||
fi
|
||||
|
||||
# Build JSON object from PUBLIC_ env vars
|
||||
env_json="{"
|
||||
first=true
|
||||
|
||||
for var in $public_vars; do
|
||||
value=$(printenv "$var")
|
||||
|
||||
# Escape special characters for JSON
|
||||
escaped_value=$(echo "$value" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g')
|
||||
|
||||
if [ "$first" = true ]; then
|
||||
first=false
|
||||
else
|
||||
env_json="$env_json,"
|
||||
fi
|
||||
|
||||
env_json="$env_json\"$var\":\"$escaped_value\""
|
||||
echo " ✓ $var=$value"
|
||||
done
|
||||
|
||||
env_json="$env_json}"
|
||||
|
||||
# Write to env.js
|
||||
# Use CONSOLE_BUILD_PATH env var if set, otherwise try common locations
|
||||
if [ -n "$CONSOLE_BUILD_PATH" ]; then
|
||||
env_file="$CONSOLE_BUILD_PATH/_app/env.js"
|
||||
elif [ -f "/usr/share/nginx/html/console/_app/env.js" ]; then
|
||||
env_file="/usr/share/nginx/html/console/_app/env.js"
|
||||
elif [ -f "./build/_app/env.js" ]; then
|
||||
env_file="./build/_app/env.js"
|
||||
elif [ -f "./_app/env.js" ]; then
|
||||
env_file="./_app/env.js"
|
||||
else
|
||||
echo "⚠ Error: Cannot find env.js file. Set CONSOLE_BUILD_PATH env var to the build directory."
|
||||
echo "Searched in:"
|
||||
echo " - /usr/share/nginx/html/console/_app/env.js"
|
||||
echo " - ./build/_app/env.js"
|
||||
echo " - ./_app/env.js"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Writing to: $env_file"
|
||||
echo "export const env=$env_json" > "$env_file"
|
||||
|
||||
# Verify the file was written
|
||||
if [ ! -f "$env_file" ]; then
|
||||
echo "⚠ Error: Failed to write $env_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Successfully generated $env_file"
|
||||
echo "File contents:"
|
||||
cat "$env_file"
|
||||
|
||||
# NUKE all pre-compressed files so nginx serves fresh content
|
||||
echo "Nuking all .br and .gz files..."
|
||||
find /usr/share/nginx/html/console -type f \( -name "*.br" -o -name "*.gz" \) -delete
|
||||
echo "✓ Nuked all compressed files"
|
||||
|
||||
echo "========================================="
|
||||
echo "Starting nginx..."
|
||||
echo "========================================="
|
||||
|
||||
exec "$@"
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2752",
|
||||
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@5ce6dc7",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@4472521",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
|
||||
Generated
+5
-5
@@ -12,8 +12,8 @@ importers:
|
||||
specifier: ^1.1.24
|
||||
version: 1.1.24(svelte@5.43.6)(zod@3.24.3)
|
||||
'@appwrite.io/console':
|
||||
specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2752
|
||||
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2752
|
||||
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@5ce6dc7
|
||||
version: https://pkg.vc/-/@appwrite/@appwrite.io/console@5ce6dc7
|
||||
'@appwrite.io/pink-icons':
|
||||
specifier: 0.25.0
|
||||
version: 0.25.0
|
||||
@@ -272,8 +272,8 @@ packages:
|
||||
'@analytics/type-utils@0.6.4':
|
||||
resolution: {integrity: sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw==}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2752':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2752}
|
||||
'@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@5ce6dc7':
|
||||
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@5ce6dc7}
|
||||
version: 1.10.0
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1':
|
||||
@@ -3648,7 +3648,7 @@ snapshots:
|
||||
|
||||
'@analytics/type-utils@0.6.4': {}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2752': {}
|
||||
'@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@5ce6dc7': {}
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.43.6)':
|
||||
dependencies:
|
||||
|
||||
@@ -3,11 +3,15 @@ import { base } from '$app/paths';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Searcher } from '../commands';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
|
||||
export const orgSearcher = (async (query: string) => {
|
||||
const { teams } = !isCloud
|
||||
? await sdk.forConsole.teams.list()
|
||||
: await sdk.forConsole.billing.listOrganization();
|
||||
: await sdk.forConsole.billing.listOrganization([
|
||||
Query.equal('platform', resolvedProfile.organizationPlatform)
|
||||
]);
|
||||
|
||||
return teams
|
||||
.filter((organization) => organization.name.toLowerCase().includes(query.toLowerCase()))
|
||||
|
||||
@@ -29,7 +29,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';
|
||||
@@ -97,7 +96,7 @@
|
||||
function isUnarchiveDisabled(): boolean {
|
||||
if (!organization || !currentPlan) return true;
|
||||
|
||||
if (organization.billingPlan === BillingPlan.FREE) {
|
||||
if (isFreePlan(organization.billingPlan)) {
|
||||
const currentProjectCount = organization.projects?.length || 0;
|
||||
const projectLimit = currentPlan.projects || 0;
|
||||
|
||||
@@ -189,6 +188,7 @@
|
||||
}
|
||||
|
||||
import { formatName as formatNameHelper } from '$lib/helpers/string';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
function formatName(name: string, limit: number = 19) {
|
||||
return formatNameHelper(name, limit, $isSmallViewport);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { HeaderAlert } from '$lib/layout';
|
||||
@@ -10,6 +9,7 @@
|
||||
import { backupsBannerId, showPolicyAlert } from '$lib/stores/database';
|
||||
import { IconX } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
function handleClose() {
|
||||
showPolicyAlert.set(false);
|
||||
@@ -18,14 +18,14 @@
|
||||
</script>
|
||||
|
||||
{#if $showPolicyAlert && isCloud && $organization?.$id && page.url.pathname.match(/\/databases\/database-[^/]+$/)}
|
||||
{@const isFreePlan = $organization?.billingPlan === BillingPlan.FREE}
|
||||
{@const isFree = isFreePlan($organization?.billingPlan)}
|
||||
|
||||
{@const subtitle = isFreePlan
|
||||
{@const subtitle = isFree
|
||||
? 'Upgrade your plan to ensure your data stays safe and backed up'
|
||||
: 'Protect your data by quickly adding a backup policy'}
|
||||
|
||||
{@const ctaText = isFreePlan ? 'Upgrade plan' : 'Create policy'}
|
||||
{@const ctaURL = isFreePlan ? $upgradeURL : `${page.url.pathname}/backups`}
|
||||
{@const ctaText = isFree ? 'Upgrade plan' : 'Create policy'}
|
||||
{@const ctaURL = isFree ? $upgradeURL : `${page.url.pathname}/backups`}
|
||||
|
||||
<HeaderAlert type="warning" title="Your database has no backup policy">
|
||||
<svelte:fragment>{subtitle}</svelte:fragment>
|
||||
@@ -35,7 +35,7 @@
|
||||
href={ctaURL}
|
||||
secondary
|
||||
fullWidthMobile
|
||||
event={isFreePlan ? 'backup_banner_upgrade' : 'backup_banner_add'}>
|
||||
event={isFree ? 'backup_banner_upgrade' : 'backup_banner_add'}>
|
||||
<span class="text">{ctaText}</span>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { isCloud, isSelfHosted } from '$lib/system';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { BackupArchive, BackupRestoration } from '$lib/sdk/backups';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
@@ -13,6 +13,7 @@
|
||||
import { getProjectId } from '$lib/helpers/project';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
const backupRestoreItems: {
|
||||
archives: Map<string, BackupArchive>;
|
||||
@@ -125,7 +126,7 @@
|
||||
|
||||
onMount(() => {
|
||||
// fast path: don't subscribe if org is on a free plan or is self-hosted.
|
||||
if (isSelfHosted || (isCloud && $organization?.billingPlan === BillingPlan.FREE)) return;
|
||||
if (isSelfHosted || (isCloud && isFreePlan($organization?.billingPlan))) return;
|
||||
|
||||
return realtime.forProject(page.params.region, 'console', (response) => {
|
||||
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
import { HeaderAlert } from '$lib/layout';
|
||||
import { hideBillingHeaderRoutes, readOnly, tierToPlan, upgradeURL } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
</script>
|
||||
|
||||
{#if $organization?.$id && $organization?.billingPlan === BillingPlan.FREE && $readOnly && !hideBillingHeaderRoutes.includes(page.url.pathname)}
|
||||
{#if $organization?.$id && isFreePlan($organization?.billingPlan) && $readOnly && !hideBillingHeaderRoutes.includes(page.url.pathname)}
|
||||
<HeaderAlert
|
||||
type="error"
|
||||
title={`${$organization.name} usage has reached the ${tierToPlan($organization.billingPlan).name} plan limit`}>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants';
|
||||
import { NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { activeHeaderAlert } from '$routes/(console)/store';
|
||||
@@ -10,6 +10,7 @@
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import { Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
let show = true;
|
||||
|
||||
@@ -24,7 +25,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if show && $organization?.$id && $organization?.billingPlan === BillingPlan.FREE && !page.url.pathname.includes(base + '/account')}
|
||||
{#if show && $organization?.$id && isFreePlan($organization?.billingPlan) && !page.url.pathname.includes(base + '/account')}
|
||||
<GradientBanner on:close={handleClose}>
|
||||
<Layout.Stack
|
||||
gap="m"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { abbreviateNumber, formatCurrency, isWithinSafeRange } from '$lib/helpers/numbers';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Table, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let show = false;
|
||||
export let org: Organization;
|
||||
@@ -17,7 +18,7 @@
|
||||
? new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1).toString()
|
||||
: org?.billingNextInvoiceDate;
|
||||
|
||||
$: isFree = org.billingPlan === BillingPlan.FREE;
|
||||
$: isFree = isFreePlan(org.billingPlan);
|
||||
|
||||
// equal or above means unlimited!
|
||||
const getCorrectSeatsCountValue = (count: number): string | number => {
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
import { upgradeURL } from '$lib/stores/billing';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import { Badge, Layout, Link, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
</script>
|
||||
|
||||
<Base>
|
||||
<Layout.Stack gap="s">
|
||||
{#if isCloud}
|
||||
{#if $organization?.billingPlan !== BillingPlan.FREE}
|
||||
{#if !isFreePlan($organization?.billingPlan)}
|
||||
<Typography.Text variant="m-600">Roles</Typography.Text>
|
||||
<Typography.Text>Owner, Developer, Editor, Analyst and Billing.</Typography.Text>
|
||||
<Typography.Text>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { BillingPlan } from '@appwrite.io/console';
|
||||
|
||||
export function isFreePlan(plan: BillingPlan | string): boolean {
|
||||
switch (plan) {
|
||||
case BillingPlan.Imaginebasic:
|
||||
case BillingPlan.Tier0:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
import { tierToPlan } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { Tooltip } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let title: string;
|
||||
export let tooltipContent =
|
||||
$organization?.billingPlan === BillingPlan.FREE
|
||||
? `Upgrade to add more ${title.toLocaleLowerCase()}`
|
||||
: `You've reached the ${title.toLocaleLowerCase()} limit for the ${
|
||||
tierToPlan($organization?.billingPlan)?.name
|
||||
} plan`;
|
||||
export let tooltipContent = isFreePlan($organization?.billingPlan)
|
||||
? `Upgrade to add more ${title.toLocaleLowerCase()}`
|
||||
: `You've reached the ${title.toLocaleLowerCase()} limit for the ${
|
||||
tierToPlan($organization?.billingPlan)?.name
|
||||
} plan`;
|
||||
export let disabled: boolean;
|
||||
export let buttonText: string;
|
||||
export let buttonMethod: () => void | Promise<void> = () => {};
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import { ContainerButton } from '.';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let title: string;
|
||||
export let serviceId = title.toLocaleLowerCase() as PlanServices;
|
||||
@@ -68,7 +69,7 @@
|
||||
// these can be organization level limitations as well.
|
||||
// we need to migrate this sometime later, but soon!
|
||||
$: hasProjectLimitation =
|
||||
checkForProjectLimitation(serviceId) && $organization?.billingPlan === BillingPlan.FREE;
|
||||
checkForProjectLimitation(serviceId) && isFreePlan($organization?.billingPlan);
|
||||
$: hasUsageFees = hasProjectLimitation
|
||||
? checkForUsageFees($organization?.billingPlan, serviceId)
|
||||
: false;
|
||||
@@ -99,7 +100,7 @@
|
||||
|
||||
{#if services.length}
|
||||
<slot name="alert" {limit} {tier} {title} {upgradeMethod} {hasUsageFees} {services}>
|
||||
{#if $organization?.billingPlan !== BillingPlan.FREE && hasUsageFees}
|
||||
{#if !isFreePlan($organization?.billingPlan) && hasUsageFees}
|
||||
<Alert.Inline status="info">
|
||||
<span class="text">
|
||||
You've reached the {services} limit for the {tier} plan.
|
||||
@@ -149,7 +150,7 @@
|
||||
<p class="text">
|
||||
You are limited to {limit}
|
||||
{title.toLocaleLowerCase()} per project on the {tier} plan.
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}<Link
|
||||
{#if isFreePlan($organization?.billingPlan)}<Link
|
||||
href={$upgradeURL}
|
||||
event="organization_upgrade"
|
||||
eventData={{ from: 'button', source: 'resource_limit_tag' }}
|
||||
@@ -169,7 +170,7 @@
|
||||
<p class="text">
|
||||
You are limited to {limit}
|
||||
{title.toLocaleLowerCase()} per organization on the {tier} plan.
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
{#if isFreePlan($organization?.billingPlan)}
|
||||
<Link href={$upgradeURL}>Upgrade</Link>
|
||||
for additional {title.toLocaleLowerCase()}.
|
||||
{/if}
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
import SideNavigation from '$lib/layout/navigation.svelte';
|
||||
import { hasOnboardingDismissed } from '$lib/helpers/onboarding';
|
||||
import { isSidebarOpen, noWidthTransition } from '$lib/stores/sidebar';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { page } from '$app/stores';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { getSidebarState, isInDatabasesRoute, updateSidebarState } from '$lib/helpers/sidebar';
|
||||
import { isTabletViewport } from '$lib/stores/viewport';
|
||||
import { ProfileMode, resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let showHeader = true;
|
||||
export let showFooter = true;
|
||||
@@ -165,7 +165,7 @@
|
||||
return {
|
||||
name: org.name,
|
||||
$id: org.$id,
|
||||
showUpgrade: billingPlan === BillingPlan.FREE,
|
||||
showUpgrade: isFreePlan(billingPlan),
|
||||
tierName: isCloud ? tierToPlan(billingPlan).name : null,
|
||||
isSelected: $organization?.$id === org.$id
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { asset, resolve } from '$app/paths';
|
||||
import type { ResolvedPathname } from '$app/types';
|
||||
import { PUBLIC_CONSOLE_PROFILE } from '$env/static/public';
|
||||
import { Platform } from '@appwrite.io/console';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { BillingPlan } from '@appwrite.io/console';
|
||||
|
||||
export const enum ProfileMode {
|
||||
STUDIO = 'studio',
|
||||
@@ -10,6 +12,8 @@ export const enum ProfileMode {
|
||||
export type Profile = {
|
||||
id: ProfileMode;
|
||||
platform: string;
|
||||
organizationPlatform: Platform;
|
||||
freeTier: BillingPlan;
|
||||
logo: {
|
||||
src: {
|
||||
dark: string;
|
||||
@@ -42,6 +46,8 @@ export type Profile = {
|
||||
export const base: Profile = {
|
||||
id: ProfileMode.CONSOLE,
|
||||
platform: 'Appwrite',
|
||||
organizationPlatform: Platform.Appwrite,
|
||||
freeTier: BillingPlan.Tier0,
|
||||
logo: {
|
||||
src: {
|
||||
dark: asset('/images/appwrite-logo-dark.svg'),
|
||||
@@ -79,6 +85,8 @@ export const base: Profile = {
|
||||
export const studio: Profile = {
|
||||
id: ProfileMode.STUDIO,
|
||||
platform: 'Imagine',
|
||||
organizationPlatform: Platform.Imagine,
|
||||
freeTier: BillingPlan.Imaginebasic,
|
||||
logo: {
|
||||
src: {
|
||||
dark: asset('/images/imagine-logo-dark.svg'),
|
||||
@@ -114,7 +122,7 @@ export const studio: Profile = {
|
||||
};
|
||||
|
||||
const resolver = $derived(() => {
|
||||
switch (PUBLIC_CONSOLE_PROFILE) {
|
||||
switch (env.PUBLIC_CONSOLE_PROFILE) {
|
||||
case 'studio':
|
||||
return studio;
|
||||
default:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Tier } from '$lib/stores/billing';
|
||||
import type { Campaign } from '$lib/stores/campaigns';
|
||||
import type { Client, Models } from '@appwrite.io/console';
|
||||
import type { Client, Models, Platform } from '@appwrite.io/console';
|
||||
import type { PaymentMethod } from '@stripe/stripe-js';
|
||||
import type { Organization, OrganizationError, OrganizationList } from '../stores/organization';
|
||||
|
||||
@@ -490,6 +490,7 @@ export class Billing {
|
||||
name: string,
|
||||
billingPlan: string,
|
||||
paymentMethodId: string,
|
||||
platform: Platform,
|
||||
billingAddressId: string = null,
|
||||
couponId: string = null,
|
||||
invites: Array<string> = [],
|
||||
@@ -500,6 +501,7 @@ export class Billing {
|
||||
const params = {
|
||||
organizationId,
|
||||
name,
|
||||
platform,
|
||||
billingPlan,
|
||||
paymentMethodId,
|
||||
billingAddressId,
|
||||
|
||||
@@ -41,6 +41,8 @@ import BudgetLimitAlert from '$routes/(console)/organization-[organization]/budg
|
||||
import TeamReadonlyAlert from '$routes/(console)/organization-[organization]/teamReadonlyAlert.svelte';
|
||||
import ProjectsLimit from '$lib/components/billing/alerts/projectsLimit.svelte';
|
||||
import EnterpriseTrial from '$routes/(console)/organization-[organization]/enterpriseTrial.svelte';
|
||||
import { ProfileMode, resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export type Tier = 'tier-0' | 'tier-1' | 'tier-2' | 'auto-1' | 'cont-1' | 'ent-1';
|
||||
|
||||
@@ -321,7 +323,7 @@ export function calculateEnterpriseTrial(org: Organization) {
|
||||
}
|
||||
|
||||
export function calculateTrialDay(org: Organization) {
|
||||
if (org?.billingPlan === BillingPlan.FREE) return false;
|
||||
if (isFreePlan(org?.billingPlan)) return false;
|
||||
const endDate = new Date(org?.billingStartDate);
|
||||
const today = new Date();
|
||||
|
||||
@@ -341,7 +343,7 @@ export async function checkForProjectsLimit(org: Organization, orgProjectCount?:
|
||||
const plan = await sdk.forConsole.billing.getOrganizationPlan(org.$id);
|
||||
if (!plan) return;
|
||||
|
||||
if (plan.$id !== BillingPlan.FREE) return;
|
||||
if (!isFreePlan(plan.$id)) return;
|
||||
if (!org.projects) return;
|
||||
if (org.projects.length > 0) return;
|
||||
|
||||
@@ -373,7 +375,7 @@ export async function checkForUsageLimit(org: Organization) {
|
||||
readOnly.set(false);
|
||||
return;
|
||||
}
|
||||
if (org?.billingPlan !== BillingPlan.FREE) {
|
||||
if (!isFreePlan(org?.billingPlan)) {
|
||||
const { budgetLimit } = org?.billingLimits ?? {};
|
||||
|
||||
if (budgetLimit && budgetLimit >= 100) {
|
||||
@@ -453,7 +455,7 @@ export async function checkForUsageLimit(org: Organization) {
|
||||
}
|
||||
|
||||
export async function checkPaymentAuthorizationRequired(org: Organization) {
|
||||
if (org.billingPlan === BillingPlan.FREE) return;
|
||||
if (isFreePlan(org.billingPlan)) return;
|
||||
|
||||
const invoices = await sdk.forConsole.billing.listInvoices(org.$id, [
|
||||
Query.equal('status', 'requires_authentication')
|
||||
@@ -555,9 +557,10 @@ export async function checkForMandate(org: Organization) {
|
||||
|
||||
export async function checkForMissingPaymentMethod() {
|
||||
const orgs = await sdk.forConsole.billing.listOrganization([
|
||||
Query.notEqual('billingPlan', BillingPlan.FREE),
|
||||
Query.notEqual('billingPlan', resolvedProfile.freeTier),
|
||||
Query.isNull('paymentMethodId'),
|
||||
Query.isNull('backupPaymentMethodId')
|
||||
Query.isNull('backupPaymentMethodId'),
|
||||
Query.equal('platform', resolvedProfile.organizationPlatform)
|
||||
]);
|
||||
if (orgs?.total) {
|
||||
orgMissingPaymentMethod.set(orgs.teams[0]);
|
||||
@@ -572,8 +575,10 @@ export async function checkForMissingPaymentMethod() {
|
||||
|
||||
// Display upgrade banner for new users after 1 week for 30 days
|
||||
export async function checkForNewDevUpgradePro(org: Organization) {
|
||||
if (resolvedProfile.id !== ProfileMode.CONSOLE) return;
|
||||
|
||||
// browser or plan check.
|
||||
if (!browser || org?.billingPlan !== BillingPlan.FREE) return;
|
||||
if (!browser || !isFreePlan(org?.billingPlan)) return;
|
||||
|
||||
// already dismissed by user!
|
||||
if (localStorage.getItem('newDevUpgradePro')) return;
|
||||
@@ -588,7 +593,8 @@ export async function checkForNewDevUpgradePro(org: Organization) {
|
||||
if (now - accountCreated < 1000 * 60 * 60 * 24 * 7) return;
|
||||
|
||||
const organizations = await sdk.forConsole.billing.listOrganization([
|
||||
Query.notEqual('billingPlan', BillingPlan.FREE)
|
||||
Query.notEqual('billingPlan', resolvedProfile.freeTier),
|
||||
Query.equal('platform', resolvedProfile.organizationPlatform)
|
||||
]);
|
||||
|
||||
if (organizations?.total) return;
|
||||
|
||||
@@ -7,6 +7,7 @@ import BackupDatabase from '$lib/components/backupDatabaseAlert.svelte';
|
||||
import { shouldShowNotification } from '$lib/helpers/notifications';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { currentPlan } from '$lib/stores/organization';
|
||||
import { ProfileMode, resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
|
||||
export const database = derived(page, ($page) => $page.data?.database as Models.Database);
|
||||
|
||||
@@ -19,7 +20,10 @@ export async function checkForDatabaseBackupPolicies(
|
||||
projectId: string,
|
||||
database: Models.Database
|
||||
) {
|
||||
// fast path: return if user dismissed the banner
|
||||
// fast path: not the valid profile to show this!
|
||||
if (resolvedProfile.id !== ProfileMode.CONSOLE) return;
|
||||
|
||||
// fast path: return if user dismissed the banner.
|
||||
if (!shouldShowNotification(backupsBannerId)) return;
|
||||
|
||||
let total = 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { page } from '$app/stores';
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import type { Models, Platform } from '@appwrite.io/console';
|
||||
import type { Tier } from './billing';
|
||||
import type { Plan } from '$lib/sdk/billing';
|
||||
|
||||
@@ -35,6 +35,7 @@ export type Organization = Models.Team<Record<string, unknown>> & {
|
||||
status: string;
|
||||
remarks: string;
|
||||
projects: string[];
|
||||
platform: Platform;
|
||||
};
|
||||
|
||||
export type OrganizationList = {
|
||||
|
||||
@@ -22,7 +22,8 @@ import {
|
||||
Tokens,
|
||||
TablesDB,
|
||||
Domains,
|
||||
Realtime
|
||||
Realtime,
|
||||
Organizations
|
||||
} from '@appwrite.io/console';
|
||||
import { Billing } from '../sdk/billing';
|
||||
import { Backups } from '../sdk/backups';
|
||||
@@ -95,7 +96,8 @@ function createConsoleSdk(client: Client) {
|
||||
sites: new Sites(client),
|
||||
domains: new Domains(client),
|
||||
storage: new Storage(client),
|
||||
realtime: new Realtime(client)
|
||||
realtime: new Realtime(client),
|
||||
organizations: new Organizations(client)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_AI_SERVICE_BASE_URL } from '$env/static/public';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { get } from 'svelte/store';
|
||||
@@ -287,9 +286,9 @@ export async function initImagine(
|
||||
if (!configInitialized) {
|
||||
initImagineConfig(
|
||||
{
|
||||
AI_SERVICE_ENDPOINT: PUBLIC_AI_SERVICE_BASE_URL,
|
||||
APPWRITE_ENDPOINT: PUBLIC_APPWRITE_ENDPOINT,
|
||||
APPWRITE_SITES_BASE_URL: 'https://stage.appwrite.network'
|
||||
AI_SERVICE_ENDPOINT: env.PUBLIC_AI_SERVICE_BASE_URL,
|
||||
APPWRITE_ENDPOINT: env.PUBLIC_APPWRITE_ENDPOINT,
|
||||
APPWRITE_SITES_BASE_URL: ''
|
||||
},
|
||||
{
|
||||
initialTheme: get(app).themeInUse,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { BillingPlan, INTERVAL } from '$lib/constants';
|
||||
import { INTERVAL } from '$lib/constants';
|
||||
import Footer from '$lib/layout/footer.svelte';
|
||||
import Shell from '$lib/layout/shell.svelte';
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import type { LayoutData } from './$types';
|
||||
import Studio from '$lib/studio/studio.svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let data: LayoutData;
|
||||
|
||||
@@ -306,7 +307,7 @@
|
||||
checkForMarkedForDeletion(org);
|
||||
await checkForNewDevUpgradePro(org);
|
||||
|
||||
if (org?.billingPlan !== BillingPlan.FREE) {
|
||||
if (!isFreePlan(org.billingPlan)) {
|
||||
await paymentExpired(org);
|
||||
await checkPaymentAuthorizationRequired(org);
|
||||
await checkForMandate(org);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { Icon, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
const {
|
||||
data
|
||||
@@ -39,14 +40,14 @@
|
||||
function isOrganizationOnTrial(organization: Organization): boolean {
|
||||
if (!organization?.billingTrialStartDate) return false;
|
||||
if ($daysLeftInTrial <= 0) return false;
|
||||
if (organization.billingPlan === BillingPlan.FREE) return false;
|
||||
if (isFreePlan(organization.billingPlan)) return false;
|
||||
|
||||
return !!$plansInfo.get(organization.billingPlan)?.trialDays;
|
||||
}
|
||||
|
||||
function isNonPayingOrganization(organization: Organization): boolean {
|
||||
return (
|
||||
organization?.billingPlan === BillingPlan.FREE ||
|
||||
isFreePlan(organization?.billingPlan) ||
|
||||
organization?.billingPlan === BillingPlan.GITHUB_EDUCATION
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,19 @@ import { getLimit, getPage, pageToOffset } from '$lib/helpers/load';
|
||||
import { CARD_LIMIT } from '$lib/constants';
|
||||
import type { PageLoad } from './$types';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
|
||||
export const load: PageLoad = async ({ url, route }) => {
|
||||
const page = getPage(url);
|
||||
const limit = getLimit(url, route, CARD_LIMIT);
|
||||
const offset = pageToOffset(page, limit);
|
||||
|
||||
const queries = [Query.offset(offset), Query.limit(limit), Query.orderDesc('')];
|
||||
const queries = [
|
||||
Query.offset(offset),
|
||||
Query.limit(limit),
|
||||
Query.orderDesc(''),
|
||||
Query.equal('platform', resolvedProfile.organizationPlatform)
|
||||
];
|
||||
|
||||
const organizations = !isCloud
|
||||
? await sdk.forConsole.teams.list({ queries })
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
name,
|
||||
billingPlan,
|
||||
paymentMethodId,
|
||||
resolvedProfile.organizationPlatform,
|
||||
null,
|
||||
couponData.code ? couponData.code : null,
|
||||
collaborators,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isSameDay } from '$lib/helpers/date';
|
||||
import { type BottomModalAlertItem, showBottomModalAlert } from '$lib/stores/bottom-alerts';
|
||||
import DbOperatorsDark from '$lib/images/promos/db-operators-dark.png';
|
||||
import DbOperatorsLight from '$lib/images/promos/db-operators-light.png';
|
||||
import { ProfileMode, resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
|
||||
const listOfPromotions: BottomModalAlertItem[] = [];
|
||||
|
||||
@@ -30,6 +31,9 @@ if (isCloud) {
|
||||
}
|
||||
|
||||
export function addBottomModalAlerts() {
|
||||
// fast path: not the valid profile to show this!
|
||||
if (resolvedProfile.id !== ProfileMode.CONSOLE) return;
|
||||
|
||||
listOfPromotions.forEach((promotion) => showBottomModalAlert(promotion));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import EstimatedTotalBox from '$lib/components/billing/estimatedTotalBox.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing.js';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -108,12 +109,13 @@
|
||||
try {
|
||||
let org: Organization | OrganizationError;
|
||||
|
||||
if (selectedPlan === BillingPlan.FREE) {
|
||||
if (isFreePlan(selectedPlan)) {
|
||||
org = await sdk.forConsole.billing.createOrganization(
|
||||
ID.unique(),
|
||||
name,
|
||||
BillingPlan.FREE,
|
||||
resolvedProfile.freeTier,
|
||||
null,
|
||||
resolvedProfile.organizationPlatform,
|
||||
null
|
||||
);
|
||||
} else {
|
||||
@@ -122,6 +124,7 @@
|
||||
name,
|
||||
selectedPlan,
|
||||
paymentMethodId,
|
||||
resolvedProfile.organizationPlatform,
|
||||
null,
|
||||
selectedCoupon?.code,
|
||||
collaborators,
|
||||
@@ -205,7 +208,7 @@
|
||||
<PlanSelection bind:billingPlan={selectedPlan} isNewOrg />
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{#if selectedPlan !== BillingPlan.FREE}
|
||||
{#if !isFreePlan(selectedPlan)}
|
||||
<Fieldset legend="Payment">
|
||||
<Layout.Stack gap="s" alignItems="flex-start">
|
||||
<SelectPaymentMethod
|
||||
@@ -242,7 +245,7 @@
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if selectedPlan !== BillingPlan.FREE}
|
||||
{#if !isFreePlan(selectedPlan)}
|
||||
<EstimatedTotalBox
|
||||
billingPlan={selectedPlan}
|
||||
{collaborators}
|
||||
|
||||
@@ -25,8 +25,9 @@
|
||||
organization = await sdk.forConsole.billing.createOrganization(
|
||||
ID.unique(),
|
||||
organizationName,
|
||||
BillingPlan.FREE,
|
||||
resolvedProfile.freeTier,
|
||||
null,
|
||||
resolvedProfile.organizationPlatform,
|
||||
null
|
||||
);
|
||||
|
||||
|
||||
@@ -44,8 +44,9 @@ export const load: PageLoad = async ({ parent }) => {
|
||||
const org = await sdk.forConsole.billing.createOrganization(
|
||||
ID.unique(),
|
||||
'Personal projects',
|
||||
BillingPlan.FREE,
|
||||
resolvedProfile.freeTier,
|
||||
null,
|
||||
resolvedProfile.organizationPlatform,
|
||||
null
|
||||
);
|
||||
trackEvent(Submit.OrganizationCreate, {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { redirect } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { base } from '$app/paths';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export const load: PageLoad = async ({ parent, depends }) => {
|
||||
const { organization, scopes, currentPlan, countryList, locale } = await parent();
|
||||
@@ -54,7 +55,7 @@ export const load: PageLoad = async ({ parent, depends }) => {
|
||||
|
||||
const areCreditsSupported = isCloud
|
||||
? (currentPlan?.supportsCredits ??
|
||||
(organization.billingPlan !== BillingPlan.FREE &&
|
||||
(!isFreePlan(organization.billingPlan) &&
|
||||
organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION))
|
||||
: false;
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import AddCreditModal from './addCreditModal.svelte';
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import { upgradeURL } from '$lib/stores/billing';
|
||||
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Alert, Badge, Icon, Link, Table, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let areCreditsSupported: boolean;
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<CardGrid hideFooter={$organization?.billingPlan !== BillingPlan.FREE}>
|
||||
<CardGrid hideFooter={!isFreePlan($organization?.billingPlan)}>
|
||||
<svelte:fragment slot="title">
|
||||
{!areCreditsSupported ? 'Credits' : 'Available credit'}
|
||||
</svelte:fragment>
|
||||
@@ -172,7 +172,7 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
{#if isFreePlan($organization?.billingPlan)}
|
||||
<Button
|
||||
secondary
|
||||
href={$upgradeURL}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { IconTrash } from '@appwrite.io/pink-icons-svelte';
|
||||
import InputSelect from '$lib/elements/forms/inputSelect.svelte';
|
||||
import type { Plan } from '$lib/sdk/billing';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let organization: Organization;
|
||||
export let currentPlan: Plan;
|
||||
@@ -148,7 +149,7 @@
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Form onSubmit={updateBudget}>
|
||||
{#if organization?.billingPlan === BillingPlan.FREE || organization?.billingPlan === BillingPlan.GITHUB_EDUCATION}
|
||||
{#if isFreePlan(organization?.billingPlan) || organization?.billingPlan === BillingPlan.GITHUB_EDUCATION}
|
||||
<Button
|
||||
secondary
|
||||
href={$upgradeURL}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputNumber, InputSwitch } from '$lib/elements/forms';
|
||||
import { showUsageRatesModal, upgradeURL } from '$lib/stores/billing';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -11,6 +11,7 @@
|
||||
import { Alert, Link } from '@appwrite.io/pink-svelte';
|
||||
import BudgetAlert from './budgetAlert.svelte';
|
||||
import type { Plan } from '$lib/sdk/billing';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let currentPlan: Plan;
|
||||
export let organization: Organization;
|
||||
@@ -89,7 +90,7 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
{#if organization?.billingPlan === BillingPlan.FREE}
|
||||
{#if isFreePlan(organization?.billingPlan)}
|
||||
<Button
|
||||
secondary
|
||||
href={$upgradeURL}
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import Confirm from '$lib/components/confirm.svelte';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let showDelete = false;
|
||||
export let isBackup = false;
|
||||
@@ -16,7 +17,7 @@
|
||||
let error: string;
|
||||
|
||||
async function removeDefaultMethod() {
|
||||
if ($organization?.billingPlan !== BillingPlan.FREE && !hasOtherMethod) return;
|
||||
if (!isFreePlan($organization?.billingPlan) && !hasOtherMethod) return;
|
||||
|
||||
try {
|
||||
await sdk.forConsole.billing.removeOrganizationPaymentMethod($organization.$id);
|
||||
@@ -35,7 +36,7 @@
|
||||
}
|
||||
}
|
||||
async function removeBackuptMethod() {
|
||||
if ($organization?.billingPlan !== BillingPlan.FREE && !hasOtherMethod) return;
|
||||
if (!isFreePlan($organization?.billingPlan) && !hasOtherMethod) return;
|
||||
showDelete = false;
|
||||
|
||||
try {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, CreditCardBrandImage, CreditCardInfo } from '$lib/components';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { type Organization } from '$lib/stores/organization';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
@@ -33,6 +33,7 @@
|
||||
IconSwitchHorizontal,
|
||||
IconTrash
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let organization: Organization;
|
||||
export let methods: PaymentList;
|
||||
@@ -332,5 +333,5 @@
|
||||
bind:showDelete
|
||||
{hasOtherMethod}
|
||||
isBackup={isSelectedBackup}
|
||||
disabled={organization?.billingPlan !== BillingPlan.FREE && !hasOtherMethod} />
|
||||
disabled={!isFreePlan(organization?.billingPlan) && !hasOtherMethod} />
|
||||
{/if}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import { isSmallViewport, isTabletViewport } from '$lib/stores/viewport';
|
||||
import CancelDowngradeModel from './cancelDowngradeModal.svelte';
|
||||
import { IconTag } from '@appwrite.io/pink-icons-svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let currentPlan: Plan;
|
||||
export let nextPlan: Plan | null = null;
|
||||
@@ -497,7 +498,7 @@
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="actions-container">
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE || $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION}
|
||||
{#if isFreePlan($organization?.billingPlan) || $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION}
|
||||
<div
|
||||
class="u-flex u-cross-center u-gap-8 u-flex-wrap u-width-full-line u-main-end actions-mobile">
|
||||
{#if !currentPlan?.usagePerProject}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { IconInfo, IconTag } from '@appwrite.io/pink-icons-svelte';
|
||||
import CancelDowngradeModel from './cancelDowngradeModal.svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let currentPlan: Plan;
|
||||
export let currentInvoice: Invoice | undefined = undefined;
|
||||
@@ -139,7 +140,7 @@
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
|
||||
{#if $organization?.billingPlan !== BillingPlan.FREE && $organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION}
|
||||
{#if !isFreePlan($organization?.billingPlan) && $organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION}
|
||||
<Divider />
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Typography.Text color="--fgcolor-neutral-primary" variant="m-500">
|
||||
@@ -169,7 +170,7 @@
|
||||
</Card.Base>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE || $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION}
|
||||
{#if isFreePlan($organization?.billingPlan) || $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION}
|
||||
<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}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { HeaderAlert } from '$lib/layout';
|
||||
import { hideBillingHeaderRoutes, readOnly, showBudgetAlert } from '$lib/stores/billing';
|
||||
import { base } from '$app/paths';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
$: redirectUrl = `${base}/organization-${$organization.$id}/billing#update-budget`;
|
||||
</script>
|
||||
|
||||
{#if $showBudgetAlert && $organization?.$id && $organization?.billingPlan !== BillingPlan.FREE && $readOnly && !hideBillingHeaderRoutes.includes(page.url.pathname)}
|
||||
{#if $showBudgetAlert && $organization?.$id && !isFreePlan($organization?.billingPlan) && $readOnly && !hideBillingHeaderRoutes.includes(page.url.pathname)}
|
||||
<HeaderAlert type="error" title="Budget limit reached">
|
||||
<svelte:fragment>
|
||||
This organization has reached its budget limit and is now blocked. To continue using
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing.js';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -315,7 +316,7 @@
|
||||
$: isDowngrade = $plansInfo.get(selectedPlan).order < $currentPlan?.order;
|
||||
$: isButtonDisabled =
|
||||
$organization?.billingPlan === selectedPlan ||
|
||||
(isDowngrade && selectedPlan === BillingPlan.FREE && data.hasFreeOrgs);
|
||||
(isDowngrade && isFreePlan(selectedPlan) && data.hasFreeOrgs);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -345,7 +346,7 @@
|
||||
|
||||
<PlanSelection bind:billingPlan={selectedPlan} selfService={data.selfService} />
|
||||
|
||||
{#if isDowngrade && selectedPlan === BillingPlan.FREE && data.hasFreeOrgs}
|
||||
{#if isDowngrade && isFreePlan(selectedPlan) && data.hasFreeOrgs}
|
||||
<Alert.Inline
|
||||
status="warning"
|
||||
title="You can only have one free organization per account">
|
||||
@@ -377,7 +378,7 @@
|
||||
>you will be charged {price} monthly for {extraMembers} team members.</b>
|
||||
This will be reflected in your next invoice.
|
||||
</Alert.Inline>
|
||||
{:else if selectedPlan === BillingPlan.FREE}
|
||||
{:else if isFreePlan(selectedPlan)}
|
||||
<Alert.Inline
|
||||
status="error"
|
||||
title={`Your organization will switch to ${tierToPlan(selectedPlan).name} plan on ${toLocaleDate(
|
||||
@@ -445,7 +446,7 @@
|
||||
id="members" />
|
||||
</Fieldset>
|
||||
{/if}
|
||||
{#if isDowngrade && selectedPlan === BillingPlan.FREE && !data.hasFreeOrgs}
|
||||
{#if isDowngrade && isFreePlan(selectedPlan) && !data.hasFreeOrgs}
|
||||
<Fieldset legend="Feedback">
|
||||
<Layout.Stack gap="xl">
|
||||
<InputSelect
|
||||
@@ -467,7 +468,7 @@
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if selectedPlan !== BillingPlan.FREE && data.organization.billingPlan !== selectedPlan && data.organization.billingPlan !== BillingPlan.CUSTOM}
|
||||
{#if !isFreePlan(selectedPlan) && data.organization.billingPlan !== selectedPlan && data.organization.billingPlan !== BillingPlan.CUSTOM}
|
||||
<EstimatedTotalBox
|
||||
{collaborators}
|
||||
{isDowngrade}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { PageLoad } from './$types';
|
||||
import type { Organization } from '$lib/stores/organization';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export const load: PageLoad = async ({ depends, parent }) => {
|
||||
const { members, currentPlan, organizations } = await parent();
|
||||
@@ -24,8 +25,8 @@ export const load: PageLoad = async ({ depends, parent }) => {
|
||||
}
|
||||
|
||||
const selfService = currentPlan?.selfService ?? true;
|
||||
const hasFreeOrgs = organizations.teams?.some(
|
||||
(org) => (org as Organization)?.billingPlan === BillingPlan.FREE
|
||||
const hasFreeOrgs = organizations.teams?.some((org) =>
|
||||
isFreePlan((org as Organization)?.billingPlan)
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
+5
-1
@@ -1,13 +1,17 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
|
||||
export const load = async ({ parent, depends }) => {
|
||||
depends(Dependencies.DOMAINS);
|
||||
|
||||
const organizations = !isCloud
|
||||
? await sdk.forConsole.teams.list()
|
||||
: await sdk.forConsole.billing.listOrganization();
|
||||
: await sdk.forConsole.billing.listOrganization([
|
||||
Query.equal('platform', resolvedProfile.organizationPlatform)
|
||||
]);
|
||||
|
||||
const { domain } = await parent();
|
||||
return {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
import { IconGithub, IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Badge, Icon, Layout, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
let areMembersLimited: boolean = $state(false);
|
||||
|
||||
@@ -116,7 +117,7 @@
|
||||
<Badge variant="secondary" content="Education">
|
||||
<Icon icon={IconGithub} size="s" slot="start" />
|
||||
</Badge>
|
||||
{:else if isCloud && organization?.billingPlan === BillingPlan.FREE}
|
||||
{:else if isCloud && isFreePlan(organization?.billingPlan)}
|
||||
<Badge variant="secondary" content="Free"></Badge>
|
||||
{/if}
|
||||
|
||||
@@ -154,7 +155,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
<div slot="tooltip">
|
||||
{organization?.billingPlan === BillingPlan.FREE
|
||||
{isFreePlan(organization?.billingPlan)
|
||||
? 'Upgrade to add more members'
|
||||
: `You've reached the members limit for the ${
|
||||
tierToPlan(organization?.billingPlan)?.name
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
ActionMenu,
|
||||
Tooltip
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { tierToPlan } from '$lib/stores/billing';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
// Calculate if button should be disabled and tooltip should show
|
||||
$: memberCount = data.organizationMembers?.total ?? 0;
|
||||
$: isFreeWithMembers = $organization?.billingPlan === BillingPlan.FREE && memberCount >= 1;
|
||||
$: isFreeWithMembers = isFreePlan($organization?.billingPlan) && memberCount >= 1;
|
||||
$: isButtonDisabled = isCloud ? isFreeWithMembers : false;
|
||||
|
||||
const resend = async (member: Models.Membership) => {
|
||||
@@ -89,7 +89,7 @@
|
||||
</ConsoleButton>
|
||||
</div>
|
||||
<div slot="tooltip">
|
||||
{$organization?.billingPlan === BillingPlan.FREE
|
||||
{isFreePlan($organization?.billingPlan)
|
||||
? 'Upgrade to add more members'
|
||||
: `You've reached the members limit for the ${
|
||||
tierToPlan($organization?.billingPlan)?.name
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import { IconChartSquareBar, IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import type { UsageProjectInfo } from '../../store';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -61,7 +62,7 @@
|
||||
<div class="u-flex u-cross-center u-main-space-between">
|
||||
<Typography.Title>Usage</Typography.Title>
|
||||
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
{#if isFreePlan($organization?.billingPlan)}
|
||||
<Button
|
||||
href={$upgradeURL}
|
||||
on:click={() => {
|
||||
@@ -104,7 +105,7 @@
|
||||
</Link.Anchor>
|
||||
{/if}
|
||||
</p>
|
||||
{:else if $organization.billingPlan === BillingPlan.FREE}
|
||||
{:else if isFreePlan($organization.billingPlan)}
|
||||
<p class="text">
|
||||
If you exceed the limits of the Free plan, services for your organization's projects may
|
||||
be disrupted.
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@
|
||||
import { IconInfo, IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon, Layout, Table, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import DualTimeView from '$lib/components/dualTimeView.svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let members: Models.MembershipList;
|
||||
|
||||
@@ -21,7 +22,7 @@
|
||||
<svelte:fragment slot="title">Members</svelte:fragment>
|
||||
The number of members in your organization.
|
||||
<svelte:fragment slot="aside">
|
||||
{#if $organization.billingPlan !== BillingPlan.FREE}
|
||||
{#if !isFreePlan($organization?.billingPlan)}
|
||||
<div class="u-flex u-flex-vertical">
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Layout.Stack gap="s" direction="row" alignItems="center">
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
import { trackEvent, Click } from '$lib/actions/analytics';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
import { getServiceLimit, readOnly, upgradeURL } from '$lib/stores/billing';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { hideNotification, shouldShowNotification } from '$lib/helpers/notifications';
|
||||
import { onMount, type ComponentType } from 'svelte';
|
||||
import { canWriteProjects } from '$lib/stores/roles';
|
||||
@@ -41,6 +40,7 @@
|
||||
import ArchiveProject from '$lib/components/archiveProject.svelte';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
|
||||
{#if isCloud && data.organization.billingPlan === BillingPlan.FREE && projectsToArchive.length === 0 && !freePlanAlertDismissed}
|
||||
{#if isCloud && isFreePlan(data.organization.billingPlan) && projectsToArchive.length === 0 && !freePlanAlertDismissed}
|
||||
<Alert.Inline dismissible on:dismiss={dismissFreePlanAlert}>
|
||||
<Typography.Text
|
||||
>Your Free plan includes up to 2 projects and limited resources. Upgrade to unlock
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import { ID } from '@appwrite.io/console';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { upgradeURL } from '$lib/stores/billing';
|
||||
import CreatePolicy from './database-[database]/backups/createPolicy.svelte';
|
||||
@@ -15,6 +14,7 @@
|
||||
import { Alert, Icon, Tag } from '@appwrite.io/pink-svelte';
|
||||
import { IconPencil } from '@appwrite.io/pink-icons-svelte';
|
||||
import { page } from '$app/state';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let showCreate = false;
|
||||
let totalPolicies: UserBackupPolicy[] = [];
|
||||
@@ -132,7 +132,7 @@
|
||||
<CustomId bind:show={showCustomId} name="Database" bind:id autofocus={false} />
|
||||
|
||||
{#if isCloud}
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
{#if isFreePlan($organization?.billingPlan)}
|
||||
<Alert.Inline title="This database won't be backed up" status="warning">
|
||||
Upgrade your plan to ensure your data stays safe and backed up.
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.migrations.createCsvMigration({
|
||||
.migrations.createCSVImport({
|
||||
bucketId: file.bucketId,
|
||||
fileId: file.$id,
|
||||
resourceId: `${page.params.database}:${page.params.table}`,
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { CustomId } from '$lib/components';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { InputSelect, InputText } from '$lib/elements/forms';
|
||||
import Link from '$lib/elements/link.svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
import { upgradeURL } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { isCloud } from '$lib/system';
|
||||
@@ -78,7 +78,7 @@
|
||||
disabled={specificationOptions.length < 1}
|
||||
options={specificationOptions}
|
||||
bind:value={specification} />
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
{#if isFreePlan($organization?.billingPlan)}
|
||||
<Input.Helper state="default">
|
||||
<Link href={$upgradeURL} variant="muted">Upgrade</Link> to Pro or Scale to adjust
|
||||
your CPU and RAM beyond the default.
|
||||
|
||||
+3
-2
@@ -2,7 +2,7 @@
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputSelect } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
@@ -14,6 +14,7 @@
|
||||
import { isCloud } from '$lib/system';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { page } from '$app/state';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let func: Models.Function;
|
||||
export let specs: Models.SpecificationList;
|
||||
@@ -89,7 +90,7 @@
|
||||
disabled={options.length < 1}
|
||||
bind:value={specification}
|
||||
{options} />
|
||||
{#if isCloud && $organization.billingPlan === BillingPlan.FREE}
|
||||
{#if isCloud && isFreePlan($organization?.billingPlan)}
|
||||
<Alert.Inline title="Customizing specs available with Pro or Scale plans">
|
||||
Upgrade to Pro or Scale to adjust your CPU and RAM beyond the default.
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import InputPassword from '$lib/elements/forms/inputPassword.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import InputNumber from '$lib/elements/forms/inputNumber.svelte';
|
||||
@@ -18,6 +18,7 @@
|
||||
import InputSelect from '$lib/elements/forms/inputSelect.svelte';
|
||||
import { upgradeURL } from '$lib/stores/billing';
|
||||
import { Link, Selector, Alert } from '@appwrite.io/pink-svelte';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
let enabled = false;
|
||||
let senderName: string;
|
||||
@@ -123,7 +124,7 @@
|
||||
<Link.Anchor href={`${base}/project-${$project.region}-${$project.$id}/auth/templates`}
|
||||
>here</Link.Anchor>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if $organization.billingPlan === BillingPlan.FREE}
|
||||
{#if isFreePlan($organization?.billingPlan)}
|
||||
<Alert.Inline
|
||||
status="info"
|
||||
title="Custom SMTP is a Pro plan feature. Upgrade to enable custom SMTP sever.">
|
||||
@@ -198,7 +199,7 @@
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
submit
|
||||
disabled={isButtonDisabled || $organization.billingPlan === BillingPlan.FREE}>
|
||||
disabled={isButtonDisabled || isFreePlan($organization?.billingPlan)}>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
|
||||
+3
-2
@@ -15,6 +15,7 @@
|
||||
import { Accordion, Icon, Layout, Link, Table, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { IconChartSquareBar } from '@appwrite.io/pink-icons-svelte';
|
||||
import { page } from '$app/state';
|
||||
import { isFreePlan } from '$lib/helpers/billing.js';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -52,7 +53,7 @@
|
||||
<div class="u-flex u-cross-center u-main-space-between">
|
||||
<Typography.Title>Usage</Typography.Title>
|
||||
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
{#if isFreePlan($organization?.billingPlan)}
|
||||
<Button href={$upgradeURL}>
|
||||
<span class="text">Upgrade</span>
|
||||
</Button>
|
||||
@@ -73,7 +74,7 @@
|
||||
on:click={() => ($showUsageRatesModal = true)}
|
||||
>Learn more about plan usage limits.</Link.Button>
|
||||
</p>
|
||||
{:else if $organization.billingPlan === BillingPlan.FREE}
|
||||
{:else if isFreePlan($organization?.billingPlan)}
|
||||
<p class="text">
|
||||
If you exceed the limits of the {plan} plan, services for your projects may be disrupted.
|
||||
<Link.Anchor href={$upgradeURL} class="link"
|
||||
|
||||
+3
-2
@@ -2,7 +2,7 @@
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputSelect } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
@@ -13,6 +13,7 @@
|
||||
import { isCloud } from '$lib/system';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { page } from '$app/state';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let site: Models.Site;
|
||||
export let specs: Models.SpecificationList;
|
||||
@@ -82,7 +83,7 @@
|
||||
disabled={options.length < 1}
|
||||
bind:value={specification}
|
||||
{options} />
|
||||
{#if isCloud && $organization.billingPlan === BillingPlan.FREE}
|
||||
{#if isCloud && isFreePlan($organization?.billingPlan)}
|
||||
<Alert.Inline title="Customizing specs available with Pro or Scale plans">
|
||||
Upgrade to Pro or Scale to adjust your CPU and RAM beyond the default.
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
+3
-3
@@ -2,7 +2,6 @@
|
||||
import { Click, Submit, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Button, Form, InputNumber, InputSelect } from '$lib/elements/forms';
|
||||
import { humanFileSize, sizeToBytes } from '$lib/helpers/sizeConvertion';
|
||||
import { createByteUnitPair } from '$lib/helpers/unit';
|
||||
@@ -12,6 +11,7 @@
|
||||
import { updateBucket } from './+page.svelte';
|
||||
import type { Plan } from '$lib/sdk/billing';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { isFreePlan } from '$lib/helpers/billing';
|
||||
|
||||
export let bucket: Models.Bucket;
|
||||
export let currentPlan: Plan | null;
|
||||
@@ -50,11 +50,11 @@
|
||||
The {currentPlan.name} plan has a maximum upload file size limit of {Math.floor(
|
||||
parseInt(size.value)
|
||||
)}{size.unit}.
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
{#if isFreePlan($organization?.billingPlan)}
|
||||
Upgrade to allow files of a larger size.
|
||||
{/if}
|
||||
<svelte:fragment slot="actions">
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
{#if isFreePlan($organization?.billingPlan)}
|
||||
<div class="alert-buttons u-flex">
|
||||
<Button
|
||||
text
|
||||
|
||||
@@ -2,12 +2,12 @@ import { sdk } from '$lib/stores/sdk.js';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { base } from '$app/paths';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { ID, type Models } from '@appwrite.io/console';
|
||||
import { ID, type Models, Query } from '@appwrite.io/console';
|
||||
import type { OrganizationList } from '$lib/stores/organization';
|
||||
import { redirectTo } from '$routes/store';
|
||||
import type { PageLoad } from './$types';
|
||||
import { getRepositoryInfo } from '$lib/helpers/github';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
|
||||
export const load: PageLoad = async ({ parent, url }) => {
|
||||
const { account } = await parent();
|
||||
@@ -66,7 +66,9 @@ export const load: PageLoad = async ({ parent, url }) => {
|
||||
// Get organizations
|
||||
let organizations: Models.TeamList<Record<string, unknown>> | OrganizationList | undefined;
|
||||
if (isCloud) {
|
||||
organizations = await sdk.forConsole.billing.listOrganization();
|
||||
organizations = await sdk.forConsole.billing.listOrganization([
|
||||
Query.equal('platform', resolvedProfile.organizationPlatform)
|
||||
]);
|
||||
} else {
|
||||
organizations = await sdk.forConsole.teams.list();
|
||||
}
|
||||
@@ -77,8 +79,9 @@ export const load: PageLoad = async ({ parent, url }) => {
|
||||
await sdk.forConsole.billing.createOrganization(
|
||||
ID.unique(),
|
||||
'Personal Projects',
|
||||
BillingPlan.FREE,
|
||||
resolvedProfile.freeTier,
|
||||
null,
|
||||
resolvedProfile.organizationPlatform,
|
||||
null
|
||||
);
|
||||
} else {
|
||||
@@ -89,7 +92,9 @@ export const load: PageLoad = async ({ parent, url }) => {
|
||||
}
|
||||
|
||||
if (isCloud) {
|
||||
organizations = await sdk.forConsole.billing.listOrganization();
|
||||
organizations = await sdk.forConsole.billing.listOrganization([
|
||||
Query.equal('platform', resolvedProfile.organizationPlatform)
|
||||
]);
|
||||
} else {
|
||||
organizations = await sdk.forConsole.teams.list();
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import { sdk } from '$lib/stores/sdk.js';
|
||||
import { redirect, error } from '@sveltejs/kit';
|
||||
import { base } from '$app/paths';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { ID, type Models } from '@appwrite.io/console';
|
||||
import { ID, type Models, Query } from '@appwrite.io/console';
|
||||
import type { OrganizationList } from '$lib/stores/organization';
|
||||
import { redirectTo } from '$routes/store';
|
||||
import type { PageLoad } from './$types';
|
||||
import { getRepositoryInfo } from '$lib/helpers/github';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
|
||||
export const load: PageLoad = async ({ parent, url }) => {
|
||||
const { account } = await parent();
|
||||
@@ -83,7 +83,9 @@ export const load: PageLoad = async ({ parent, url }) => {
|
||||
let organizations: Models.TeamList<Record<string, unknown>> | OrganizationList | undefined;
|
||||
|
||||
if (isCloud) {
|
||||
organizations = await sdk.forConsole.billing.listOrganization();
|
||||
organizations = await sdk.forConsole.billing.listOrganization([
|
||||
Query.equal('platform', resolvedProfile.organizationPlatform)
|
||||
]);
|
||||
} else {
|
||||
organizations = await sdk.forConsole.teams.list();
|
||||
}
|
||||
@@ -94,8 +96,9 @@ export const load: PageLoad = async ({ parent, url }) => {
|
||||
await sdk.forConsole.billing.createOrganization(
|
||||
ID.unique(),
|
||||
'Personal Projects',
|
||||
BillingPlan.FREE,
|
||||
null
|
||||
resolvedProfile.freeTier,
|
||||
null,
|
||||
resolvedProfile.organizationPlatform
|
||||
);
|
||||
} else {
|
||||
await sdk.forConsole.teams.create({
|
||||
@@ -106,7 +109,9 @@ export const load: PageLoad = async ({ parent, url }) => {
|
||||
|
||||
// Refetch organizations after creation
|
||||
if (isCloud) {
|
||||
organizations = await sdk.forConsole.billing.listOrganization();
|
||||
organizations = await sdk.forConsole.billing.listOrganization([
|
||||
Query.equal('platform', resolvedProfile.organizationPlatform)
|
||||
]);
|
||||
} else {
|
||||
organizations = await sdk.forConsole.teams.list();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { BillingPlan } from '$lib/constants.js';
|
||||
import { sdk } from '$lib/stores/sdk.js';
|
||||
import { ID, type Models } from '@appwrite.io/console';
|
||||
import { ID, type Models, Query } from '@appwrite.io/console';
|
||||
import { isCloud } from '$lib/system.js';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
import type { OrganizationList } from '$lib/stores/organization.js';
|
||||
import { redirectTo } from '$routes/store.js';
|
||||
import { base } from '$app/paths';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
|
||||
export const load = async ({ parent, url, params }) => {
|
||||
const { account } = await parent();
|
||||
@@ -39,7 +40,11 @@ export const load = async ({ parent, url, params }) => {
|
||||
|
||||
let organizations: Models.TeamList<Record<string, unknown>> | OrganizationList | undefined;
|
||||
if (isCloud) {
|
||||
organizations = account?.$id ? await sdk.forConsole.billing.listOrganization() : undefined;
|
||||
organizations = account?.$id
|
||||
? await sdk.forConsole.billing.listOrganization([
|
||||
Query.equal('platform', resolvedProfile.organizationPlatform)
|
||||
])
|
||||
: undefined;
|
||||
} else {
|
||||
organizations = account?.$id ? await sdk.forConsole.teams.list() : undefined;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ import type { LayoutLoad } from './$types';
|
||||
import { redirectTo } from './store';
|
||||
import { base, resolve } from '$app/paths';
|
||||
import type { Account } from '$lib/stores/user';
|
||||
import type { AppwriteException } from '@appwrite.io/console';
|
||||
import { type AppwriteException, Query } from '@appwrite.io/console';
|
||||
import { isCloud, VARS } from '$lib/system';
|
||||
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
|
||||
export const ssr = false;
|
||||
|
||||
@@ -42,7 +43,9 @@ export const load: LayoutLoad = async ({ depends, url, route }) => {
|
||||
account: account,
|
||||
organizations: !isCloud
|
||||
? await sdk.forConsole.teams.list()
|
||||
: await sdk.forConsole.billing.listOrganization()
|
||||
: await sdk.forConsole.billing.listOrganization([
|
||||
Query.equal('platform', resolvedProfile.organizationPlatform)
|
||||
])
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user