-
Notifications
You must be signed in to change notification settings - Fork 235
feat: add Premium Geo DB addon to project settings #2981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
470b047
5954b75
7e73d36
0e53210
66e615e
afb13fc
13b0478
0cba797
6fc000f
304142c
67566db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,48 @@ | ||
| import { Dependencies, PAGE_LIMIT } from '$lib/constants'; | ||
| import { isCloud } from '$lib/system'; | ||
| import { sdk } from '$lib/stores/sdk'; | ||
| import { Query } from '@appwrite.io/console'; | ||
| import { Addon, Query } from '@appwrite.io/console'; | ||
| import type { PageLoad } from './$types'; | ||
|
|
||
| export const load: PageLoad = async ({ depends, url, params }) => { | ||
| 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 [variables, installations] = await Promise.all([ | ||
| const [variables, installations, addons, addonPrice] = await Promise.all([ | ||
| 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) | ||
| ]); | ||
|
|
||
| return { | ||
| limit, | ||
| offset, | ||
| variablesOffset, | ||
| variables, | ||
| installations | ||
| installations, | ||
| addons, | ||
| addonPrice | ||
| }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| <script lang="ts"> | ||
| import { Box, CardGrid } from '$lib/components'; | ||
| import { Button } from '$lib/elements/forms'; | ||
| import { getChangePlanUrl, plansInfo } from '$lib/stores/billing'; | ||
| import { currentPlan, organization } from '$lib/stores/organization'; | ||
| import { page } from '$app/state'; | ||
| import { get } from 'svelte/store'; | ||
| import { invalidate } from '$app/navigation'; | ||
| import { Dependencies } from '$lib/constants'; | ||
| import { addNotification } from '$lib/stores/notifications'; | ||
| import { sdk } from '$lib/stores/sdk'; | ||
| import { formatCurrency } from '$lib/helpers/numbers'; | ||
| import { Badge } from '@appwrite.io/pink-svelte'; | ||
| import type { Models } from '@appwrite.io/console'; | ||
| import PremiumGeoDBEnableModal from './premiumGeoDBEnableModal.svelte'; | ||
| import PremiumGeoDBDisableModal from './premiumGeoDBDisableModal.svelte'; | ||
|
|
||
| export let addons: Models.AddonList | null = null; | ||
| export let addonPrice: Models.AddonPrice | null = null; | ||
|
|
||
| let showEnable = false; | ||
| let showDisable = false; | ||
| let reEnabling = false; | ||
| let cancelling = false; | ||
|
|
||
| $: planSupportsPremiumGeoDB = $currentPlan?.supportedAddons?.premiumGeoDB === true; | ||
| $: canUpgradeToPremiumGeoDB = | ||
| !planSupportsPremiumGeoDB && hasUpgradeablePlanWithPremiumGeoDB($currentPlan); | ||
| $: premiumGeoDBAddon = addons?.addons?.find( | ||
| (a) => a.key === 'premiumGeoDB' && (a.status === 'active' || a.status === 'pending') | ||
| ); | ||
| $: isPending = premiumGeoDBAddon?.status === 'pending'; | ||
| $: isActive = premiumGeoDBAddon?.status === 'active'; | ||
| $: isScheduledForRemoval = isActive && premiumGeoDBAddon?.nextValue === 0; | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| $: monthlyPriceLabel = addonPrice ? formatCurrency(addonPrice.monthlyPrice) : null; | ||
|
|
||
| function hasUpgradeablePlanWithPremiumGeoDB(plan: Models.BillingPlan): boolean { | ||
| if (!plan) return false; | ||
| const plans = get(plansInfo); | ||
| for (const [, p] of plans) { | ||
| if (p.order > plan.order && p.supportedAddons?.premiumGeoDB) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| async function handleCancelAndRetry() { | ||
| cancelling = true; | ||
| try { | ||
| await sdk.forConsoleIn(page.params.region).projects.deleteAddon({ | ||
| projectId: page.params.project, | ||
| addonId: premiumGeoDBAddon.$id | ||
| }); | ||
| await invalidate(Dependencies.ADDONS); | ||
| showEnable = true; | ||
| } catch (e) { | ||
| addNotification({ | ||
| message: e.message, | ||
| type: 'error' | ||
| }); | ||
| } finally { | ||
| cancelling = false; | ||
| } | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
| } | ||
|
Comment on lines
+67
to
+86
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The enable modal handles this correctly via the |
||
| </script> | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
|
|
||
| <CardGrid> | ||
| <svelte:fragment slot="title">Premium Geo DB</svelte:fragment> | ||
| 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. | ||
| <svelte:fragment slot="aside"> | ||
| <Box> | ||
| <h6> | ||
| <b>Premium Geo DB</b> | ||
| </h6> | ||
| {#if !planSupportsPremiumGeoDB && canUpgradeToPremiumGeoDB} | ||
| <p class="text u-margin-block-start-8"> | ||
| Premium Geo DB is not available on your current plan. Upgrade your plan to | ||
| enable it. | ||
| </p> | ||
| <Button | ||
| secondary | ||
| class="u-margin-block-start-16" | ||
| href={getChangePlanUrl($organization?.$id)}> | ||
| <span class="text">Upgrade plan</span> | ||
| </Button> | ||
| {:else if !planSupportsPremiumGeoDB} | ||
| <p class="text u-margin-block-start-8"> | ||
| Premium Geo DB is not available on your current plan. | ||
| </p> | ||
| {:else if isPending} | ||
| <div class="u-flex u-cross-center u-gap-8 u-margin-block-start-8"> | ||
| <Badge variant="secondary" type="warning" content="Payment pending" /> | ||
| </div> | ||
| <p class="text u-margin-block-start-8"> | ||
| A payment is awaiting confirmation. If the payment was interrupted, you can | ||
| cancel and retry. | ||
| </p> | ||
| <Button | ||
| secondary | ||
| class="u-margin-block-start-16" | ||
| disabled={cancelling} | ||
| on:click={handleCancelAndRetry}> | ||
| <span class="text">Cancel & retry</span> | ||
| </Button> | ||
| {:else if isActive} | ||
| <div class="u-flex u-cross-center u-gap-8 u-margin-block-start-8"> | ||
| {#if isScheduledForRemoval} | ||
| <Badge variant="secondary" type="warning" content="Scheduled for removal" /> | ||
| {:else} | ||
| <Badge variant="secondary" type="success" content="Active" /> | ||
| {/if} | ||
| </div> | ||
| <p class="text u-margin-block-start-8"> | ||
| {#if monthlyPriceLabel} | ||
| Premium Geo DB is enabled for this project at {monthlyPriceLabel}/month. | ||
| {:else} | ||
| Premium Geo DB is enabled for this project. | ||
| {/if} | ||
| </p> | ||
| {#if isScheduledForRemoval} | ||
| <p class="text u-margin-block-start-8"> | ||
| Premium Geo DB will be removed at the end of your current billing cycle. | ||
| </p> | ||
| <Button | ||
| secondary | ||
| class="u-margin-block-start-16" | ||
| disabled={reEnabling} | ||
| on:click={handleReEnable}> | ||
| <span class="text">Keep Premium Geo DB</span> | ||
| </Button> | ||
| {:else} | ||
| <Button | ||
| secondary | ||
| class="u-margin-block-start-16" | ||
| on:click={() => (showDisable = true)}> | ||
| <span class="text">Disable Premium Geo DB</span> | ||
| </Button> | ||
| {/if} | ||
| {:else} | ||
| <p class="text u-margin-block-start-8"> | ||
| 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} | ||
| </p> | ||
| <Button | ||
| secondary | ||
| class="u-margin-block-start-16" | ||
| on:click={() => (showEnable = true)}> | ||
| <span class="text">Enable Premium Geo DB</span> | ||
| </Button> | ||
| {/if} | ||
| </Box> | ||
| </svelte:fragment> | ||
| </CardGrid> | ||
|
|
||
| <PremiumGeoDBEnableModal bind:show={showEnable} {addonPrice} /> | ||
|
|
||
| {#if premiumGeoDBAddon} | ||
| <PremiumGeoDBDisableModal bind:show={showDisable} addonId={premiumGeoDBAddon.$id} /> | ||
| {/if} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| <script lang="ts"> | ||
| import { page } from '$app/state'; | ||
| import { invalidate } from '$app/navigation'; | ||
| import { Modal } from '$lib/components'; | ||
| import { Button } from '$lib/elements/forms'; | ||
| import { Dependencies } from '$lib/constants'; | ||
| import { addNotification } from '$lib/stores/notifications'; | ||
| import { sdk } from '$lib/stores/sdk'; | ||
|
|
||
| let { | ||
| show = $bindable(false), | ||
| addonId | ||
| }: { | ||
| show: boolean; | ||
| addonId: string; | ||
| } = $props(); | ||
|
|
||
| let error = $state<string | null>(null); | ||
| let submitting = $state(false); | ||
|
|
||
| async function handleSubmit() { | ||
| submitting = true; | ||
| error = null; | ||
| try { | ||
| await sdk.forConsoleIn(page.params.region).projects.deleteAddon({ | ||
| projectId: page.params.project, | ||
| addonId | ||
| }); | ||
| await Promise.all([invalidate(Dependencies.ADDONS), invalidate(Dependencies.PROJECT)]); | ||
| addNotification({ | ||
| message: | ||
| 'Premium Geo DB addon will be removed at the end of your current billing cycle', | ||
| type: 'success' | ||
| }); | ||
| show = false; | ||
| } catch (e) { | ||
| error = e.message; | ||
| } finally { | ||
| submitting = false; | ||
| } | ||
| } | ||
| </script> | ||
|
|
||
| <Modal bind:error bind:show onSubmit={handleSubmit} title="Disable Premium Geo DB"> | ||
| <p class="text"> | ||
| 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. | ||
| </p> | ||
|
|
||
| <svelte:fragment slot="footer"> | ||
| <Button text on:click={() => (show = false)}>Cancel</Button> | ||
| <Button secondary submit disabled={submitting}>Disable Premium Geo DB</Button> | ||
| </svelte:fragment> | ||
| </Modal> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nameThe hardcoded
billingAddonNamesmap (addon_baa → 'HIPAA BAA') has been removed in favour ofaddon.name. If the billing-aggregation API response doesn't populatenameon existing BAA resources, BAA will display asaddon_baa overage (1)in the plan summary for current BAA subscribers. Make sure the cloud API (and the new SDK pin) actually includesnameon billing resource objects, or keep a fallback map alongside the API-provided name as a safety net.