mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-06 05:40:02 -06:00
fix: Tweaked thank you card (#1923)
Co-authored-by: Johannes <johannes@formbricks.com> Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
1fd339e5a0
commit
aa63c89a6a
@@ -1,6 +1,6 @@
|
||||
import nextMDX from "@next/mdx";
|
||||
|
||||
import { withPlausibleProxy } from "next-plausible";
|
||||
|
||||
import { recmaPlugins } from "./mdx/recma.mjs";
|
||||
import { rehypePlugins } from "./mdx/rehype.mjs";
|
||||
import { remarkPlugins } from "./mdx/remark.mjs";
|
||||
@@ -170,6 +170,11 @@ const nextConfig = {
|
||||
destination: "/community",
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: "/signup",
|
||||
destination: "https://app.formbricks.com/auth/signup",
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
async rewrites() {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { useState } from "react";
|
||||
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
import { Input } from "@formbricks/ui/Input";
|
||||
import { Label } from "@formbricks/ui/Label";
|
||||
import QuestionFormInput from "@formbricks/ui/QuestionFormInput";
|
||||
import { Switch } from "@formbricks/ui/Switch";
|
||||
@@ -23,6 +25,9 @@ export default function EditThankYouCard({
|
||||
}: EditThankYouCardProps) {
|
||||
// const [open, setOpen] = useState(false);
|
||||
let open = activeQuestionId == "end";
|
||||
const [showThankYouCardCTA, setshowThankYouCardCTA] = useState<boolean>(
|
||||
localSurvey.thankYouCard.buttonLabel || localSurvey.thankYouCard.buttonLink ? true : false
|
||||
);
|
||||
const setOpen = (e) => {
|
||||
if (e) {
|
||||
setActiveQuestionId("end");
|
||||
@@ -114,6 +119,59 @@ export default function EditThankYouCard({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Switch
|
||||
id="showButton"
|
||||
checked={showThankYouCardCTA}
|
||||
onCheckedChange={() => {
|
||||
if (showThankYouCardCTA) {
|
||||
updateSurvey({ buttonLabel: undefined, buttonLink: undefined });
|
||||
} else {
|
||||
updateSurvey({
|
||||
buttonLabel: "Create your own Survey",
|
||||
buttonLink: "https://formbricks.com/signup",
|
||||
});
|
||||
}
|
||||
setshowThankYouCardCTA(!showThankYouCardCTA);
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="showButton" className="cursor-pointer">
|
||||
<div className="ml-2">
|
||||
<h3 className="text-sm font-semibold text-slate-700">Show Button</h3>
|
||||
<p className="text-xs font-normal text-slate-500">
|
||||
Send your respondents to a page of your choice.
|
||||
</p>
|
||||
</div>
|
||||
</Label>
|
||||
</div>
|
||||
{showThankYouCardCTA && (
|
||||
<div className="border-1 mt-4 space-y-4 rounded-md border bg-slate-100 p-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Button Label</Label>
|
||||
<Input
|
||||
id="buttonLabel"
|
||||
name="buttonLabel"
|
||||
className="bg-white"
|
||||
placeholder="Create your own Survey"
|
||||
value={localSurvey.thankYouCard.buttonLabel}
|
||||
onChange={(e) => updateSurvey({ buttonLabel: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Button Link</Label>
|
||||
<Input
|
||||
id="buttonLink"
|
||||
name="buttonLink"
|
||||
className="bg-white"
|
||||
placeholder="https://formbricks.com/signup"
|
||||
value={localSurvey.thankYouCard.buttonLink}
|
||||
onChange={(e) => updateSurvey({ buttonLink: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Input } from "@formbricks/ui/Input";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/Tooltip";
|
||||
|
||||
import { deleteSurveyAction, updateSurveyAction } from "../actions";
|
||||
import { validateQuestion } from "./Validation";
|
||||
import { isValidUrl, validateQuestion } from "./Validation";
|
||||
|
||||
interface SurveyMenuBarProps {
|
||||
localSurvey: TSurvey;
|
||||
@@ -122,6 +122,26 @@ export default function SurveyMenuBar({
|
||||
return;
|
||||
}
|
||||
|
||||
const { thankYouCard } = localSurvey;
|
||||
if (thankYouCard.enabled) {
|
||||
const { buttonLabel, buttonLink } = thankYouCard;
|
||||
|
||||
if (buttonLabel && !buttonLink) {
|
||||
toast.error("Button Link missing on Thank you card.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!buttonLabel && buttonLink) {
|
||||
toast.error("Button Label missing on Thank you card.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonLink && !isValidUrl(buttonLink)) {
|
||||
toast.error("Invalid URL on Thank You card.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
faultyQuestions = [];
|
||||
for (let index = 0; index < survey.questions.length; index++) {
|
||||
const question = survey.questions[index];
|
||||
|
||||
@@ -37,3 +37,12 @@ const validateQuestion = (question) => {
|
||||
};
|
||||
|
||||
export { validateQuestion };
|
||||
|
||||
export const isValidUrl = (string: string): boolean => {
|
||||
try {
|
||||
new URL(string);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,6 +12,8 @@ const thankYouCardDefault = {
|
||||
enabled: true,
|
||||
headline: "Thank you!",
|
||||
subheader: "We appreciate your feedback.",
|
||||
buttonLabel: "Create your own Survey",
|
||||
buttonLink: "https://formbricks.com/signup",
|
||||
};
|
||||
|
||||
const hiddenFieldsDefault: TSurveyHiddenFields = {
|
||||
|
||||
@@ -488,13 +488,16 @@ export const createSurvey = async (environmentId: string, surveyBody: TSurveyInp
|
||||
const actionClasses = await getActionClasses(environmentId);
|
||||
revalidateSurveyByActionClassId(actionClasses, surveyBody.triggers);
|
||||
}
|
||||
|
||||
// TODO: Create with triggers & attributeFilters
|
||||
delete surveyBody.triggers;
|
||||
delete surveyBody.attributeFilters;
|
||||
const data: Omit<TSurveyInput, "triggers" | "attributeFilters"> = {
|
||||
...surveyBody,
|
||||
};
|
||||
if (surveyBody.type === "web" && data.thankYouCard) {
|
||||
data.thankYouCard.buttonLabel = "";
|
||||
data.thankYouCard.buttonLink = "";
|
||||
}
|
||||
|
||||
const survey = await prisma.survey.create({
|
||||
data: {
|
||||
|
||||
@@ -217,6 +217,9 @@ export function Survey({
|
||||
? replaceRecallInfo(survey.thankYouCard.subheader)
|
||||
: ""
|
||||
}
|
||||
buttonLabel={survey.thankYouCard.buttonLabel}
|
||||
buttonLink={survey.thankYouCard.buttonLink}
|
||||
imageUrl={survey.thankYouCard.imageUrl}
|
||||
redirectUrl={survey.redirectUrl}
|
||||
isRedirectDisabled={isRedirectDisabled}
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import Button from "@/components/buttons/SubmitButton";
|
||||
import Headline from "@/components/general/Headline";
|
||||
import QuestionImage from "@/components/general/QuestionImage";
|
||||
import RedirectCountDown from "@/components/general/RedirectCountdown";
|
||||
import Subheader from "@/components/general/Subheader";
|
||||
import { useEffect } from "preact/hooks";
|
||||
|
||||
interface ThankYouCardProps {
|
||||
headline?: string;
|
||||
subheader?: string;
|
||||
redirectUrl: string | null;
|
||||
isRedirectDisabled: boolean;
|
||||
buttonLabel?: string;
|
||||
buttonLink?: string;
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
export default function ThankYouCard({
|
||||
@@ -14,9 +20,27 @@ export default function ThankYouCard({
|
||||
subheader,
|
||||
redirectUrl,
|
||||
isRedirectDisabled,
|
||||
buttonLabel,
|
||||
buttonLink,
|
||||
imageUrl,
|
||||
}: ThankYouCardProps) {
|
||||
useEffect(() => {
|
||||
if (!buttonLink) return;
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Enter") {
|
||||
window.location.href = buttonLink;
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [buttonLink]);
|
||||
|
||||
return (
|
||||
<div className="text-center">
|
||||
{imageUrl && <QuestionImage imgUrl={imageUrl} />}
|
||||
|
||||
<div className="text-brand flex items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -39,6 +63,19 @@ export default function ThankYouCard({
|
||||
<Headline alignTextCenter={true} headline={headline} questionId="thankYouCard" />
|
||||
<Subheader subheader={subheader} questionId="thankYouCard" />
|
||||
<RedirectCountDown redirectUrl={redirectUrl} isRedirectDisabled={isRedirectDisabled} />
|
||||
{buttonLabel && (
|
||||
<div className="mt-6 flex w-full flex-col items-center justify-center space-y-4">
|
||||
<Button
|
||||
buttonLabel={buttonLabel}
|
||||
isLastQuestion={false}
|
||||
onClick={() => {
|
||||
if (!buttonLink) return;
|
||||
window.location.href = buttonLink;
|
||||
}}
|
||||
/>
|
||||
<p class="text-xs">Press Enter ↵</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,9 @@ export const ZSurveyThankYouCard = z.object({
|
||||
enabled: z.boolean(),
|
||||
headline: z.optional(z.string()),
|
||||
subheader: z.optional(z.string()),
|
||||
buttonLabel: z.optional(z.string()),
|
||||
buttonLink: z.optional(z.string()),
|
||||
imageUrl: z.string().optional(),
|
||||
});
|
||||
|
||||
export enum TSurveyQuestionType {
|
||||
|
||||
@@ -63,7 +63,11 @@ const QuestionFormInput = ({
|
||||
const fallbackInputRef = useRef<HTMLInputElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [showImageUploader, setShowImageUploader] = useState<boolean>(
|
||||
questionId === "end" ? false : !!(question as TSurveyQuestion).imageUrl && type === "headline"
|
||||
questionId === "end"
|
||||
? localSurvey.thankYouCard.imageUrl
|
||||
? true
|
||||
: false
|
||||
: !!(question as TSurveyQuestion).imageUrl
|
||||
);
|
||||
const [showQuestionSelect, setShowQuestionSelect] = useState(false);
|
||||
const [showFallbackInput, setShowFallbackInput] = useState(false);
|
||||
@@ -244,17 +248,21 @@ const QuestionFormInput = ({
|
||||
<div className="mt-3 w-full">
|
||||
<Label htmlFor="headline">{type === "headline" ? "Question" : "Description"}</Label>
|
||||
<div className="mt-2 flex flex-col gap-6 overflow-hidden">
|
||||
{showImageUploader && (
|
||||
{showImageUploader && type === "headline" && (
|
||||
<FileInput
|
||||
id="question-image"
|
||||
allowedFileExtensions={["png", "jpeg", "jpg"]}
|
||||
environmentId={environmentId}
|
||||
onFileUpload={(url: string[] | undefined) => {
|
||||
if (updateQuestion && url) {
|
||||
if (isThankYouCard && updateSurvey && url) {
|
||||
updateSurvey({ imageUrl: url[0] });
|
||||
} else if (updateQuestion && url) {
|
||||
updateQuestion(questionIdx, { imageUrl: url[0] });
|
||||
}
|
||||
}}
|
||||
fileUrl={isThankYouCard ? "" : (question as TSurveyQuestion).imageUrl}
|
||||
fileUrl={
|
||||
isThankYouCard ? localSurvey.thankYouCard.imageUrl : (question as TSurveyQuestion).imageUrl
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center space-x-2">
|
||||
|
||||
Reference in New Issue
Block a user