diff --git a/bun.lock b/bun.lock index f438c612cb..11be84daa3 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@4b3ae78", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@68c295a", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3", "@appwrite.io/pink-legacy": "^1.0.3", @@ -126,7 +126,7 @@ "@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="], - "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@4b3ae78", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@68c295a", { "dependencies": { "json-bigint": "1.0.0" } }, "sha512-P73khuGZlZwk2skLi21X7kdT5/7uwsELpeHUlRj6dqJx0QWiwNXKYksT0gAWhhtNcJPDATwm+/W0qT/lyHnbaw=="], "@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="], diff --git a/package.json b/package.json index ae999bd918..4ddff3cbd4 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@4b3ae78", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@68c295a", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index 0efaed49d9..3142957a8e 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -299,7 +299,9 @@ // on the resource entry. Once the cloud rollout is complete this map can // be removed entirely. const billingAddonNamesFallback: Record = { - addon_baa: 'HIPAA BAA' + addon_baa: 'HIPAA BAA', + addon_premiumGeoDB: 'Premium Geo DB', + addon_premiumGeoDBOrg: 'Premium Geo DB' }; const addons = (currentAggregation?.resources || []) @@ -466,6 +468,21 @@ priceFormatter: ({ amount }) => formatCurrency(amount), includeProgress: false }), + ...resources + .filter((r) => r.resourceId?.startsWith('addon_') && (r.amount ?? 0) > 0) + .map((addon) => + createRow({ + id: `addon-${addon.resourceId}`, + label: + addon.name || + billingAddonNamesFallback[addon.resourceId] || + addon.resourceId, + resource: addon, + usageFormatter: ({ value }) => formatNum(value), + priceFormatter: ({ amount }) => formatCurrency(amount), + includeProgress: false + }) + ), createRow({ id: 'usage-details', label: `Usage details`, diff --git a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte index 77ae89946d..d5ce61951c 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte @@ -18,7 +18,9 @@ import UpdateVariables from '../updateVariables.svelte'; import { page } from '$app/state'; import UpdateLabels from './updateLabels.svelte'; + import PremiumGeoDB from './premiumGeoDB.svelte'; import UpdateOAuth2Server from './updateOAuth2Server.svelte'; + import { isCloud } from '$lib/system'; import type { PageData } from './$types'; import { Alert } from '@appwrite.io/pink-svelte'; import { flags } from '$lib/flags'; @@ -104,6 +106,9 @@ {/if} + {#if isCloud && $canWriteProjects} + + {/if} { depends(Dependencies.PROJECT_VARIABLES); depends(Dependencies.PROJECT_INSTALLATIONS); + depends(Dependencies.ADDONS); const limit = PAGE_LIMIT; const offset = Number(url.searchParams.get('offset') ?? 0); const variablesOffset = Number(url.searchParams.get('variablesOffset') ?? 0); const projectSdk = sdk.forProject(params.region, params.project); - const [variablesResult, installationsResult] = await Promise.allSettled([ - projectSdk.projectApi.listVariables({ - queries: [Query.limit(limit), Query.offset(variablesOffset)] - }), - projectSdk.vcs.listInstallations({ - queries: [Query.limit(limit), Query.offset(offset)] - }) - ]); + const [variablesResult, installationsResult, addonsResult, addonPriceResult] = + await Promise.allSettled([ + projectSdk.projectApi.listVariables({ + queries: [Query.limit(limit), Query.offset(variablesOffset)] + }), + projectSdk.vcs.listInstallations({ + queries: [Query.limit(limit), Query.offset(offset)] + }), + isCloud + ? sdk + .forConsoleIn(params.region) + .projects.listAddons({ projectId: params.project }) + .catch(() => null) + : Promise.resolve(null), + isCloud + ? sdk + .forConsoleIn(params.region) + .projects.getAddonPrice({ + projectId: params.project, + addon: Addon.Premiumgeodb + }) + .catch(() => null) + : Promise.resolve(null) + ]); const variables = variablesResult.status === 'fulfilled' ? variablesResult.value : (() => { - // Read-only users can be blocked from write-adjacent settings APIs. - // Only silence those permission errors so genuine load failures still surface. if (!isReadonlySettingsPermissionError(variablesResult.reason)) { throw variablesResult.reason; } @@ -45,8 +61,6 @@ export const load: PageLoad = async ({ depends, url, params }) => { installationsResult.status === 'fulfilled' ? installationsResult.value : (() => { - // Read-only users can be blocked from write-adjacent settings APIs. - // Only silence those permission errors so genuine load failures still surface. if (!isReadonlySettingsPermissionError(installationsResult.reason)) { throw installationsResult.reason; } @@ -57,11 +71,16 @@ export const load: PageLoad = async ({ depends, url, params }) => { }; })(); + const addons = addonsResult.status === 'fulfilled' ? addonsResult.value : null; + const addonPrice = addonPriceResult.status === 'fulfilled' ? addonPriceResult.value : null; + return { limit, offset, variablesOffset, variables, - installations + installations, + addons, + addonPrice }; }; diff --git a/src/routes/(console)/project-[region]-[project]/settings/premiumGeoDB.svelte b/src/routes/(console)/project-[region]-[project]/settings/premiumGeoDB.svelte new file mode 100644 index 0000000000..c2124d4289 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/settings/premiumGeoDB.svelte @@ -0,0 +1,280 @@ + + + + Premium Geo DB + Give your project richer location intelligence on every user. Tailor pricing by region, surface the + right currency and language, comply with local regulations, and spot suspicious sign-ins before they + cause damage — all without leaving Appwrite. + + +
+ Premium Geo DB +
+ {#if !planSupportsPremiumGeoDB && canUpgradeToPremiumGeoDB} +

+ Premium Geo DB is not available on your current plan. Upgrade your plan to + enable it. +

+ + {:else if !planSupportsPremiumGeoDB} +

+ Premium Geo DB is not available on your current plan. +

+ {:else if isPending} +
+ +
+

+ A payment is awaiting confirmation. If you've completed authentication, click + refresh to check the payment status. +

+ + {:else if isActive} +
+ {#if isScheduledForRemoval} + + {:else} + + {/if} +
+

+ {#if monthlyPriceLabel} + Premium Geo DB is enabled for this project at {monthlyPriceLabel}/month. + {:else} + Premium Geo DB is enabled for this project. + {/if} +

+ {#if isScheduledForRemoval} +

+ Premium Geo DB will be removed at the end of your current billing cycle. +

+ + {:else} + + {/if} + {:else} +

+ Enable Premium Geo DB for this project to collect detailed geolocation data on + every request.{#if monthlyPriceLabel} + This addon costs {monthlyPriceLabel}/month, prorated for your current + billing cycle. + {:else} + Billed prorated for your current cycle. + {/if} +

+ + {/if} +
+
+
+ + + +{#if premiumGeoDBAddon} + +{/if} diff --git a/src/routes/(console)/project-[region]-[project]/settings/premiumGeoDBDisableModal.svelte b/src/routes/(console)/project-[region]-[project]/settings/premiumGeoDBDisableModal.svelte new file mode 100644 index 0000000000..98a36efec6 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/settings/premiumGeoDBDisableModal.svelte @@ -0,0 +1,54 @@ + + + +

+ Are you sure you want to disable the Premium Geo DB addon? The addon will remain active + until the end of your current billing cycle and will not be renewed. +

+ + + + + +
diff --git a/src/routes/(console)/project-[region]-[project]/settings/premiumGeoDBEnableModal.svelte b/src/routes/(console)/project-[region]-[project]/settings/premiumGeoDBEnableModal.svelte new file mode 100644 index 0000000000..322842dbec --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/settings/premiumGeoDBEnableModal.svelte @@ -0,0 +1,144 @@ + + + + {#if addonPrice} +

+ By clicking Enable, the amount of + {formatCurrency(addonPrice.monthlyPrice)} will be added to your subscription and + your payment method will be charged + {formatCurrency(addonPrice.proratedAmount)} immediately for the remaining days in your + billing cycle. +

+ {:else} +

+ By clicking Enable, your payment method will be charged for the prorated amount + for the remaining days in your billing cycle, and the addon will be added to this + project's subscription for future cycles. +

+ {/if} +

+ Premium Geo DB enriches session and request data with premium geolocation details including + timezone, postal code, ISP, connection type, and organization. +

+ + {#if addonPrice} +
+
+ {addonPrice.name} + {formatCurrency(addonPrice.monthlyPrice)} / month +
+
+
+ Due today (prorated) + {formatCurrency(addonPrice.proratedAmount)} +
+

+ * Plus applicable tax and fees +

+
+ {/if} + + + + + +
+ +