128 lines
3.8 KiB
TypeScript
128 lines
3.8 KiB
TypeScript
import { parseWithZod } from "@conform-to/zod";
|
|
import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
|
|
import { type PlainClient, uiComponent } from "@team-plain/typescript-sdk";
|
|
import { z } from "zod";
|
|
import { redirectWithSuccessMessage } from "~/models/message.server";
|
|
import { requireUser } from "~/services/session.server";
|
|
import { sendToPlain } from "~/utils/plain.server";
|
|
|
|
let _client: PlainClient | undefined;
|
|
|
|
export const feedbackTypes = {
|
|
bug: {
|
|
label: "Bug report",
|
|
labelTypeId: "lt_01HB920BTPFS36KH1JT9C36YVY",
|
|
threadTitle: "Web app: Bug report",
|
|
},
|
|
feature: {
|
|
label: "Feature request",
|
|
labelTypeId: "lt_01HB920BV8CJGYXVE15WWN6P07",
|
|
threadTitle: "Web app: Feature request",
|
|
},
|
|
help: {
|
|
label: "Help me out",
|
|
labelTypeId: "lt_01KTVCAPZY5ZJ0SS4ACMXWYYT3",
|
|
threadTitle: "Web app: Help me out",
|
|
},
|
|
enterprise: {
|
|
label: "Enterprise enquiry",
|
|
labelTypeId: "lt_01K7PF5EV2877EH4SZYB667FW4",
|
|
threadTitle: "Web app: Enterprise enquiry",
|
|
},
|
|
feedback: {
|
|
label: "General feedback",
|
|
labelTypeId: "lt_01HB920BSRZ3RA1ETHBVEB5ST2",
|
|
threadTitle: "Web app: General feedback",
|
|
},
|
|
concurrency: {
|
|
label: "Increase my concurrency",
|
|
labelTypeId: "lt_01KTVCCY2PDE5V6WV2PQ8N85K2",
|
|
threadTitle: "Web app: Increase my concurrency",
|
|
},
|
|
region: {
|
|
label: "Suggest a new region",
|
|
labelTypeId: "lt_01KTVCDPYYBW6KS9H5V8MTQ0GG",
|
|
threadTitle: "Web app: Suggest a new region",
|
|
},
|
|
hipaa: {
|
|
label: "HIPAA BAA request",
|
|
labelTypeId: "lt_01KS54WBRYKE6DY369KPK2SS4W",
|
|
threadTitle: "Web app: HIPAA BAA request",
|
|
},
|
|
} as const satisfies Record<string, { label: string; labelTypeId?: string; threadTitle: string }>;
|
|
|
|
export type FeedbackType = keyof typeof feedbackTypes;
|
|
|
|
const feedbackTypeLiterals = Object.keys(feedbackTypes).map((key) => z.literal(key));
|
|
|
|
const feedbackType = z.union(
|
|
[feedbackTypeLiterals[0], feedbackTypeLiterals[1], ...feedbackTypeLiterals.slice(2)],
|
|
{
|
|
required_error: "Must be either 'bug' or 'feature'",
|
|
invalid_type_error: "Must be either 'bug' or 'feature'",
|
|
}
|
|
);
|
|
|
|
export const schema = z.object({
|
|
path: z.string(),
|
|
feedbackType,
|
|
message: z.string().min(10, "Must be at least 10 characters"),
|
|
});
|
|
|
|
export async function action({ request }: ActionFunctionArgs) {
|
|
const user = await requireUser(request);
|
|
|
|
const formData = await request.formData();
|
|
const submission = parseWithZod(formData, { schema });
|
|
|
|
if (submission.status !== "success") {
|
|
return json(submission.reply());
|
|
}
|
|
|
|
const inquiry = feedbackTypes[submission.value.feedbackType as FeedbackType];
|
|
try {
|
|
await sendToPlain({
|
|
userId: user.id,
|
|
email: user.email,
|
|
name: user.name ?? user.displayName ?? user.email,
|
|
title: inquiry.threadTitle,
|
|
labelTypeIds: inquiry.labelTypeId ? [inquiry.labelTypeId] : undefined,
|
|
components: [
|
|
uiComponent.text({
|
|
text: `New ${inquiry.label} reported by ${user.name} (${user.email})`,
|
|
}),
|
|
uiComponent.divider({ spacingSize: "M" }),
|
|
uiComponent.text({
|
|
size: "S",
|
|
color: "MUTED",
|
|
text: "Page",
|
|
}),
|
|
uiComponent.text({
|
|
text: submission.value.path,
|
|
}),
|
|
uiComponent.spacer({ size: "M" }),
|
|
uiComponent.text({
|
|
size: "S",
|
|
color: "MUTED",
|
|
text: "Message",
|
|
}),
|
|
uiComponent.text({
|
|
text: submission.value.message,
|
|
}),
|
|
],
|
|
});
|
|
|
|
return redirectWithSuccessMessage(
|
|
submission.value.path,
|
|
request,
|
|
"Thanks for your feedback! We'll get back to you soon."
|
|
);
|
|
} catch (e) {
|
|
return json(
|
|
submission.reply({
|
|
fieldErrors: { message: [e instanceof Error ? e.message : "Unknown error"] },
|
|
})
|
|
);
|
|
}
|
|
}
|