mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-08 16:21:09 -06:00
feat: Ttc on welcome card (#1461)
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
11ede2e517
commit
ac8cf987d3
@@ -157,7 +157,7 @@ export default function EditWelcomeCard({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="mt-8 flex items-center">
|
||||
<div className="mt-8 flex items-center">
|
||||
<div className="mr-2">
|
||||
<Switch
|
||||
id="timeToFinish"
|
||||
@@ -176,7 +176,7 @@ export default function EditWelcomeCard({
|
||||
Display an estimate of completion time for survey
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</form>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
|
||||
@@ -18,7 +18,7 @@ const welcomeCardDefault: TSurveyWelcomeCard = {
|
||||
enabled: false,
|
||||
headline: "Welcome!",
|
||||
html: "Thanks for providing your feedback - let's go!",
|
||||
timeToFinish: false,
|
||||
timeToFinish: true,
|
||||
};
|
||||
|
||||
export const templates: TTemplate[] = [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { TSurveyWithTriggers } from "@formbricks/types/js";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import Progress from "./Progress";
|
||||
import { calculateElementIdx } from "../lib/utils";
|
||||
|
||||
interface ProgressBarProps {
|
||||
survey: TSurveyWithTriggers;
|
||||
@@ -13,6 +14,7 @@ const PROGRESS_INCREMENT = 0.1;
|
||||
export default function ProgressBar({ survey, questionId, brandColor }: ProgressBarProps) {
|
||||
const [progress, setProgress] = useState(0); // [0, 1]
|
||||
const [prevQuestionIdx, setPrevQuestionIdx] = useState(0); // [0, survey.questions.length
|
||||
const [prevQuestionId, setPrevQuestionId] = useState(""); // [0, survey.questions.length
|
||||
|
||||
useEffect(() => {
|
||||
// calculate progress
|
||||
@@ -20,28 +22,10 @@ export default function ProgressBar({ survey, questionId, brandColor }: Progress
|
||||
function calculateProgress(questionId: string, survey: TSurveyWithTriggers, progress: number) {
|
||||
if (survey.questions.length === 0) return 0;
|
||||
if (questionId === "end") return 1;
|
||||
|
||||
let currentQustionIdx = survey.questions.findIndex((e) => e.id === questionId);
|
||||
if (progress > 0 && currentQustionIdx === prevQuestionIdx) return progress;
|
||||
if (progress > 0 && questionId === prevQuestionId) return progress;
|
||||
if (currentQustionIdx === -1) currentQustionIdx = 0;
|
||||
const currentQuestion = survey.questions[currentQustionIdx];
|
||||
const surveyLength = survey.questions.length;
|
||||
const middleIdx = Math.floor(surveyLength / 2);
|
||||
const possibleNextQuestions = currentQuestion.logic?.map((l) => l.destination) || [];
|
||||
|
||||
const getLastQuestionIndex = () => {
|
||||
const lastQuestion = survey.questions
|
||||
.filter((q) => possibleNextQuestions.includes(q.id))
|
||||
.sort((a, b) => survey.questions.indexOf(a) - survey.questions.indexOf(b))
|
||||
.pop();
|
||||
return survey.questions.findIndex((e) => e.id === lastQuestion?.id);
|
||||
};
|
||||
|
||||
let elementIdx = currentQustionIdx || 0.5;
|
||||
const lastprevQuestionIdx = getLastQuestionIndex();
|
||||
|
||||
if (lastprevQuestionIdx > 0) elementIdx = Math.min(middleIdx, lastprevQuestionIdx - 1);
|
||||
if (possibleNextQuestions.includes("end")) elementIdx = middleIdx;
|
||||
const elementIdx = calculateElementIdx(survey, currentQustionIdx);
|
||||
|
||||
const newProgress = elementIdx / survey.questions.length;
|
||||
|
||||
@@ -57,7 +41,7 @@ export default function ProgressBar({ survey, questionId, brandColor }: Progress
|
||||
} else if (newProgress <= progress && progress + PROGRESS_INCREMENT <= 1) {
|
||||
updatedProgress = progress + PROGRESS_INCREMENT;
|
||||
}
|
||||
|
||||
setPrevQuestionId(questionId);
|
||||
setPrevQuestionIdx(currentQustionIdx);
|
||||
return updatedProgress;
|
||||
}
|
||||
|
||||
@@ -131,6 +131,7 @@ export function Survey({
|
||||
timeToFinish={survey.welcomeCard.timeToFinish}
|
||||
brandColor={brandColor}
|
||||
onSubmit={onSubmit}
|
||||
survey={survey}
|
||||
/>
|
||||
);
|
||||
} else if (questionId === "end" && survey.thankYouCard.enabled) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import Headline from "./Headline";
|
||||
import HtmlBody from "./HtmlBody";
|
||||
import SubmitButton from "./SubmitButton";
|
||||
import { calculateElementIdx } from "../lib/utils";
|
||||
import { TSurveyWithTriggers } from "@formbricks/types/js";
|
||||
|
||||
interface WelcomeCardProps {
|
||||
headline?: string;
|
||||
@@ -10,8 +12,26 @@ interface WelcomeCardProps {
|
||||
timeToFinish?: boolean;
|
||||
brandColor: string;
|
||||
onSubmit: (data: { [x: string]: any }) => void;
|
||||
survey: TSurveyWithTriggers;
|
||||
}
|
||||
|
||||
const TimerIcon = () => {
|
||||
return (
|
||||
<div className="mr-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-stopwatch"
|
||||
viewBox="0 0 16 16">
|
||||
<path d="M8.5 5.6a.5.5 0 1 0-1 0v2.9h-3a.5.5 0 0 0 0 1H8a.5.5 0 0 0 .5-.5V5.6z" />
|
||||
<path d="M6.5 1A.5.5 0 0 1 7 .5h2a.5.5 0 0 1 0 1v.57c1.36.196 2.594.78 3.584 1.64a.715.715 0 0 1 .012-.013l.354-.354-.354-.353a.5.5 0 0 1 .707-.708l1.414 1.415a.5.5 0 1 1-.707.707l-.353-.354-.354.354a.512.512 0 0 1-.013.012A7 7 0 1 1 7 2.071V1.5a.5.5 0 0 1-.5-.5zM8 3a6 6 0 1 0 .001 12A6 6 0 0 0 8 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function WelcomeCard({
|
||||
headline,
|
||||
html,
|
||||
@@ -20,7 +40,36 @@ export default function WelcomeCard({
|
||||
timeToFinish,
|
||||
brandColor,
|
||||
onSubmit,
|
||||
survey,
|
||||
}: WelcomeCardProps) {
|
||||
const calculateTimeToComplete = () => {
|
||||
let idx = calculateElementIdx(survey, 0);
|
||||
if (idx === 0.5) {
|
||||
idx = 1;
|
||||
}
|
||||
const timeInSeconds = (survey.questions.length / idx) * 15; //15 seconds per question.
|
||||
if (timeInSeconds > 360) {
|
||||
// If it's more than 6 minutes
|
||||
return "6+ minutes";
|
||||
}
|
||||
// Calculate minutes, if there are any seconds left, add a minute
|
||||
const minutes = Math.floor(timeInSeconds / 60);
|
||||
const remainingSeconds = timeInSeconds % 60;
|
||||
|
||||
if (remainingSeconds > 0) {
|
||||
// If there are any seconds left, we'll need to round up to the next minute
|
||||
if (minutes === 0) {
|
||||
// If less than 1 minute, return 'less than 1 minute'
|
||||
return "less than 1 minute";
|
||||
} else {
|
||||
// If more than 1 minute, return 'less than X minutes', where X is minutes + 1
|
||||
return `less than ${minutes + 1} minutes`;
|
||||
}
|
||||
}
|
||||
// If there are no remaining seconds, just return the number of minutes
|
||||
return `${minutes} minutes`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{fileUrl && (
|
||||
@@ -46,7 +95,12 @@ export default function WelcomeCard({
|
||||
<div className="flex items-center text-xs text-slate-600">Press Enter ↵</div>
|
||||
</div>
|
||||
</div>
|
||||
{timeToFinish && <></>}
|
||||
{timeToFinish && (
|
||||
<div className="item-center mt-4 flex text-slate-500">
|
||||
<TimerIcon />
|
||||
<p className="text-xs">Takes {calculateTimeToComplete()}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ export function evaluateCondition(logic: TSurveyLogic, responseValue: any): bool
|
||||
(Array.isArray(responseValue) && responseValue.length === 0) ||
|
||||
responseValue === "" ||
|
||||
responseValue === null ||
|
||||
responseValue === undefined ||
|
||||
responseValue === "dismissed"
|
||||
);
|
||||
default:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { TSurveyWithTriggers } from "@formbricks/types/js";
|
||||
|
||||
export const cn = (...classes: string[]) => {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
};
|
||||
@@ -45,3 +47,25 @@ export const shuffleQuestions = (array: any[], shuffleOption: string) => {
|
||||
|
||||
return arrayCopy;
|
||||
};
|
||||
|
||||
export const calculateElementIdx = (survey: TSurveyWithTriggers, currentQustionIdx: number): number => {
|
||||
const currentQuestion = survey.questions[currentQustionIdx];
|
||||
const surveyLength = survey.questions.length;
|
||||
const middleIdx = Math.floor(surveyLength / 2);
|
||||
const possibleNextQuestions = currentQuestion?.logic?.map((l) => l.destination) || [];
|
||||
|
||||
const getLastQuestionIndex = () => {
|
||||
const lastQuestion = survey.questions
|
||||
.filter((q) => possibleNextQuestions.includes(q.id))
|
||||
.sort((a, b) => survey.questions.indexOf(a) - survey.questions.indexOf(b))
|
||||
.pop();
|
||||
return survey.questions.findIndex((e) => e.id === lastQuestion?.id);
|
||||
};
|
||||
|
||||
let elementIdx = currentQustionIdx || 0.5;
|
||||
const lastprevQuestionIdx = getLastQuestionIndex();
|
||||
|
||||
if (lastprevQuestionIdx > 0) elementIdx = Math.min(middleIdx, lastprevQuestionIdx - 1);
|
||||
if (possibleNextQuestions.includes("end")) elementIdx = middleIdx;
|
||||
return elementIdx;
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ export const ZSurveyWelcomeCard = z.object({
|
||||
html: z.string().optional(),
|
||||
fileUrl: z.string().optional(),
|
||||
buttonLabel: z.string().optional(),
|
||||
timeToFinish: z.boolean().default(false),
|
||||
timeToFinish: z.boolean().default(true),
|
||||
});
|
||||
|
||||
export const ZSurveyHiddenFields = z.object({
|
||||
|
||||
Reference in New Issue
Block a user