Skip to content

Commit 93b4715

Browse files
authored
feat(webapp): hipaa baa add-on on paid pricing tiers (#3904)
## Summary HIPAA BAA is offered as a paid add-on on every paid plan. Each paid tier on the in-app pricing card now has a "HIPAA BAA add-on" row with a "Request a BAA" link that opens the existing contact dialog pre-filled with a new `hipaa` inquiry type, prompting the user for their company name and a brief description of the PHI workload. The contact form's `feedbackTypes` are restructured to match the marketing /contact form: every inquiry type carries a Plain label ID and a "Contact form: ..." thread title, so threads land in Plain identically whether they come from the dashboard or the marketing site. The included-compute line on each tier also picks up the credits wording from the marketing pricing page, and the Enterprise tier lifts its title above the features row.
1 parent d0b2d79 commit 93b4715

6 files changed

Lines changed: 234 additions & 99 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: feature
4+
---
5+
6+
Request a HIPAA BAA add-on directly from any paid pricing tier in the dashboard.

apps/webapp/app/components/DefinitionTooltip.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ export function DefinitionTip({
66
content,
77
children,
88
title,
9+
disableHoverableContent = true,
910
}: {
1011
content: React.ReactNode;
1112
children: React.ReactNode;
1213
title: React.ReactNode;
14+
disableHoverableContent?: boolean;
1315
}) {
1416
return (
1517
<TooltipProvider>
16-
<Tooltip disableHoverableContent>
18+
<Tooltip disableHoverableContent={disableHoverableContent}>
1719
<TooltipTrigger className="text-left">
1820
<span className="cursor-default underline decoration-charcoal-500 decoration-dashed underline-offset-4 transition hover:decoration-charcoal-400">
1921
{children}

apps/webapp/app/components/Feedback.tsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { conform, useForm } from "@conform-to/react";
22
import { parse } from "@conform-to/zod";
33
import { InformationCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid";
4-
import { EnvelopeIcon } from "@heroicons/react/24/solid";
4+
import { EnvelopeIcon, ShieldCheckIcon } from "@heroicons/react/24/solid";
55
import { Form, useActionData, useLocation, useNavigation, useSearchParams } from "@remix-run/react";
66
import { type ReactNode, useEffect, useState } from "react";
7-
import { type FeedbackType, feedbackTypeLabel, schema } from "~/routes/resources.feedback";
7+
import { type FeedbackType, feedbackTypes, schema } from "~/routes/resources.feedback";
88
import { Button } from "./primitives/Buttons";
99
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "./primitives/Dialog";
1010
import { Fieldset } from "./primitives/Fieldset";
@@ -84,9 +84,12 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
8484
How can we help? We read every message and will respond as quickly as we can.
8585
</Paragraph>
8686
</div>
87-
{!(type === "feature" || type === "help" || type === "concurrency") && (
88-
<hr className="border-grid-dimmed" />
89-
)}
87+
{!(
88+
type === "feature" ||
89+
type === "help" ||
90+
type === "concurrency" ||
91+
type === "hipaa"
92+
) && <hr className="border-grid-dimmed" />}
9093
<Form method="post" action="/resources/feedback" {...form.props} className="w-full">
9194
<Fieldset className="max-w-full gap-y-3">
9295
<input value={location.pathname} {...conform.input(path, { type: "hidden" })} />
@@ -132,19 +135,32 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
132135
</Paragraph>
133136
</InfoPanel>
134137
)}
138+
{type === "hipaa" && (
139+
<InfoPanel
140+
icon={ShieldCheckIcon}
141+
iconClassName="text-green-500"
142+
panelClassName="w-full mb-2"
143+
>
144+
<Paragraph variant="small">
145+
We offer a signed Business Associate Agreement (BAA) as a paid add-on on any
146+
paid plan. To help us get back to you quickly, please include your company
147+
name, and a brief description of the PHI workload you plan to run.
148+
</Paragraph>
149+
</InfoPanel>
150+
)}
135151
<Select
136152
{...conform.select(feedbackType)}
137153
variant="tertiary/medium"
138154
value={type}
139155
defaultValue={type}
140156
setValue={(v) => setType(v as FeedbackType)}
141157
placeholder="Select type"
142-
text={(value) => feedbackTypeLabel[value as FeedbackType]}
158+
text={(value) => feedbackTypes[value as FeedbackType].label}
143159
dropdownIcon
144160
>
145-
{Object.entries(feedbackTypeLabel).map(([name, title]) => (
161+
{Object.entries(feedbackTypes).map(([name, { label }]) => (
146162
<SelectItem key={name} value={name}>
147-
{title}
163+
{label}
148164
</SelectItem>
149165
))}
150166
</Select>

apps/webapp/app/routes/resources.feedback.ts

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,55 @@ import { sendToPlain } from "~/utils/plain.server";
88

99
let client: PlainClient | undefined;
1010

11-
export const feedbackTypeLabel = {
12-
bug: "Bug report",
13-
feature: "Feature request",
14-
help: "Help me out",
15-
enterprise: "Enterprise enquiry",
16-
feedback: "General feedback",
17-
concurrency: "Increase my concurrency",
18-
region: "Suggest a new region",
19-
};
11+
export const feedbackTypes = {
12+
bug: {
13+
label: "Bug report",
14+
labelTypeId: "lt_01HB920BTPFS36KH1JT9C36YVY",
15+
threadTitle: "Contact form: Bug report",
16+
},
17+
feature: {
18+
label: "Feature request",
19+
labelTypeId: "lt_01HB920BV8CJGYXVE15WWN6P07",
20+
threadTitle: "Contact form: Feature request",
21+
},
22+
help: {
23+
label: "Help me out",
24+
labelTypeId: "lt_01KTVCAPZY5ZJ0SS4ACMXWYYT3",
25+
threadTitle: "Contact form: Help me out",
26+
},
27+
enterprise: {
28+
label: "Enterprise enquiry",
29+
labelTypeId: "lt_01K7PF5EV2877EH4SZYB667FW4",
30+
threadTitle: "Contact form: Enterprise enquiry",
31+
},
32+
feedback: {
33+
label: "General feedback",
34+
labelTypeId: "lt_01HB920BSRZ3RA1ETHBVEB5ST2",
35+
threadTitle: "Contact form: General feedback",
36+
},
37+
concurrency: {
38+
label: "Increase my concurrency",
39+
labelTypeId: "lt_01KTVCCY2PDE5V6WV2PQ8N85K2",
40+
threadTitle: "Contact form: Increase my concurrency",
41+
},
42+
region: {
43+
label: "Suggest a new region",
44+
labelTypeId: "lt_01KTVCDPYYBW6KS9H5V8MTQ0GG",
45+
threadTitle: "Contact form: Suggest a new region",
46+
},
47+
hipaa: {
48+
label: "HIPAA BAA request",
49+
labelTypeId: "lt_01KS54WBRYKE6DY369KPK2SS4W",
50+
threadTitle: "Contact form: HIPAA BAA request",
51+
},
52+
} as const satisfies Record<
53+
string,
54+
{ label: string; labelTypeId?: string; threadTitle: string }
55+
>;
2056

21-
export type FeedbackType = keyof typeof feedbackTypeLabel;
57+
export type FeedbackType = keyof typeof feedbackTypes;
2258

23-
const feedbackTypeLiterals = Object.keys(feedbackTypeLabel).map((key) => z.literal(key));
59+
const feedbackTypeLiterals = Object.keys(feedbackTypes).map((key) => z.literal(key));
2460

2561
const feedbackType = z.union(
2662
[feedbackTypeLiterals[0], feedbackTypeLiterals[1], ...feedbackTypeLiterals.slice(2)],
@@ -46,16 +82,17 @@ export async function action({ request }: ActionFunctionArgs) {
4682
return json(submission);
4783
}
4884

49-
const title = feedbackTypeLabel[submission.value.feedbackType as FeedbackType];
85+
const inquiry = feedbackTypes[submission.value.feedbackType as FeedbackType];
5086
try {
5187
await sendToPlain({
5288
userId: user.id,
5389
email: user.email,
5490
name: user.name ?? user.displayName ?? user.email,
55-
title,
91+
title: inquiry.threadTitle,
92+
labelTypeIds: inquiry.labelTypeId ? [inquiry.labelTypeId] : undefined,
5693
components: [
5794
uiComponent.text({
58-
text: `New ${title} reported by ${user.name} (${user.email})`,
95+
text: `New ${inquiry.label} reported by ${user.name} (${user.email})`,
5996
}),
6097
uiComponent.divider({ spacingSize: "M" }),
6198
uiComponent.text({

0 commit comments

Comments
 (0)