diff --git a/Dockerfile b/Dockerfile index b7a4c303d..44a6fcae7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/docker/generate-env.sh b/docker/generate-env.sh new file mode 100755 index 000000000..82cf94071 --- /dev/null +++ b/docker/generate-env.sh @@ -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 "$@" \ No newline at end of file diff --git a/package.json b/package.json index 6ac79cd4e..80aeca8ff 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b680bc162..ec49e3767 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/src/lib/commandCenter/searchers/organizations.ts b/src/lib/commandCenter/searchers/organizations.ts index 1d959e992..564df0aaf 100644 --- a/src/lib/commandCenter/searchers/organizations.ts +++ b/src/lib/commandCenter/searchers/organizations.ts @@ -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())) diff --git a/src/lib/components/archiveProject.svelte b/src/lib/components/archiveProject.svelte index c7896c991..512b35380 100644 --- a/src/lib/components/archiveProject.svelte +++ b/src/lib/components/archiveProject.svelte @@ -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); } diff --git a/src/lib/components/backupDatabaseAlert.svelte b/src/lib/components/backupDatabaseAlert.svelte index 1c2ceed16..ecf359aa5 100644 --- a/src/lib/components/backupDatabaseAlert.svelte +++ b/src/lib/components/backupDatabaseAlert.svelte @@ -1,6 +1,5 @@ {#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`} {subtitle} @@ -35,7 +35,7 @@ href={ctaURL} secondary fullWidthMobile - event={isFreePlan ? 'backup_banner_upgrade' : 'backup_banner_add'}> + event={isFree ? 'backup_banner_upgrade' : 'backup_banner_add'}> {ctaText} diff --git a/src/lib/components/backupRestoreBox.svelte b/src/lib/components/backupRestoreBox.svelte index f1de9d3ba..85d8cf6c0 100644 --- a/src/lib/components/backupRestoreBox.svelte +++ b/src/lib/components/backupRestoreBox.svelte @@ -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; @@ -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; diff --git a/src/lib/components/billing/alerts/limitReached.svelte b/src/lib/components/billing/alerts/limitReached.svelte index 4885db75e..0b5f0ac5e 100644 --- a/src/lib/components/billing/alerts/limitReached.svelte +++ b/src/lib/components/billing/alerts/limitReached.svelte @@ -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'; -{#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)} diff --git a/src/lib/components/billing/alerts/newDevUpgradePro.svelte b/src/lib/components/billing/alerts/newDevUpgradePro.svelte index dfb9caefe..00eb36b76 100644 --- a/src/lib/components/billing/alerts/newDevUpgradePro.svelte +++ b/src/lib/components/billing/alerts/newDevUpgradePro.svelte @@ -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 @@ } -{#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')} { diff --git a/src/lib/components/roles/upgrade.svelte b/src/lib/components/roles/upgrade.svelte index b3f15e85b..6836f33fa 100644 --- a/src/lib/components/roles/upgrade.svelte +++ b/src/lib/components/roles/upgrade.svelte @@ -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'; {#if isCloud} - {#if $organization?.billingPlan !== BillingPlan.FREE} + {#if !isFreePlan($organization?.billingPlan)} Roles Owner, Developer, Editor, Analyst and Billing. diff --git a/src/lib/helpers/billing.ts b/src/lib/helpers/billing.ts new file mode 100644 index 000000000..0ade94986 --- /dev/null +++ b/src/lib/helpers/billing.ts @@ -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; + } +} diff --git a/src/lib/layout/containerButton.svelte b/src/lib/layout/containerButton.svelte index 53cfc35bf..47fe8b7de 100644 --- a/src/lib/layout/containerButton.svelte +++ b/src/lib/layout/containerButton.svelte @@ -1,17 +1,16 @@ - + {!areCreditsSupported ? 'Credits' : 'Available credit'} @@ -172,7 +172,7 @@ {/if} - {#if $organization?.billingPlan === BillingPlan.FREE} + {#if isFreePlan($organization?.billingPlan)}
- {organization?.billingPlan === BillingPlan.FREE + {isFreePlan(organization?.billingPlan) ? 'Upgrade to add more members' : `You've reached the members limit for the ${ tierToPlan(organization?.billingPlan)?.name diff --git a/src/routes/(console)/organization-[organization]/members/+page.svelte b/src/routes/(console)/organization-[organization]/members/+page.svelte index 05c350080..4ac568c6f 100644 --- a/src/routes/(console)/organization-[organization]/members/+page.svelte +++ b/src/routes/(console)/organization-[organization]/members/+page.svelte @@ -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 @@
- {$organization?.billingPlan === BillingPlan.FREE + {isFreePlan($organization?.billingPlan) ? 'Upgrade to add more members' : `You've reached the members limit for the ${ tierToPlan($organization?.billingPlan)?.name diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte index a9422df60..706a51102 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte @@ -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 @@
Usage - {#if $organization?.billingPlan === BillingPlan.FREE} + {#if isFreePlan($organization?.billingPlan)}