feat(form): add submission link webhook placeholder

This commit is contained in:
2026-07-28 19:58:36 +07:00 Unverified
parent fa35f00f38
commit 2ac971bb6e
5 changed files with 37 additions and 4 deletions
+8
View File
@@ -15,6 +15,12 @@ import {
} from "@/lib/submission-intake"; } from "@/lib/submission-intake";
import { submissionWebhookMessage } from "@/lib/submission-webhook"; import { submissionWebhookMessage } from "@/lib/submission-webhook";
function getSubmissionResultLink(formId: string, submissionId: string) {
const baseUrl = process.env.BASE_URL || process.env.NEXTAUTH_URL;
if (!baseUrl) return `/admin/form/${formId}/result/${submissionId}`;
return new URL(`/admin/form/${formId}/result/${submissionId}`, baseUrl).toString();
}
export async function submitForm( export async function submitForm(
formId: string, formId: string,
answersList: FormAnswerPayload[], answersList: FormAnswerPayload[],
@@ -171,11 +177,13 @@ export async function submitForm(
if (form.discordWebhookUrl) { if (form.discordWebhookUrl) {
try { try {
const submissionLink = getSubmissionResultLink(formId, result.submissionId);
const message = submissionWebhookMessage({ const message = submissionWebhookMessage({
template: templateToUse, template: templateToUse,
isUpdate: result.isUpdate, isUpdate: result.isUpdate,
userName: session.user.name, userName: session.user.name,
discordId, discordId,
submissionLink,
questions: questionsList, questions: questionsList,
answers: normalizedAnswers, answers: normalizedAnswers,
oldAnswersByQuestionId: result.oldAnswersDict, oldAnswersByQuestionId: result.oldAnswersDict,
+4 -4
View File
@@ -336,10 +336,10 @@ export default function FormExtraClient({ form }: FormExtraClientProps) {
className="min-h-24" className="min-h-24"
/> />
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Use <code>{`{1}`}</code>, <code>{`{2}`}</code> to insert answers according to question order, and <code>{`{username}`}</code>, <code>{`{displayname}`}</code>, <code>{`{id}`}</code> for user info. Use <code>{`{1}`}</code>, <code>{`{2}`}</code> to insert answers according to question order, <code>{`{link}`}</code> for the admin result link, and <code>{`{username}`}</code>, <code>{`{displayname}`}</code>, <code>{`{id}`}</code> for user info.
</p> </p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{["username", "displayname", "id", "1", "2"].map((variable) => ( {["username", "displayname", "id", "link", "1", "2"].map((variable) => (
<Button <Button
key={variable} key={variable}
type="button" type="button"
@@ -368,10 +368,10 @@ export default function FormExtraClient({ form }: FormExtraClientProps) {
className="min-h-24" className="min-h-24"
/> />
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Used when a user updates their existing submission. Use <code>{`{1}`}</code>, <code>{`{2}`}</code> for answers, <code>{`{update}`}</code> to show what was changed, and <code>{`{username}`}</code>, <code>{`{displayname}`}</code>, <code>{`{id}`}</code> for user info. Used when a user updates their existing submission. Use <code>{`{1}`}</code>, <code>{`{2}`}</code> for answers, <code>{`{update}`}</code> to show what was changed, <code>{`{link}`}</code> for the admin result link, and <code>{`{username}`}</code>, <code>{`{displayname}`}</code>, <code>{`{id}`}</code> for user info.
</p> </p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{["username", "displayname", "id", "update", "1", "2"].map((variable) => ( {["username", "displayname", "id", "link", "update", "1", "2"].map((variable) => (
<Button <Button
key={variable} key={variable}
type="button" type="button"
+3
View File
@@ -30,6 +30,9 @@ export function previewWebhookTemplate(
const key = variable.toLowerCase(); const key = variable.toLowerCase();
if (key === "username" || key === "displayname") return "Example User"; if (key === "username" || key === "displayname") return "Example User";
if (key === "id") return "123456789"; if (key === "id") return "123456789";
if (key === "link") {
return "https://erika.sudloh.com/admin/form/example-form/result/example-submission";
}
if (key === "update") return "Question 1: Previous answer → Example answer"; if (key === "update") return "Question 1: Previous answer → Example answer";
if (/^\d+$/.test(key)) return `Example answer ${key}`; if (/^\d+$/.test(key)) return `Example answer ${key}`;
return `{${variable}}`; return `{${variable}}`;
+17
View File
@@ -31,6 +31,7 @@ test("uses an image URL for image-only placeholders", () => {
isUpdate: false, isUpdate: false,
userName: "Gunshiz", userName: "Gunshiz",
discordId: "123", discordId: "123",
submissionLink: "https://erika.sudloh.com/admin/form/form-1/result/submission-1",
questions, questions,
answers: [{ answers: [{
questionId: "name", questionId: "name",
@@ -48,12 +49,26 @@ test("uses the configured template", () => {
isUpdate: false, isUpdate: false,
userName: "Gunshiz", userName: "Gunshiz",
discordId: "123", discordId: "123",
submissionLink: "https://erika.sudloh.com/admin/form/form-1/result/submission-1",
questions, questions,
answers, answers,
oldAnswersByQuestionId: {}, oldAnswersByQuestionId: {},
})).toBe("New answer: Erika by Gunshiz (123)"); })).toBe("New answer: Erika by Gunshiz (123)");
}); });
test("uses a submission result link placeholder", () => {
expect(submissionWebhookMessage({
template: "Review: {link}",
isUpdate: false,
userName: "Gunshiz",
discordId: "123",
submissionLink: "https://erika.sudloh.com/admin/form/form-1/result/submission-1",
questions,
answers,
oldAnswersByQuestionId: {},
})).toBe("Review: https://erika.sudloh.com/admin/form/form-1/result/submission-1");
});
describe("default templates", () => { describe("default templates", () => {
test("sends a new-submission message when its template is blank", () => { test("sends a new-submission message when its template is blank", () => {
expect(submissionWebhookMessage({ expect(submissionWebhookMessage({
@@ -61,6 +76,7 @@ describe("default templates", () => {
isUpdate: false, isUpdate: false,
userName: "Gunshiz", userName: "Gunshiz",
discordId: "123", discordId: "123",
submissionLink: "https://erika.sudloh.com/admin/form/form-1/result/submission-1",
questions, questions,
answers, answers,
oldAnswersByQuestionId: {}, oldAnswersByQuestionId: {},
@@ -73,6 +89,7 @@ describe("default templates", () => {
isUpdate: true, isUpdate: true,
userName: "Gunshiz", userName: "Gunshiz",
discordId: "123", discordId: "123",
submissionLink: "https://erika.sudloh.com/admin/form/form-1/result/submission-1",
questions, questions,
answers, answers,
oldAnswersByQuestionId: { name: "Old name" }, oldAnswersByQuestionId: { name: "Old name" },
+5
View File
@@ -14,6 +14,7 @@ type SubmissionWebhookMessageOptions = {
isUpdate: boolean; isUpdate: boolean;
userName: string | null | undefined; userName: string | null | undefined;
discordId: string; discordId: string;
submissionLink: string;
questions: SubmissionQuestion[]; questions: SubmissionQuestion[];
answers: NormalizedSubmissionAnswer[]; answers: NormalizedSubmissionAnswer[];
oldAnswersByQuestionId: OldAnswersByQuestionId; oldAnswersByQuestionId: OldAnswersByQuestionId;
@@ -24,6 +25,7 @@ export function submissionWebhookMessage({
isUpdate, isUpdate,
userName, userName,
discordId, discordId,
submissionLink,
questions, questions,
answers, answers,
oldAnswersByQuestionId, oldAnswersByQuestionId,
@@ -43,6 +45,9 @@ export function submissionWebhookMessage({
if (key === "id") { if (key === "id") {
return discordId; return discordId;
} }
if (key === "link") {
return submissionLink;
}
if (key === "update") { if (key === "update") {
return updatesText; return updatesText;
} }