Skip to content

Commit

Permalink
feat: πŸš€ Added custom end screen message setting
Browse files Browse the repository at this point in the history
Form creator can add a custom message to show when form is submitted

βœ… Closes: 61
  • Loading branch information
growupanand committed Mar 30, 2024
1 parent f658846 commit aa84182
Show file tree
Hide file tree
Showing 10 changed files with 724 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
"use client";

import { useRef } from "react";
import Image from "next/image";
import { Organization } from "@clerk/clerk-sdk-node";
import { Form } from "@convoform/db";
import { Button } from "@convoform/ui/components/ui/button";
import {
Collapsible,
CollapsibleContent,
} from "@convoform/ui/components/ui/collapsible";
import { Label } from "@convoform/ui/components/ui/label";
import {
Sheet,
Expand All @@ -13,19 +18,32 @@ import {
SheetTrigger,
} from "@convoform/ui/components/ui/sheet";
import { Switch } from "@convoform/ui/components/ui/switch";
import { Textarea } from "@convoform/ui/components/ui/textarea";
import { toast } from "@convoform/ui/components/ui/use-toast";
import { useQueryClient } from "@tanstack/react-query";
import { Palette } from "lucide-react";

import { isRateLimitErrorResponse } from "@/lib/errorHandlers";
import { debounce } from "@/lib/utils";
import { api } from "@/trpc/react";

type Props = {
form: Pick<Form, "id" | "showOrganizationName" | "showOrganizationLogo">;
form: Pick<
Form,
| "id"
| "showOrganizationName"
| "showOrganizationLogo"
| "showCustomEndScreenMessage"
| "customEndScreenMessage"
>;
organization: Pick<Organization, "name" | "imageUrl">;
};

export function FormCustomize({ form, organization }: Readonly<Props>) {
const customEndScreenMessageRef = useRef<string>(
form.customEndScreenMessage || "",
);

const queryClient = useQueryClient();

const params = new URLSearchParams();
Expand Down Expand Up @@ -101,6 +119,52 @@ export function FormCustomize({ form, organization }: Readonly<Props>) {
});
};

const updateShowCustomEndScreenMessage =
api.form.updateShowCustomEndScreenMessage.useMutation({
onSuccess: () => {
toast({
title: "Changes saved successfully",
duration: 1500,
});
queryClient.invalidateQueries({
queryKey: [["form"]],
});
},
onError: (error) => {
toast({
title: "Unable to save changes",
duration: 2000,
variant: "destructive",
description: isRateLimitErrorResponse(error)
? error.message
: undefined,
});
},
});
const { isPending: isPendingCustomEndScreenMessage } =
updateShowCustomEndScreenMessage;

const handleToggleShowCustomEndScreenMessage = async (checked: boolean) => {
await updateShowCustomEndScreenMessage.mutateAsync({
formId: form.id,
showCustomEndScreenMessage: checked,
customEndScreenMessage: customEndScreenMessageRef.current,
});
};

const handleChangeCustomEndScreenMessage = (
e: React.ChangeEvent<HTMLTextAreaElement>,
) => {
const updatedCustomEndMessage = e.target.value as string;
console.log(updatedCustomEndMessage);
customEndScreenMessageRef.current = updatedCustomEndMessage;
debounce(() => {
if (customEndScreenMessageRef.current !== form.customEndScreenMessage) {
handleToggleShowCustomEndScreenMessage(form.showCustomEndScreenMessage);
}
}, 1000);
};

return (
<Sheet>
<SheetTrigger asChild>
Expand Down Expand Up @@ -160,6 +224,35 @@ export function FormCustomize({ form, organization }: Readonly<Props>) {
id="showOrganizationLogoSwitch"
/>
</div>
<div className="px-2">
<div className="mb-2 flex items-start justify-between gap-3">
<Label
htmlFor="showCustomEndScreenMessageSwitch"
className="text-md flex-grow cursor-pointer font-normal"
>
<div>End screen message</div>
<div className="text-muted-foreground text-sm">
Display custom message after form submission
</div>
</Label>
<Switch
disabled={isPendingCustomEndScreenMessage}
defaultChecked={form.showCustomEndScreenMessage}
onCheckedChange={handleToggleShowCustomEndScreenMessage}
id="showCustomEndScreenMessageSwitch"
/>
</div>
<Collapsible open={form.showCustomEndScreenMessage}>
<CollapsibleContent>
<Textarea
disabled={isPendingCustomEndScreenMessage}
onChange={handleChangeCustomEndScreenMessage}
placeholder="Thank you for filling the form!"
defaultValue={customEndScreenMessageRef.current}
/>
</CollapsibleContent>
</Collapsible>
</div>
</div>
</SheetContent>
</Sheet>
Expand Down
10 changes: 9 additions & 1 deletion apps/web/src/components/formViewer/formViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export type FormStage = "welcomeScreen" | "conversationFlow" | "endScreen";
export function FormViewer({ form, refresh, isPreview }: Props) {
const apiEndpoint = `/api/form/${form.id}/conversation`;

const { showCustomEndScreenMessage, customEndScreenMessage } = form;

const [state, setState] = useState<State>({
formStage: "welcomeScreen",
endScreenMessage: "",
Expand Down Expand Up @@ -73,6 +75,7 @@ export function FormViewer({ form, refresh, isPreview }: Props) {
}
return "";
};

const currentQuestion = getCurrentQuestion();

const handleFormSubmit = (event: any) => {
Expand Down Expand Up @@ -113,9 +116,14 @@ export function FormViewer({ form, refresh, isPreview }: Props) {

useEffect(() => {
if (data?.includes("conversationFinished")) {
const currentEndScreenMessage =
showCustomEndScreenMessage && customEndScreenMessage
? customEndScreenMessage
: currentQuestion;

setState((cs) => ({
...cs,
endScreenMessage: getCurrentQuestion(),
endScreenMessage: currentEndScreenMessage,
formStage: "endScreen",
}));
}
Expand Down
24 changes: 24 additions & 0 deletions packages/api/src/router/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,28 @@ export const formRouter = createTRPCRouter({
.set({ showOrganizationLogo, organizationLogoUrl })
.where(eq(form.id, input.formId));
}),

updateShowCustomEndScreenMessage: protectedProcedure
.input(
z.object({
formId: z.string().min(1),
showCustomEndScreenMessage: z.boolean(),
customEndScreenMessage: z.string().optional(),
}),
)
.mutation(async ({ input, ctx }) => {
const { showCustomEndScreenMessage, customEndScreenMessage } = input;

if (
showCustomEndScreenMessage &&
(!customEndScreenMessage || customEndScreenMessage.trim() === "")
) {
throw new Error("End screen message is required");
}

return await ctx.db
.update(form)
.set({ showCustomEndScreenMessage, customEndScreenMessage })
.where(eq(form.id, input.formId));
}),
});
2 changes: 2 additions & 0 deletions packages/db/drizzle/migrations/0006_narrow_sir_ram.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE "Form" ADD COLUMN "showCustomEndScreenMessage" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "Form" ADD COLUMN "customEndScreenMessage" text;

0 comments on commit aa84182

Please sign in to comment.