mirror of
https://github.com/formbricks/formbricks.git
synced 2026-03-06 18:39:28 -06:00
Feature Thank You Card (#222)
add thankyou card to surveys. --------- Co-authored-by: Johannes <johannes@formbricks.com> Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import clsx from "clsx";
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { testURLmatch } from "./testURLmatch";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
interface EventDetailModalProps {
|
||||
environmentId: string;
|
||||
@@ -70,6 +71,8 @@ export default function AddNoCodeEventModal({
|
||||
watch("noCodeConfig.[pageUrl].rule")
|
||||
);
|
||||
setIsMatch(match);
|
||||
if (match === "yes") toast.success("Your survey would be shown on this URL.");
|
||||
if (match === "no") toast.error("Your survey would not be shown.");
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -82,9 +85,9 @@ export default function AddNoCodeEventModal({
|
||||
<CursorArrowRaysIcon />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xl font-medium text-slate-700">Add No-Code Event</div>
|
||||
<div className="text-xl font-medium text-slate-700">Add Trigger Event</div>
|
||||
<div className="text-sm text-slate-500">
|
||||
Create a new no-code event to filter your user base with.
|
||||
Create a new trigger event to show the survey at a specific point in the user journey.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ export function EditTeamName() {
|
||||
<Label htmlFor="teamname">Team Name</Label>
|
||||
<Input type="text" id="teamname" />
|
||||
|
||||
<Button type="submit" className="mt-4" onClick={(e) => console.log(e)}>
|
||||
<Button type="submit" className="mt-4" onClick={() => new Error("Not implemented yet")}>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,52 @@
|
||||
import { Survey } from "@formbricks/types/surveys";
|
||||
import Modal from "@/components/preview/Modal";
|
||||
import MultipleChoiceSingleQuestion from "@/components/preview/MultipleChoiceSingleQuestion";
|
||||
import OpenTextQuestion from "@/components/preview/OpenTextQuestion";
|
||||
import ThankYouCard from "@/components/preview/ThankYouCard";
|
||||
import type { Question } from "@formbricks/types/questions";
|
||||
import { useEffect, useState } from "react";
|
||||
import QuestionConditional from "@/components/preview/QuestionConditional";
|
||||
|
||||
interface PreviewSurveyProps {
|
||||
localSurvey?: Survey;
|
||||
setActiveQuestionId: (id: string | null) => void;
|
||||
activeQuestionId?: string | null;
|
||||
questions: Question[];
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function PreviewSurvey({ activeQuestionId, questions, brandColor }: PreviewSurveyProps) {
|
||||
export default function PreviewSurvey({
|
||||
localSurvey,
|
||||
setActiveQuestionId,
|
||||
activeQuestionId,
|
||||
questions,
|
||||
brandColor,
|
||||
}: PreviewSurveyProps) {
|
||||
const [isModalOpen, setIsModalOpen] = useState(true);
|
||||
const [currentQuestion, setCurrentQuestion] = useState<Question | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!localSurvey?.thankYouCard.enabled) {
|
||||
if (activeQuestionId === "thank-you-card") {
|
||||
setIsModalOpen(false);
|
||||
setTimeout(() => {
|
||||
setCurrentQuestion(questions[0]);
|
||||
setActiveQuestionId(questions[0].id);
|
||||
setIsModalOpen(true);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}, [localSurvey]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentIndex = questions.findIndex((q) => q.id === currentQuestion?.id);
|
||||
if (currentIndex < questions.length && currentIndex >= 0 && !localSurvey) return;
|
||||
|
||||
if (activeQuestionId) {
|
||||
if (currentQuestion && currentQuestion.id === activeQuestionId) {
|
||||
setCurrentQuestion(questions.find((q) => q.id === activeQuestionId) || null);
|
||||
return;
|
||||
}
|
||||
if (activeQuestionId === "thank-you-card") return;
|
||||
|
||||
setIsModalOpen(false);
|
||||
setTimeout(() => {
|
||||
setCurrentQuestion(questions.find((q) => q.id === activeQuestionId) || null);
|
||||
@@ -30,24 +57,38 @@ export default function PreviewSurvey({ activeQuestionId, questions, brandColor
|
||||
setCurrentQuestion(questions[0]);
|
||||
}
|
||||
}
|
||||
}, [activeQuestionId, questions]);
|
||||
}, [activeQuestionId, currentQuestion, localSurvey, questions]);
|
||||
|
||||
const gotoNextQuestion = () => {
|
||||
if (currentQuestion) {
|
||||
const currentIndex = questions.findIndex((q) => q.id === currentQuestion.id);
|
||||
if (currentIndex < questions.length - 1) {
|
||||
setCurrentQuestion(questions[currentIndex + 1]);
|
||||
setActiveQuestionId(questions[currentIndex + 1].id);
|
||||
} else {
|
||||
// start over
|
||||
setIsModalOpen(false);
|
||||
setTimeout(() => {
|
||||
setCurrentQuestion(questions[0]);
|
||||
setIsModalOpen(true);
|
||||
}, 500);
|
||||
if (localSurvey?.thankYouCard?.enabled) {
|
||||
setActiveQuestionId("thank-you-card");
|
||||
} else {
|
||||
setIsModalOpen(false);
|
||||
setTimeout(() => {
|
||||
setCurrentQuestion(questions[0]);
|
||||
setActiveQuestionId(questions[0].id);
|
||||
setIsModalOpen(true);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const resetPreview = () => {
|
||||
setIsModalOpen(false);
|
||||
setTimeout(() => {
|
||||
setCurrentQuestion(questions[0]);
|
||||
setActiveQuestionId(questions[0].id);
|
||||
setIsModalOpen(true);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
if (!currentQuestion) {
|
||||
return null;
|
||||
}
|
||||
@@ -55,22 +96,21 @@ export default function PreviewSurvey({ activeQuestionId, questions, brandColor
|
||||
const lastQuestion = currentQuestion.id === questions[questions.length - 1].id;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isModalOpen}>
|
||||
{currentQuestion.type === "openText" ? (
|
||||
<OpenTextQuestion
|
||||
question={currentQuestion}
|
||||
onSubmit={() => gotoNextQuestion()}
|
||||
lastQuestion={lastQuestion}
|
||||
<Modal isOpen={isModalOpen} reset={resetPreview}>
|
||||
{activeQuestionId == "thank-you-card" ? (
|
||||
<ThankYouCard
|
||||
brandColor={brandColor}
|
||||
headline={localSurvey?.thankYouCard?.headline || ""}
|
||||
subheader={localSurvey?.thankYouCard?.subheader || ""}
|
||||
/>
|
||||
) : currentQuestion.type === "multipleChoiceSingle" ? (
|
||||
<MultipleChoiceSingleQuestion
|
||||
question={currentQuestion}
|
||||
onSubmit={() => gotoNextQuestion()}
|
||||
lastQuestion={lastQuestion}
|
||||
) : (
|
||||
<QuestionConditional
|
||||
currentQuestion={currentQuestion}
|
||||
brandColor={brandColor}
|
||||
lastQuestion={lastQuestion}
|
||||
onSubmit={gotoNextQuestion}
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { PlusIcon } from "@heroicons/react/24/solid";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { useState } from "react";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
|
||||
interface AddQuestionButtonProps {
|
||||
addQuestion: (question: any) => void;
|
||||
@@ -16,7 +17,10 @@ export default function AddQuestionButton({ addQuestion }: AddQuestionButtonProp
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
className=" w-full space-y-2 rounded-lg border border-dashed border-slate-300 bg-white hover:cursor-pointer">
|
||||
className={cn(
|
||||
open ? "scale-100 shadow-lg" : "scale-97 shadow-md",
|
||||
"w-full space-y-2 rounded-lg border border-dashed border-slate-300 bg-white transition-transform duration-300 ease-in-out hover:cursor-pointer"
|
||||
)}>
|
||||
<Collapsible.CollapsibleTrigger asChild className="group h-full w-full">
|
||||
<div className="inline-flex">
|
||||
<div className="bg-brand-dark flex w-10 items-center justify-center rounded-l-lg group-aria-expanded:rounded-bl-none">
|
||||
@@ -33,7 +37,7 @@ export default function AddQuestionButton({ addQuestion }: AddQuestionButtonProp
|
||||
{questionTypes.map((questionType) => (
|
||||
<button
|
||||
key={questionType.id}
|
||||
className="inline-flex items-center py-2 px-4 text-sm font-medium text-slate-700 last:mb-2 hover:bg-slate-100"
|
||||
className="inline-flex items-center px-4 py-2 text-sm font-medium text-slate-700 last:mb-2 hover:bg-slate-100"
|
||||
onClick={() => {
|
||||
addQuestion({
|
||||
id: createId(),
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { Input, Label, Switch } from "@formbricks/ui";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
|
||||
interface EditThankYouCardProps {
|
||||
localSurvey: Survey;
|
||||
setLocalSurvey: (survey: Survey) => void;
|
||||
setActiveQuestionId: (id: string | null) => void;
|
||||
activeQuestionId: string | null;
|
||||
}
|
||||
|
||||
export default function EditThankYouCard({
|
||||
localSurvey,
|
||||
setLocalSurvey,
|
||||
setActiveQuestionId,
|
||||
activeQuestionId,
|
||||
}: EditThankYouCardProps) {
|
||||
// const [open, setOpen] = useState(false);
|
||||
let open = activeQuestionId == "thank-you-card";
|
||||
const setOpen = (e) => {
|
||||
if (e) {
|
||||
setActiveQuestionId("thank-you-card");
|
||||
} else {
|
||||
setActiveQuestionId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const updateSurvey = (data) => {
|
||||
setLocalSurvey({
|
||||
...localSurvey,
|
||||
thankYouCard: {
|
||||
...localSurvey.thankYouCard,
|
||||
...data,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
open ? "scale-100 shadow-lg" : "scale-97 shadow-md",
|
||||
"flex flex-row rounded-lg bg-white transition-transform duration-300 ease-in-out"
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
open ? "bg-slate-700" : "bg-slate-400",
|
||||
"flex w-10 items-center justify-center rounded-l-lg hover:bg-slate-600 group-aria-expanded:rounded-bl-none"
|
||||
)}>
|
||||
<p>🙏</p>
|
||||
</div>
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
className="flex-1 rounded-r-lg border border-slate-200 transition-all duration-300 ease-in-out">
|
||||
<Collapsible.CollapsibleTrigger
|
||||
asChild
|
||||
className="flex cursor-pointer justify-between p-4 hover:bg-slate-50">
|
||||
<div>
|
||||
<div className="inline-flex">
|
||||
<div>
|
||||
<p className="text-sm font-semibold">Thank You Card</p>
|
||||
{!open && (
|
||||
<p className="mt-1 truncate text-xs text-slate-500">
|
||||
{localSurvey?.thankYouCard?.enabled ? "Shown" : "Hidden"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="thank-you-toggle">Show</Label>
|
||||
<Switch
|
||||
id="thank-you-toggle"
|
||||
checked={localSurvey?.thankYouCard?.enabled}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
updateSurvey({ enabled: !localSurvey.thankYouCard?.enabled });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
<Collapsible.CollapsibleContent className="px-4 pb-4">
|
||||
<form>
|
||||
<div className="mt-3">
|
||||
<Label htmlFor="headline">Headline</Label>
|
||||
<div className="mt-2">
|
||||
<Input
|
||||
id="headline"
|
||||
name="headline"
|
||||
defaultValue={localSurvey?.thankYouCard?.headline}
|
||||
onChange={(e) => {
|
||||
updateSurvey({ headline: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
<Label htmlFor="subheader">Description</Label>
|
||||
<div className="mt-2">
|
||||
<Input
|
||||
id="subheader"
|
||||
name="subheader"
|
||||
defaultValue={localSurvey?.thankYouCard?.subheader}
|
||||
onChange={(e) => {
|
||||
updateSurvey({ subheader: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export default function MultipleChoiceSingleForm({
|
||||
return (
|
||||
<form>
|
||||
<div className="mt-3">
|
||||
<Label htmlFor="headline">Headline</Label>
|
||||
<Label htmlFor="headline">Question</Label>
|
||||
<div className="mt-2">
|
||||
<Input
|
||||
id="headline"
|
||||
@@ -56,7 +56,7 @@ export default function MultipleChoiceSingleForm({
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
<Label htmlFor="subheader">Subheader</Label>
|
||||
<Label htmlFor="subheader">Description</Label>
|
||||
<div className="mt-2">
|
||||
<Input
|
||||
id="subheader"
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function OpenQuestionForm({
|
||||
return (
|
||||
<form>
|
||||
<div className="mt-3">
|
||||
<Label htmlFor="headline">Headline</Label>
|
||||
<Label htmlFor="headline">Question</Label>
|
||||
<div className="mt-2">
|
||||
<Input
|
||||
id="headline"
|
||||
@@ -30,7 +30,7 @@ export default function OpenQuestionForm({
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
<Label htmlFor="subheader">Subheader</Label>
|
||||
<Label htmlFor="subheader">Description</Label>
|
||||
<div className="mt-2">
|
||||
<Input
|
||||
id="subheader"
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Switch } from "@formbricks/ui";
|
||||
import { getQuestionTypeName } from "@/lib/questions";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import type { Question } from "@formbricks/types/questions";
|
||||
import { Bars4Icon } from "@heroicons/react/24/solid";
|
||||
import { Bars3BottomLeftIcon } from "@heroicons/react/24/solid";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import MultipleChoiceSingleForm from "./MultipleChoiceSingleForm";
|
||||
@@ -36,14 +36,17 @@ export default function QuestionCard({
|
||||
<Draggable draggableId={question.id} index={questionIdx}>
|
||||
{(provided) => (
|
||||
<div
|
||||
className="flex flex-row rounded-lg bg-white shadow-lg"
|
||||
className={cn(
|
||||
open ? "scale-100 shadow-lg" : "scale-97 shadow-md",
|
||||
"flex flex-row rounded-lg bg-white transition-all duration-300 ease-in-out"
|
||||
)}
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}>
|
||||
<div
|
||||
className={cn(
|
||||
open ? "bg-slate-600" : "bg-slate-500",
|
||||
"top-0 w-10 cursor-grabbing rounded-l-lg p-2 text-center text-sm text-white hover:bg-slate-700"
|
||||
open ? "bg-slate-700" : "bg-slate-400",
|
||||
"top-0 w-10 cursor-move rounded-l-lg p-2 text-center text-sm text-white hover:bg-slate-600"
|
||||
)}>
|
||||
{questionIdx + 1}
|
||||
</div>
|
||||
@@ -58,7 +61,7 @@ export default function QuestionCard({
|
||||
className="flex cursor-pointer justify-between p-4 hover:bg-slate-50">
|
||||
<div>
|
||||
<div className="inline-flex">
|
||||
<Bars4Icon className="-ml-0.5 mr-2 h-5 w-5 text-slate-400" />
|
||||
<Bars3BottomLeftIcon className="-ml-0.5 mr-2 h-5 w-5 text-slate-400" />
|
||||
<div>
|
||||
<p className="text-sm font-semibold">
|
||||
{question.headline || getQuestionTypeName(question.type)}
|
||||
@@ -70,20 +73,23 @@ export default function QuestionCard({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{open && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="airplane-mode">Required</Label>
|
||||
<Switch
|
||||
id="airplane-mode"
|
||||
checked={question.required}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
updateQuestion(questionIdx, { required: !question.required });
|
||||
}}
|
||||
/>
|
||||
<QuestionDropdown deleteQuestion={deleteQuestion} questionIdx={questionIdx} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{open && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="required-toggle">Required</Label>
|
||||
<Switch
|
||||
id="required-toggle"
|
||||
checked={question.required}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
updateQuestion(questionIdx, { required: !question.required });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<QuestionDropdown deleteQuestion={deleteQuestion} questionIdx={questionIdx} />
|
||||
</div>
|
||||
</div>
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
<Collapsible.CollapsibleContent className="px-4 pb-4">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { DragDropContext } from "react-beautiful-dnd";
|
||||
import AddQuestionButton from "./AddQuestionButton";
|
||||
import QuestionCard from "./QuestionCard";
|
||||
import { StrictModeDroppable } from "./StrictModeDroppable";
|
||||
import EditThankYouCard from "./EditThankYouCard";
|
||||
|
||||
interface QuestionsViewProps {
|
||||
localSurvey: Survey;
|
||||
@@ -80,6 +81,14 @@ export default function QuestionsView({
|
||||
</div>
|
||||
</DragDropContext>
|
||||
<AddQuestionButton addQuestion={addQuestion} />
|
||||
<div className="mt-5">
|
||||
<EditThankYouCard
|
||||
localSurvey={localSurvey}
|
||||
setLocalSurvey={setLocalSurvey}
|
||||
setActiveQuestionId={setActiveQuestionId}
|
||||
activeQuestionId={activeQuestionId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -83,8 +83,10 @@ export default function SurveyEditor({ environmentId, surveyId }: SurveyEditorPr
|
||||
<aside className="relative hidden h-full flex-1 flex-shrink-0 overflow-hidden border-l border-slate-200 bg-slate-200 shadow-inner md:flex md:flex-col">
|
||||
<PreviewSurvey
|
||||
activeQuestionId={activeQuestionId}
|
||||
setActiveQuestionId={setActiveQuestionId}
|
||||
questions={localSurvey.questions}
|
||||
brandColor={product.brandColor}
|
||||
localSurvey={localSurvey}
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { Survey } from "@formbricks/types/surveys";
|
||||
import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui";
|
||||
import { CheckCircleIcon, PlusIcon, TrashIcon } from "@heroicons/react/24/solid";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import AddNoCodeEventModal from "../../../events/AddNoCodeEventModal";
|
||||
|
||||
interface WhenToSendCardProps {
|
||||
@@ -22,14 +22,6 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur
|
||||
useEventClasses(environmentId);
|
||||
const [isAddEventModalOpen, setAddEventModalOpen] = useState(false);
|
||||
|
||||
if (isLoadingEventClasses) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (isErrorEventClasses) {
|
||||
return <div>Error</div>;
|
||||
}
|
||||
|
||||
const addTriggerEvent = () => {
|
||||
const updatedSurvey = { ...localSurvey };
|
||||
updatedSurvey.triggers = [...localSurvey.triggers, ""];
|
||||
@@ -48,14 +40,23 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur
|
||||
setLocalSurvey(updatedSurvey);
|
||||
};
|
||||
|
||||
/* // If there are no trigger events, set default to first event class in the eventClasses object
|
||||
if (localSurvey.triggers.length === 0 && eventClasses.length > 0) {
|
||||
setTriggerEvent(0, eventClasses[0].id);
|
||||
} */
|
||||
//create new empty trigger on page load, remove one click for user
|
||||
useEffect(() => {
|
||||
if (localSurvey.triggers.length === 0) {
|
||||
addTriggerEvent();
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (isLoadingEventClasses) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (isErrorEventClasses) {
|
||||
return <div>Error</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{" "}
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
@@ -65,7 +66,7 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur
|
||||
)}>
|
||||
<Collapsible.CollapsibleTrigger asChild className="h-full w-full cursor-pointer">
|
||||
<div className="inline-flex px-4 py-6">
|
||||
<div className="flex items-center pr-5 pl-2">
|
||||
<div className="flex items-center pl-2 pr-5">
|
||||
{localSurvey.triggers.length === 0 || !localSurvey.triggers[0] ? (
|
||||
<div className="h-7 w-7 rounded-full border border-slate-400" />
|
||||
) : (
|
||||
@@ -97,6 +98,15 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur
|
||||
{eventClasses.map((eventClass) => (
|
||||
<SelectItem value={eventClass.id}>{eventClass.name}</SelectItem>
|
||||
))}
|
||||
<button
|
||||
className="flex w-full items-center space-x-2 rounded-md p-1 text-sm font-semibold text-slate-800 hover:bg-slate-100 hover:text-slate-500 "
|
||||
value="none"
|
||||
onClick={() => {
|
||||
setAddEventModalOpen(true);
|
||||
}}>
|
||||
<PlusIcon className="mr-1 h-5 w-5" />
|
||||
Create Event
|
||||
</button>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="mx-2 text-sm">event is triggered</p>
|
||||
@@ -106,7 +116,7 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="p-3">
|
||||
<div className="ml-14 p-3">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
@@ -115,13 +125,6 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur
|
||||
<PlusIcon className="mr-2 h-4 w-4" />
|
||||
Add condition
|
||||
</Button>
|
||||
<Button
|
||||
variant="minimal"
|
||||
onClick={() => {
|
||||
setAddEventModalOpen(true);
|
||||
}}>
|
||||
Create event
|
||||
</Button>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
|
||||
@@ -5,18 +5,20 @@ import { useProduct } from "@/lib/products/products";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import type { Template } from "@formbricks/types/templates";
|
||||
import { PlusCircleIcon } from "@heroicons/react/24/outline";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import PreviewSurvey from "../PreviewSurvey";
|
||||
import TemplateMenuBar from "./TemplateMenuBar";
|
||||
import { templates } from "./templates";
|
||||
import { templates, customSurvey } from "./templates";
|
||||
import { PaintBrushIcon } from "@heroicons/react/24/solid";
|
||||
import { ErrorComponent } from "@formbricks/ui";
|
||||
import { replacePresetPlaceholders } from "@/lib/templates";
|
||||
|
||||
export default function TemplateList({ environmentId }: { environmentId: string }) {
|
||||
const [activeTemplate, setActiveTemplate] = useState<Template | null>(null);
|
||||
|
||||
const [activeQuestionId, setActiveQuestionId] = useState<string | null>(null);
|
||||
|
||||
const { product, isLoadingProduct, isErrorProduct } = useProduct(environmentId);
|
||||
const [selectedFilter, setSelectedFilter] = useState("All");
|
||||
const categories = [
|
||||
@@ -33,25 +35,6 @@ export default function TemplateList({ environmentId }: { environmentId: string
|
||||
if (isLoadingProduct) return <LoadingSpinner />;
|
||||
if (isErrorProduct) return <ErrorComponent />;
|
||||
|
||||
const customSurvey: Template = {
|
||||
name: "Custom Survey",
|
||||
description: "Create your survey from scratch.",
|
||||
icon: null,
|
||||
preset: {
|
||||
name: "New Survey",
|
||||
questions: [
|
||||
{
|
||||
id: createId(),
|
||||
type: "openText",
|
||||
headline: "What's poppin?",
|
||||
subheader: "This can help us improve your experience.",
|
||||
placeholder: "Type your answer here...",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<TemplateMenuBar activeTemplate={activeTemplate} environmentId={environmentId} />
|
||||
@@ -79,17 +62,20 @@ export default function TemplateList({ environmentId }: { environmentId: string
|
||||
.map((template: Template) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTemplate(replacePresetPlaceholders(template, product))}
|
||||
onClick={() => {
|
||||
setActiveQuestionId(null);
|
||||
setActiveTemplate(replacePresetPlaceholders(template, product));
|
||||
}}
|
||||
key={template.name}
|
||||
className={cn(
|
||||
activeTemplate?.name === template.name && "ring-brand ring-2",
|
||||
"duration-120 group relative rounded-lg bg-white p-6 shadow transition-all duration-150 hover:scale-105"
|
||||
)}>
|
||||
<div className="absolute top-6 right-6 rounded border border-slate-300 bg-slate-50 px-1.5 py-0.5 text-xs text-slate-500">
|
||||
<div className="absolute right-6 top-6 rounded border border-slate-300 bg-slate-50 px-1.5 py-0.5 text-xs text-slate-500">
|
||||
{template.category}
|
||||
</div>
|
||||
<template.icon className="h-8 w-8" />
|
||||
<h3 className="text-md mt-3 mb-1 text-left font-bold text-slate-700">{template.name}</h3>
|
||||
<h3 className="text-md mb-1 mt-3 text-left font-bold text-slate-700">{template.name}</h3>
|
||||
<p className="text-left text-xs text-slate-600">{template.description}</p>
|
||||
</button>
|
||||
))}
|
||||
@@ -101,7 +87,7 @@ export default function TemplateList({ environmentId }: { environmentId: string
|
||||
"duration-120 hover:border-brand-dark group relative rounded-lg border-2 border-dashed border-slate-300 bg-transparent p-8 transition-colors duration-150"
|
||||
)}>
|
||||
<PlusCircleIcon className="text-brand-dark h-8 w-8 transition-all duration-150 group-hover:scale-110" />
|
||||
<h3 className="text-md mt-3 mb-1 text-left font-bold text-slate-700 ">{customSurvey.name}</h3>
|
||||
<h3 className="text-md mb-1 mt-3 text-left font-bold text-slate-700 ">{customSurvey.name}</h3>
|
||||
<p className="text-left text-xs text-slate-600 ">{customSurvey.description}</p>
|
||||
</button>
|
||||
</div>
|
||||
@@ -114,9 +100,10 @@ export default function TemplateList({ environmentId }: { environmentId: string
|
||||
</Link>
|
||||
{activeTemplate && (
|
||||
<PreviewSurvey
|
||||
activeQuestionId={null}
|
||||
activeQuestionId={activeQuestionId}
|
||||
questions={activeTemplate.preset.questions}
|
||||
brandColor={product.brandColor}
|
||||
setActiveQuestionId={setActiveQuestionId}
|
||||
/>
|
||||
)}
|
||||
</aside>
|
||||
|
||||
@@ -14,6 +14,12 @@ import { ArrowRightCircleIcon } from "@formbricks/ui";
|
||||
import type { Template } from "@formbricks/types/templates";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
|
||||
const thankYouCardDefault = {
|
||||
enabled: false,
|
||||
headline: "Thank you!",
|
||||
subheader: "We appreciate your time and insight.",
|
||||
};
|
||||
|
||||
export const templates: Template[] = [
|
||||
{
|
||||
name: "Product Market Fit Survey",
|
||||
@@ -81,6 +87,7 @@ export const templates: Template[] = [
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -179,6 +186,7 @@ export const templates: Template[] = [
|
||||
],
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -247,6 +255,7 @@ export const templates: Template[] = [
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -287,6 +296,7 @@ export const templates: Template[] = [
|
||||
],
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -334,6 +344,7 @@ export const templates: Template[] = [
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -380,6 +391,7 @@ export const templates: Template[] = [
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -439,6 +451,7 @@ export const templates: Template[] = [
|
||||
],
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -476,6 +489,7 @@ export const templates: Template[] = [
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -512,6 +526,7 @@ export const templates: Template[] = [
|
||||
],
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -558,6 +573,7 @@ export const templates: Template[] = [
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -589,6 +605,7 @@ export const templates: Template[] = [
|
||||
],
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -623,6 +640,7 @@ export const templates: Template[] = [
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -669,6 +687,7 @@ export const templates: Template[] = [
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
},
|
||||
/* {
|
||||
@@ -690,3 +709,23 @@ export const templates: Template[] = [
|
||||
},s
|
||||
}, */
|
||||
];
|
||||
|
||||
export const customSurvey: Template = {
|
||||
name: "Custom Survey",
|
||||
description: "Create your survey from scratch.",
|
||||
icon: null,
|
||||
preset: {
|
||||
name: "New Survey",
|
||||
questions: [
|
||||
{
|
||||
id: createId(),
|
||||
type: "openText",
|
||||
headline: "What's poppin?",
|
||||
subheader: "This can help us improve your experience.",
|
||||
placeholder: "Type your answer here...",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { ArrowPathIcon } from "@heroicons/react/24/solid";
|
||||
|
||||
export default function Modal({ children, isOpen }: { children: ReactNode; isOpen: boolean }) {
|
||||
export default function Modal({
|
||||
children,
|
||||
isOpen,
|
||||
reset,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
isOpen: boolean;
|
||||
reset: () => void;
|
||||
}) {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setShow(isOpen);
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-live="assertive"
|
||||
className="pointer-events-none absolute inset-0 flex items-end px-4 py-6 sm:p-6">
|
||||
<div aria-live="assertive" className="absolute inset-0 flex cursor-pointer items-end px-4 py-6 sm:p-6">
|
||||
<div className="flex w-full flex-col items-center sm:items-end">
|
||||
<div className="mr-6 rounded-t bg-amber-400 px-3 text-sm font-semibold text-white">Preview</div>
|
||||
<div
|
||||
className="mr-6 flex items-center rounded-t bg-amber-500 px-3 text-sm font-semibold text-white hover:cursor-pointer"
|
||||
onClick={reset}>
|
||||
<ArrowPathIcon className="mr-1.5 mt-0.5 h-4 w-4 " />
|
||||
Preview
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
show ? "translate-x-0 opacity-100" : "translate-x-28 opacity-0",
|
||||
|
||||
33
apps/web/components/preview/QuestionConditional.tsx
Normal file
33
apps/web/components/preview/QuestionConditional.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { Question } from "@formbricks/types/questions";
|
||||
import OpenTextQuestion from "./OpenTextQuestion";
|
||||
import MultipleChoiceSingleQuestion from "./MultipleChoiceSingleQuestion";
|
||||
|
||||
interface QuestionConditionalProps {
|
||||
currentQuestion: Question;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
lastQuestion: boolean;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function QuestionConditional({
|
||||
currentQuestion,
|
||||
onSubmit,
|
||||
lastQuestion,
|
||||
brandColor,
|
||||
}: QuestionConditionalProps) {
|
||||
return currentQuestion.type === "openText" ? (
|
||||
<OpenTextQuestion
|
||||
question={currentQuestion}
|
||||
onSubmit={onSubmit}
|
||||
lastQuestion={lastQuestion}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
) : currentQuestion.type === "multipleChoiceSingle" ? (
|
||||
<MultipleChoiceSingleQuestion
|
||||
question={currentQuestion}
|
||||
onSubmit={onSubmit}
|
||||
lastQuestion={lastQuestion}
|
||||
brandColor={brandColor}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
50
apps/web/components/preview/ThankYouCard.tsx
Normal file
50
apps/web/components/preview/ThankYouCard.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import Headline from "./Headline";
|
||||
import Subheader from "./Subheader";
|
||||
|
||||
interface ThankYouCardProps {
|
||||
headline: string;
|
||||
subheader: string;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function ThankYouCard({ headline, subheader, brandColor }: ThankYouCardProps) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center" style={{ color: brandColor }}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className="h-24 w-24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span className="mb-[10px] inline-block h-1 w-16 rounded-[100%] bg-slate-300"></span>
|
||||
|
||||
<div>
|
||||
<Headline headline={headline} questionId="thankYouCard" />
|
||||
<Subheader subheader={subheader} questionId="thankYouCard" />
|
||||
</div>
|
||||
{/* <span
|
||||
className="mb-[10px] mt-[35px] inline-block h-[2px] w-4/5 rounded-full opacity-25"
|
||||
style={{ backgroundColor: brandColor }}></span>
|
||||
|
||||
<div>
|
||||
<p className="text-xs text-slate-500">
|
||||
Powered by{" "}
|
||||
<b>
|
||||
<a href="https://formbricks.com" target="_blank" className="hover:text-slate-700">
|
||||
Formbricks
|
||||
</a>
|
||||
</b>
|
||||
</p>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export const getSettings = async (environmentId: string, personId: string): Prom
|
||||
throw new Error("Product not found");
|
||||
}
|
||||
|
||||
// get all surveys that meed the displayOption criteria
|
||||
// get all surveys that meet the displayOption criteria
|
||||
const potentialSurveys = await prisma.survey.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
@@ -60,8 +60,8 @@ export const getSettings = async (environmentId: string, personId: string): Prom
|
||||
},
|
||||
},
|
||||
},
|
||||
// last display
|
||||
},
|
||||
// last display
|
||||
displays: {
|
||||
where: {
|
||||
personId,
|
||||
@@ -74,6 +74,7 @@ export const getSettings = async (environmentId: string, personId: string): Prom
|
||||
createdAt: true,
|
||||
},
|
||||
},
|
||||
thankYouCard: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -125,6 +126,7 @@ export const getSettings = async (environmentId: string, personId: string): Prom
|
||||
id: survey.id,
|
||||
questions: JSON.parse(JSON.stringify(survey.questions)),
|
||||
triggers: survey.triggers,
|
||||
thankYouCard: JSON.parse(JSON.stringify(survey.thankYouCard)),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Bars4Icon, ListBulletIcon } from "@heroicons/react/24/solid";
|
||||
import { Bars3BottomLeftIcon, ListBulletIcon } from "@heroicons/react/24/solid";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
|
||||
export type QuestionType = {
|
||||
@@ -14,7 +14,7 @@ export const questionTypes: QuestionType[] = [
|
||||
id: "openText",
|
||||
label: "Open text",
|
||||
description: "A single line of text",
|
||||
icon: Bars4Icon,
|
||||
icon: Bars3BottomLeftIcon,
|
||||
defaults: {
|
||||
placeholder: "Type your answer here...",
|
||||
},
|
||||
|
||||
@@ -38,8 +38,6 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
else if (req.method === "POST") {
|
||||
const eventClass = req.body;
|
||||
|
||||
console.log(eventClass);
|
||||
|
||||
if (eventClass.type === "automatic") {
|
||||
res.status(400).json({ message: "You are not allowed to create new automatic events" });
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
// lastSyncedAt is the last time the environment was synced (iso string)
|
||||
const { users }: { users: FormbricksUser[] } = req.body;
|
||||
|
||||
console.log(users);
|
||||
|
||||
for (const user of users) {
|
||||
// check if user with this userId as attribute already exists
|
||||
const existingUser = await prisma.person.findFirst({
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Survey" ADD COLUMN "thankYouCard" JSONB NOT NULL DEFAULT '{"enabled": false}';
|
||||
@@ -131,6 +131,7 @@ model Survey {
|
||||
environmentId String
|
||||
status SurveyStatus @default(draft)
|
||||
questions Json @default("[]")
|
||||
thankYouCard Json @default("{\"enabled\": false}")
|
||||
responses Response[]
|
||||
displayOption displayOptions @default(displayOnce)
|
||||
recontactDays Int?
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { h } from "preact";
|
||||
|
||||
export default function Headline({ headline, questionId }: { headline: string; questionId: string }) {
|
||||
export default function Headline({
|
||||
headline,
|
||||
questionId,
|
||||
style,
|
||||
}: {
|
||||
headline: string;
|
||||
questionId: string;
|
||||
style?: any;
|
||||
}) {
|
||||
return (
|
||||
<label
|
||||
htmlFor={questionId}
|
||||
className="fb-block fb-text-base fb-font-semibold fb-leading-6 fb-mr-8 text-slate-900">
|
||||
className="fb-block fb-text-base fb-font-semibold fb-leading-6 fb-mr-8 text-slate-900"
|
||||
style={style}>
|
||||
{headline}
|
||||
</label>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { JsConfig, Survey } from "@formbricks/types/js";
|
||||
import OpenTextQuestion from "./OpenTextQuestion";
|
||||
import MultipleChoiceSingleQuestion from "./MultipleChoiceSingleQuestion";
|
||||
import Progress from "./Progress";
|
||||
import ThankYouCard from "./ThankYouCard";
|
||||
|
||||
interface SurveyViewProps {
|
||||
config: JsConfig;
|
||||
@@ -63,7 +64,14 @@ export default function SurveyView({ config, survey, close, brandColor }: Survey
|
||||
setCurrentQuestion(survey.questions[questionIdx + 1]);
|
||||
} else {
|
||||
setProgress(100);
|
||||
close();
|
||||
|
||||
if (survey.thankYouCard.enabled) {
|
||||
setTimeout(() => {
|
||||
close();
|
||||
}, 2000);
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,7 +82,13 @@ export default function SurveyView({ config, survey, close, brandColor }: Survey
|
||||
loadingElement ? "fb-animate-pulse fb-opacity-60" : "",
|
||||
"fb-p-4 fb-text-slate-800 fb-font-sans"
|
||||
)}>
|
||||
{currentQuestion.type === "multipleChoiceSingle" ? (
|
||||
{progress === 100 && survey.thankYouCard.enabled ? (
|
||||
<ThankYouCard
|
||||
headline={survey.thankYouCard.headline}
|
||||
subheader={survey.thankYouCard.subheader}
|
||||
brandColor={config.settings?.brandColor}
|
||||
/>
|
||||
) : currentQuestion.type === "multipleChoiceSingle" ? (
|
||||
<MultipleChoiceSingleQuestion
|
||||
question={currentQuestion}
|
||||
onSubmit={submitResponse}
|
||||
|
||||
53
packages/js/src/components/ThankYouCard.tsx
Normal file
53
packages/js/src/components/ThankYouCard.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { h } from "preact";
|
||||
import Headline from "./Headline";
|
||||
import Subheader from "./Subheader";
|
||||
|
||||
interface ThankYouCardProps {
|
||||
headline: string;
|
||||
subheader: string;
|
||||
brandColor: string;
|
||||
}
|
||||
|
||||
export default function ThankYouCard({ headline, subheader, brandColor }: ThankYouCardProps) {
|
||||
return (
|
||||
<div className="fb-text-center">
|
||||
<div className="fb-flex fb-items-center fb-justify-center" style={{ color: brandColor }}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="fb-h-24 fb-w-24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<span className="fb-inline-block fb-rounded-[100%] fb-w-16 fb-h-1 fb-mb-[10px] fb-bg-slate-300"></span>
|
||||
|
||||
<div>
|
||||
<Headline headline={headline} questionId="thankYouCard" style={{ "margin-right": 0 }} />
|
||||
<Subheader subheader={subheader} questionId="thankYouCard" />
|
||||
</div>
|
||||
|
||||
{/* <span
|
||||
className="fb-inline-block fb-w-4/5 fb-h-[2px] fb-mt-[35px] fb-mb-[10px]"
|
||||
style={{ backgroundColor: brandColor }}></span>
|
||||
|
||||
<div>
|
||||
<p className="fb-text-xs fb-text-slate-500">
|
||||
Powered by{" "}
|
||||
<b>
|
||||
<a href="https://formbricks.com" target="_blank" className="fb-hover:text-slate-700">
|
||||
Formbricks
|
||||
</a>
|
||||
</b>
|
||||
</p>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -31,6 +31,9 @@ module.exports = {
|
||||
screens: {
|
||||
xs: "430px",
|
||||
},
|
||||
scale: {
|
||||
97: "0.97",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")],
|
||||
|
||||
@@ -70,6 +70,13 @@ export interface Survey {
|
||||
id: string;
|
||||
questions: Question[];
|
||||
triggers: Trigger[];
|
||||
thankYouCard: ThankYouCard;
|
||||
}
|
||||
|
||||
export interface ThankYouCard {
|
||||
enabled: boolean;
|
||||
headline?: string;
|
||||
subheader?: string;
|
||||
}
|
||||
|
||||
export type Question = OpenTextQuestion | MultipleChoiceSingleQuestion;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import type { Survey as PrismaSurvey } from "@prisma/client";
|
||||
import { Question } from "./questions";
|
||||
|
||||
export interface Survey extends Omit<PrismaSurvey, "questions" | "triggers"> {
|
||||
export interface ThankYouCard {
|
||||
enabled: boolean;
|
||||
headline?: string;
|
||||
subheader?: string;
|
||||
}
|
||||
export interface Survey extends Omit<PrismaSurvey, "questions" | "triggers" | "thankYouCard"> {
|
||||
questions: Question[];
|
||||
thankYouCard: ThankYouCard;
|
||||
triggers: string[];
|
||||
numDisplays: number;
|
||||
responseRate: number;
|
||||
|
||||
@@ -8,5 +8,10 @@ export interface Template {
|
||||
preset: {
|
||||
name: string;
|
||||
questions: Question[];
|
||||
thankYouCard: {
|
||||
enabled: boolean;
|
||||
headline: string;
|
||||
subheader: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user