Files
triggerdotdev--trigger.dev/apps/webapp/app/routes/resources.feedback.ts
T
2026-07-13 13:32:57 +08:00

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"] },
})
);
}
}