diff --git a/bun.lock b/bun.lock index 10ce13d787..ba8fe3f958 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@88c189e", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@352239b", "@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", @@ -124,7 +124,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@88c189e", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@352239b", { "dependencies": { "json-bigint": "1.0.0" } }], "@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 a006833186..1f97cb821d 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@88c189e", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@352239b", "@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 84683f8c66..987d1bca6d 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -249,10 +249,6 @@ }; // addons (additional members, projects, etc.) - const billingAddonNames: Record = { - addon_baa: 'HIPAA BAA' - }; - const addons = (currentAggregation?.resources || []) .filter( (r) => @@ -269,8 +265,8 @@ ? 'Additional members' : addon.resourceId === 'projects' ? 'Additional projects' - : (billingAddonNames[addon.resourceId] ?? - `${addon.resourceId} overage (${formatNum(addon.value)})`), + : addon.name || + `${addon.resourceId} overage (${formatNum(addon.value)})`, usage: '', price: formatCurrency(addon.amount) }, @@ -400,6 +396,18 @@ 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 || 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 3961307590..9cb63b24ed 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte @@ -18,6 +18,8 @@ import UpdateVariables from '../updateVariables.svelte'; import { page } from '$app/state'; import UpdateLabels from './updateLabels.svelte'; + import PremiumGeoDB from './premiumGeoDB.svelte'; + import { isCloud } from '$lib/system'; import type { PageData } from './$types'; import { Alert } from '@appwrite.io/pink-svelte'; @@ -97,6 +99,9 @@ + {#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..e2770fb7ef --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/settings/premiumGeoDB.svelte @@ -0,0 +1,188 @@ + + + + Premium Geo DB + Enrich session and request data with premium geolocation details such as timezone, postal code, ISP, + connection type, and organization. Useful for fine-grained analytics, fraud detection, and personalized + user experiences. + + +
+ 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 the payment was interrupted, you can + cancel and retry. +

+ + {: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} + + + + + +
+ +