diff --git a/bun.lock b/bun.lock index a4f83a4c1f..63b5c92177 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@467cd21", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@24e07fc", "@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", @@ -113,7 +113,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@467cd21", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@24e07fc", { "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 91e5325526..71f9cb2c12 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@467cd21", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@24e07fc", "@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/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 0aeb6bc73f..0ca802658d 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -428,6 +428,8 @@ export enum Submit { MessagingTopicSubscriberDelete = 'submit_messaging_topic_subscriber_delete', ApplyQuickFilter = 'submit_apply_quick_filter', RequestBAA = 'submit_request_baa', + BAAAddonEnable = 'submit_baa_addon_enable', + BAAAddonDisable = 'submit_baa_addon_disable', RequestSoc2 = 'submit_request_soc2', SiteCreate = 'submit_site_create', SiteDelete = 'submit_site_delete', diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 6c28da6668..f35a1eddbb 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -85,7 +85,8 @@ export enum Dependencies { MESSAGING_TOPIC_SUBSCRIBERS = 'dependency:messaging_topic_subscribers', SITE = 'dependency:site', SITES = 'dependency:sites', - SITES_DOMAINS = 'dependency:sites_domains' + SITES_DOMAINS = 'dependency:sites_domains', + ADDONS = 'dependency:addons' } export const defaultScopes: string[] = [ diff --git a/src/routes/(console)/organization-[organization]/billing/+page.ts b/src/routes/(console)/organization-[organization]/billing/+page.ts index 4c10658caa..202a6ee25c 100644 --- a/src/routes/(console)/organization-[organization]/billing/+page.ts +++ b/src/routes/(console)/organization-[organization]/billing/+page.ts @@ -25,6 +25,7 @@ export const load: PageLoad = async ({ parent, depends, url, route }) => { depends(Dependencies.CREDIT); depends(Dependencies.INVOICES); depends(Dependencies.ADDRESS); + depends(Dependencies.ADDONS); // aggregation reloads on page param changes depends(Dependencies.BILLING_AGGREGATION); diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index 8c3c651e6f..84683f8c66 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -249,8 +249,17 @@ }; // addons (additional members, projects, etc.) + const billingAddonNames: Record = { + addon_baa: 'HIPAA BAA' + }; + const addons = (currentAggregation?.resources || []) - .filter((r) => r.amount > 0 && currentPlan?.addons?.[r.resourceId]?.price > 0) + .filter( + (r) => + r.amount > 0 && + (currentPlan?.addons?.[r.resourceId]?.price > 0 || + r.resourceId.startsWith('addon_')) + ) .map((addon) => ({ id: `addon-${addon.resourceId}`, expandable: false, @@ -260,7 +269,8 @@ ? 'Additional members' : addon.resourceId === 'projects' ? 'Additional projects' - : `${addon.resourceId} overage (${formatNum(addon.value)})`, + : (billingAddonNames[addon.resourceId] ?? + `${addon.resourceId} overage (${formatNum(addon.value)})`), usage: '', price: formatCurrency(addon.amount) }, diff --git a/src/routes/(console)/organization-[organization]/settings/+page.svelte b/src/routes/(console)/organization-[organization]/settings/+page.svelte index c97d099fdc..9e4ab1a163 100644 --- a/src/routes/(console)/organization-[organization]/settings/+page.svelte +++ b/src/routes/(console)/organization-[organization]/settings/+page.svelte @@ -75,7 +75,7 @@ {#if isCloud} - + {/if} diff --git a/src/routes/(console)/organization-[organization]/settings/+page.ts b/src/routes/(console)/organization-[organization]/settings/+page.ts index 1f377bfe33..c275d01955 100644 --- a/src/routes/(console)/organization-[organization]/settings/+page.ts +++ b/src/routes/(console)/organization-[organization]/settings/+page.ts @@ -1,14 +1,15 @@ import type { PageLoad } from './$types'; import { Dependencies } from '$lib/constants'; import { sdk } from '$lib/stores/sdk'; -import { Query } from '@appwrite.io/console'; +import { Addon, Query } from '@appwrite.io/console'; import { isCloud } from '$lib/system'; export const load: PageLoad = async ({ depends, params, parent }) => { const { countryList, locale } = await parent(); depends(Dependencies.ORGANIZATION); + depends(Dependencies.ADDONS); - const [projects, invoices] = await Promise.all([ + const [projects, invoices, addons, addonPrice] = await Promise.all([ sdk.forConsole.projects.list({ queries: [Query.equal('teamId', params.organization), Query.select(['$id', 'name'])] }), @@ -16,12 +17,29 @@ export const load: PageLoad = async ({ depends, params, parent }) => { ? sdk.forConsole.organizations.listInvoices({ organizationId: params.organization }) - : undefined + : undefined, + isCloud + ? sdk.forConsole.organizations + .listAddons({ + organizationId: params.organization + }) + .catch(() => null) + : null, + isCloud + ? sdk.forConsole.organizations + .getAddonPrice({ + organizationId: params.organization, + addon: Addon.Baa + }) + .catch(() => null) + : null ]); return { projects, invoices, + addons, + addonPrice, countryList, locale }; diff --git a/src/routes/(console)/organization-[organization]/settings/BAA.svelte b/src/routes/(console)/organization-[organization]/settings/BAA.svelte index 87b33da1f1..ba349d0c73 100644 --- a/src/routes/(console)/organization-[organization]/settings/BAA.svelte +++ b/src/routes/(console)/organization-[organization]/settings/BAA.svelte @@ -1,38 +1,283 @@ BAA - After requesting a BAA, we will contact you via email for the next steps. + A Business Associate Agreement (BAA) is a HIPAA-required document ensuring outside services handling + patient information for a healthcare organization follow privacy rules.
Business Associate Agreement (BAA)
-

- A Business Associate Agreement (BAA) is a HIPAA-required document ensuring outside - services handling patient information for a healthcare organization follow privacy - rules. -

- + {#if !planSupportsBaa && canUpgradeToBaa} +

+ BAA is not available on your current plan. Upgrade your plan to enable BAA for + your organization. +

+ + {:else if !planSupportsBaa} +

+ BAA 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} +
+

+ BAA is enabled for your organization at {monthlyPriceLabel}/month. +

+ {#if isScheduledForRemoval} +

+ BAA will be removed at the end of your current billing cycle. +

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

+ Enable BAA for your organization to ensure HIPAA compliance. This addon costs + {monthlyPriceLabel}/month, prorated for your current billing cycle. +

+ + {/if}
- + + +{#if baaAddon} + +{/if} diff --git a/src/routes/(console)/organization-[organization]/settings/BAADisableModal.svelte b/src/routes/(console)/organization-[organization]/settings/BAADisableModal.svelte new file mode 100644 index 0000000000..34171f115e --- /dev/null +++ b/src/routes/(console)/organization-[organization]/settings/BAADisableModal.svelte @@ -0,0 +1,59 @@ + + + +

+ Are you sure you want to disable the BAA 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)/organization-[organization]/settings/BAAEnableModal.svelte b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte new file mode 100644 index 0000000000..9fea57a939 --- /dev/null +++ b/src/routes/(console)/organization-[organization]/settings/BAAEnableModal.svelte @@ -0,0 +1,144 @@ + + + + {#if addonPrice} +

+ By clicking Accept & 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. +

+

+ Your action confirms acceptance of Appwrite's + Business Associate Agreement + and related terms. +

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

+ * Plus applicable tax and fees +

+
+ {/if} + + + + + +
+ + diff --git a/src/routes/(console)/organization-[organization]/settings/BAAModal.svelte b/src/routes/(console)/organization-[organization]/settings/BAAModal.svelte deleted file mode 100644 index 1aaccfef2a..0000000000 --- a/src/routes/(console)/organization-[organization]/settings/BAAModal.svelte +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - -