feat: add Premium Geo DB addon to project settings#2981
Conversation
Adds a Premium Geo DB section to the project settings page so users can enable and disable the premium geolocation addon per-project on cloud. The section supports the full addon lifecycle: - Upgrade prompt when the current plan does not support the addon - Enable flow with optional 3DS payment authentication - Pending state with cancel & retry option - Active state with disable action - Scheduled-for-removal state with re-enable action Uses the new project-scoped SDK methods: listAddons, createPremiumGeoDBAddon, and deleteAddon. Bumps the @appwrite.io/console SDK pin accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR adds a Premium Geo DB addon section to project settings, mirroring the existing BAA pattern at the org level. It introduces three new Svelte components, extends the settings page loader to fetch addon data, and updates the billing plan summary to render project-scoped addon charges.
Confidence Score: 3/5The enable flow is broken for all payment types: without The premiumGeoDBEnableModal.svelte and premiumGeoDB.svelte require attention; the disable and page-loader changes are straightforward. Important Files Changed
Reviews (16): Last reviewed commit: "chore: update console SDK to 634f110" | Re-trigger Greptile |
| async function handleReEnable() { | ||
| reEnabling = true; | ||
| try { | ||
| await sdk.forConsoleIn(page.params.region).projects.createPremiumGeoDBAddon({ | ||
| projectId: page.params.project | ||
| }); | ||
| await Promise.all([invalidate(Dependencies.ADDONS), invalidate(Dependencies.PROJECT)]); | ||
| addNotification({ | ||
| message: 'Premium Geo DB addon has been re-enabled', | ||
| type: 'success' | ||
| }); | ||
| } catch (e) { | ||
| addNotification({ | ||
| message: e.message, | ||
| type: 'error' | ||
| }); | ||
| } finally { | ||
| reEnabling = false; | ||
| } | ||
| } |
There was a problem hiding this comment.
handleReEnable silently drops 3DS response
createPremiumGeoDBAddon can return either a Models.Addon (immediate success) or a Models.PaymentAuthentication (3DS required). handleReEnable discards the return value entirely, so if re-enabling requires 3DS the payment challenge is never initiated — the notification fires as "re-enabled" while the addon actually sits in pending state waiting for a payment that will never complete.
The enable modal handles this correctly via the 'clientSecret' in result check and subsequent confirmPayment call. handleReEnable should mirror that same pattern, exactly as BAA.svelte's handleReEnable does.
Mirrors the BAA addon UX by fetching the addon price via organizations.getAddonPrice(Addon.Premiumgeodb) from the settings page loader, passing it through to the Premium Geo DB card and enable modal, and rendering the monthly/prorated breakdown with formatCurrency alongside the Enable CTA. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…name - Each project row in the organization billing breakdown now iterates its resources for addon_* entries (amount > 0) and renders them as child rows (e.g. Premium Geo DB under the project it was enabled on). The backend already filters project-scoped addons out of the team-level resources response, so the org "Addons" section shows only org-scoped addons (BAA, premiumGeoDBOrg) while project-scoped ones surface where they belong. - Org-level addon labels now read addon.name from the UsageResource payload that the getAggregation endpoint populates from billingAddons config. Dropped the hard-coded billingAddonNames map so new addons surface with their proper name without a console update. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…geo-db-addon # Conflicts: # bun.lock # package.json # src/routes/(console)/project-[region]-[project]/settings/+page.svelte
…geo-db-addon # Conflicts: # bun.lock # package.json
…DK methods" This reverts commit 304142c.
…geo-db-addon # Conflicts: # src/routes/(console)/project-[region]-[project]/settings/+page.svelte # src/routes/(console)/project-[region]-[project]/settings/+page.ts
| ...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 | ||
| }) | ||
| ), |
There was a problem hiding this comment.
Possible double-counting of addon charges in plan summary
The top-level addons section (line 252–275) already creates a billing row for every addon_-prefixed resource found in currentAggregation.resources. If the billing API also surfaces those same addon resources inside projectData.resources (the per-project breakdown), each project-scoped addon (e.g. Premium Geo DB) will appear as both a top-level line item and a child row — showing the same charge twice to the user.
Before shipping, confirm whether the cloud billing API places project-level addon charges exclusively in breakdown[].resources (making the top-level filter skip them) or in both places. If the former, this code is correct; if the latter, the top-level addons filter needs to exclude resources that are already accounted for at the project level (or vice-versa).
…geo-db-addon # Conflicts: # bun.lock # package.json
…geo-db-addon # Conflicts: # bun.lock # package.json
| await confirmPayment({ | ||
| clientSecret: paymentAuth.clientSecret, | ||
| paymentMethodId: $organization.paymentMethodId, | ||
| orgId: $organization.$id, | ||
| route: `${settingsUrl}?type=confirm-addon&addonId=${paymentAuth.addonId}` | ||
| }); | ||
| return; |
There was a problem hiding this comment.
confirmPayment outcome is not inspected
confirmPayment resolves with an outcome object (it does not throw on 3DS failures — see BAA.svelte's handleReEnable which checks outcome.status === 'error' and 'requires_action'). Here, the return value is discarded entirely. If the 3DS challenge fails or is cancelled by the user, the function silently returns with no error shown, leaving the user staring at a re-enabled modal with no feedback. At minimum, treat an error outcome the same way the catch block does: error = outcome.message.
| await confirmPayment({ | ||
| clientSecret: paymentAuth.clientSecret, | ||
| paymentMethodId: $organization.paymentMethodId, | ||
| orgId: $organization.$id, | ||
| route: `${settingsUrl}?type=confirm-addon&addonId=${paymentAuth.addonId}` | ||
| }); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Missing
redirectIfRequired: true breaks non-3DS payments
Without redirectIfRequired: true, stripe.confirmPayment is called without redirect: 'if_required', so Stripe always performs a full page redirect — even for cards that don't need 3DS. Every successful payment sends the user to ?type=confirm-addon&addonId=.... Because premiumGeoDB.svelte has no onMount handler to process that return URL, the addon is left in pending state after every enable attempt, regardless of whether 3DS was involved. Compare BAAEnableModal.svelte line 47 which passes redirectIfRequired: true, limiting the redirect to only genuine 3DS challenges.
Summary
Adds a Premium Geo DB section to the project settings page so users can enable and disable the premium geolocation addon per-project on cloud.
The section supports the full addon lifecycle, mirroring the BAA pattern at the organization level:
Uses the new project-scoped SDK methods shipped with the cloud update:
listAddons,createPremiumGeoDBAddon, anddeleteAddon. Bumps the@appwrite.io/consoleSDK pin accordingly.Test plan
🤖 Generated with Claude Code