feat: Show the number of Responses to Respondents (#1720)

Co-authored-by: Johannes <johannes@formbricks.com>
This commit is contained in:
Naitik Kapadia
2023-12-04 00:57:05 +05:30
committed by GitHub
parent c8f621cea2
commit 6e08a94da7
16 changed files with 130 additions and 114 deletions

View File

@@ -22,9 +22,9 @@ import {
VideoTabletAdjustIcon,
} from "@formbricks/ui/icons";
import { createId } from "@paralleldrive/cuid2";
import { TTemplate } from "@formbricks/types/templates";
import { TSurveyQuestionType } from "@formbricks/types/surveys";
import { TTemplate } from "@formbricks/types/templates";
import { createId } from "@paralleldrive/cuid2";
const thankYouCardDefault = {
enabled: true,
@@ -32,6 +32,12 @@ const thankYouCardDefault = {
subheader: "We appreciate your feedback.",
};
const welcomeCardDefault = {
enabled: true,
timeToFinish: false,
showResponseCount: false,
};
export const customSurvey: TTemplate = {
name: "Start from scratch",
description: "Create a survey without template.",
@@ -51,10 +57,7 @@ export const customSurvey: TTemplate = {
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -150,10 +153,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -260,10 +260,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -340,10 +337,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -389,10 +383,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -447,10 +438,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -513,10 +501,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -582,10 +567,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -640,10 +622,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -685,10 +664,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -723,10 +699,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -752,10 +725,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -803,10 +773,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -848,10 +815,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -905,10 +869,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -961,10 +922,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -1013,10 +971,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -1043,10 +998,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -1071,10 +1023,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -1098,10 +1047,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -1142,10 +1088,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -1179,10 +1122,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -1216,10 +1156,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},
@@ -1281,10 +1218,7 @@ export const templates: TTemplate[] = [
},
],
thankYouCard: thankYouCardDefault,
welcomeCard: {
enabled: false,
timeToFinish: false,
},
welcomeCard: welcomeCardDefault,
hiddenFields: {
enabled: false,
},

View File

@@ -73,8 +73,6 @@ export default function SummaryMetadata({
return ttc;
}, [responses]);
console.log(ttc);
const totalResponses = responses.length;
return (

View File

@@ -177,6 +177,28 @@ export default function EditWelcomeCard({
</div>
</div>
</div>
{localSurvey?.type === "link" && (
<div className="mt-6 flex items-center">
<div className="mr-2">
<Switch
id="showResponseCount"
name="showResponseCount"
checked={localSurvey?.welcomeCard?.showResponseCount}
onCheckedChange={() =>
updateSurvey({ showResponseCount: !localSurvey.welcomeCard.showResponseCount })
}
/>
</div>
<div className="flex-column">
<Label htmlFor="showResponseCount" className="">
Show Response Count
</Label>
<div className="text-sm text-gray-500 dark:text-gray-400">
Display number of responses for survey
</div>
</div>
</div>
)}
</form>
</Collapsible.CollapsibleContent>
</Collapsible.Root>

View File

@@ -3,11 +3,12 @@
import Modal from "@/app/(app)/environments/[environmentId]/surveys/components/Modal";
import TabOption from "@/app/(app)/environments/[environmentId]/surveys/components/TabOption";
import { SurveyInline } from "@formbricks/ui/Survey";
import type { TEnvironment } from "@formbricks/types/environment";
import type { TProduct } from "@formbricks/types/product";
import { TUploadFileConfig } from "@formbricks/types/storage";
import { TSurvey } from "@formbricks/types/surveys";
import { Button } from "@formbricks/ui/Button";
import { SurveyInline } from "@formbricks/ui/Survey";
import { ArrowPathRoundedSquareIcon } from "@heroicons/react/24/outline";
import {
ArrowsPointingInIcon,
@@ -17,7 +18,6 @@ import {
} from "@heroicons/react/24/solid";
import { Variants, motion } from "framer-motion";
import { useEffect, useRef, useState } from "react";
import { TUploadFileConfig } from "@formbricks/types/storage";
type TPreviewType = "modal" | "fullwidth" | "email";
@@ -226,6 +226,7 @@ export default function PreviewSurvey({
isBrandingEnabled={product.linkSurveyBranding}
onActiveQuestionChange={setActiveQuestionId}
onFileUpload={onFileUpload}
responseCount={42}
/>
</div>
</div>
@@ -297,6 +298,7 @@ export default function PreviewSurvey({
onActiveQuestionChange={setActiveQuestionId}
isRedirectDisabled={true}
onFileUpload={onFileUpload}
responseCount={42}
/>
</div>
</div>

View File

@@ -23,6 +23,7 @@ const welcomeCardDefault: TSurveyWelcomeCard = {
headline: "Welcome!",
html: "Thanks for providing your feedback - let's go!",
timeToFinish: true,
showResponseCount: false,
};
export const testTemplate: TTemplate = {
@@ -320,6 +321,7 @@ export const testTemplate: TTemplate = {
welcomeCard: {
enabled: false,
timeToFinish: false,
showResponseCount: false,
},
hiddenFields: {
enabled: false,

View File

@@ -46,7 +46,6 @@ export async function GET(
getActionClasses(environmentId),
getProductByEnvironmentId(environmentId),
]);
if (!product) {
throw new Error("Product not found");
}

View File

@@ -25,6 +25,7 @@ interface LinkSurveyProps {
singleUseId?: string;
singleUseResponse?: TResponse;
webAppUrl: string;
responseCount?: number;
}
export default function LinkSurvey({
@@ -36,6 +37,7 @@ export default function LinkSurvey({
singleUseId,
singleUseResponse,
webAppUrl,
responseCount,
}: LinkSurveyProps) {
const responseId = singleUseResponse?.id;
const searchParams = useSearchParams();
@@ -187,6 +189,7 @@ export default function LinkSurvey({
activeQuestionId={activeQuestionId}
autoFocus={autoFocus}
prefillResponseData={prefillResponseData}
responseCount={responseCount}
/>
</ContentWrapper>
</>

View File

@@ -15,6 +15,7 @@ import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { getEmailVerificationStatus } from "./lib/helpers";
import { ZId } from "@formbricks/types/environment";
import { getResponseCountBySurveyId } from "@formbricks/lib/response/service";
interface LinkSurveyPageProps {
params: {
@@ -166,7 +167,7 @@ export default async function LinkSurveyPage({ params, searchParams }: LinkSurve
}
const isSurveyPinProtected = Boolean(!!survey && survey.pin);
const responseCount = await getResponseCountBySurveyId(survey.id);
if (isSurveyPinProtected) {
return (
<PinScreen
@@ -192,6 +193,7 @@ export default async function LinkSurveyPage({ params, searchParams }: LinkSurve
singleUseId={isSingleUseSurvey ? singleUseId : undefined}
singleUseResponse={singleUseResponse ? singleUseResponse : undefined}
webAppUrl={WEBAPP_URL}
responseCount={survey.welcomeCard.showResponseCount ? responseCount : undefined}
/>
);
}

View File

@@ -1,4 +1,5 @@
import FormbricksBranding from "@/components/general/FormbricksBranding";
import ProgressBar from "@/components/general/ProgressBar";
import { AutoCloseWrapper } from "@/components/wrappers/AutoCloseWrapper";
import { evaluateCondition } from "@/lib/logicEvaluator";
import { cn } from "@/lib/utils";
@@ -8,7 +9,6 @@ import { useEffect, useRef, useState } from "preact/hooks";
import QuestionConditional from "./QuestionConditional";
import ThankYouCard from "./ThankYouCard";
import WelcomeCard from "./WelcomeCard";
import ProgressBar from "@/components/general/ProgressBar";
export function Survey({
survey,
@@ -22,6 +22,7 @@ export function Survey({
isRedirectDisabled = false,
prefillResponseData,
onFileUpload,
responseCount,
}: SurveyBaseProps) {
const [questionId, setQuestionId] = useState(
activeQuestionId || (survey.welcomeCard.enabled ? "start" : survey?.questions[0]?.id)
@@ -33,7 +34,6 @@ export function Survey({
const currentQuestion = survey.questions[currentQuestionIndex];
const contentRef = useRef<HTMLDivElement | null>(null);
const [ttc, setTtc] = useState<TResponseTtc>({});
useEffect(() => {
if (activeQuestionId === "hidden") return;
if (activeQuestionId === "start" && !survey.welcomeCard.enabled) {
@@ -131,9 +131,9 @@ export function Survey({
html={survey.welcomeCard.html}
fileUrl={survey.welcomeCard.fileUrl}
buttonLabel={survey.welcomeCard.buttonLabel}
timeToFinish={survey.welcomeCard.timeToFinish}
onSubmit={onSubmit}
survey={survey}
responseCount={responseCount}
/>
);
} else if (questionId === "end" && survey.thankYouCard.enabled) {

View File

@@ -12,6 +12,7 @@ export function SurveyInline({
prefillResponseData,
isRedirectDisabled = false,
onFileUpload,
responseCount,
}: SurveyBaseProps) {
return (
<div id="fbjs" className="formbricks-form h-full w-full">
@@ -26,6 +27,7 @@ export function SurveyInline({
prefillResponseData={prefillResponseData}
isRedirectDisabled={isRedirectDisabled}
onFileUpload={onFileUpload}
responseCount={responseCount}
/>
</div>
);

View File

@@ -18,6 +18,7 @@ export function SurveyModal({
onFinished = () => {},
onFileUpload,
isRedirectDisabled = false,
responseCount,
}: SurveyModalProps) {
const [isOpen, setIsOpen] = useState(true);
@@ -55,6 +56,7 @@ export function SurveyModal({
}}
onFileUpload={onFileUpload}
isRedirectDisabled={isRedirectDisabled}
responseCount={responseCount}
/>
</Modal>
</div>

View File

@@ -10,9 +10,9 @@ interface WelcomeCardProps {
html?: string;
fileUrl?: string;
buttonLabel?: string;
timeToFinish?: boolean;
onSubmit: (data: TResponseData, ttc: TResponseTtc) => void;
survey: TSurvey;
responseCount?: number;
}
const TimerIcon = () => {
@@ -32,14 +32,34 @@ const TimerIcon = () => {
);
};
const UsersIcon = () => {
return (
<div className="mr-1">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"
/>
</svg>
</div>
);
};
export default function WelcomeCard({
headline,
html,
fileUrl,
buttonLabel,
timeToFinish,
onSubmit,
survey,
responseCount,
}: WelcomeCardProps) {
const calculateTimeToComplete = () => {
let idx = calculateElementIdx(survey, 0);
@@ -69,6 +89,9 @@ export default function WelcomeCard({
return `${minutes} minutes`;
};
const timeToFinish = survey.welcomeCard.timeToFinish;
const showResponseCount = survey.welcomeCard.showResponseCount;
return (
<div>
{fileUrl && (
@@ -93,12 +116,30 @@ export default function WelcomeCard({
<div className="text-subheading flex items-center text-xs">Press Enter </div>
</div>
</div>
{timeToFinish && (
{timeToFinish && !showResponseCount ? (
<div className="item-center mt-4 flex text-slate-500">
<TimerIcon />
<p className="text-xs">Takes {calculateTimeToComplete()}</p>
<p className="pt-1 text-xs">
<span> Takes {calculateTimeToComplete()} </span>
</p>
</div>
)}
) : showResponseCount && !timeToFinish && responseCount && responseCount > 3 ? (
<div className="item-center mt-4 flex text-slate-500">
<UsersIcon />
<p className="pt-1 text-xs">
<span>{`${responseCount} people responded`}</span>
</p>
</div>
) : timeToFinish && showResponseCount ? (
<div className="item-center mt-4 flex text-slate-500">
<TimerIcon />
<p className="pt-1 text-xs">
<span> Takes {calculateTimeToComplete()} </span>
<span>{responseCount && responseCount > 3 ? `${responseCount} people responded` : ""}</span>
</p>
</div>
) : null}
</div>
);
}

View File

@@ -15,6 +15,7 @@ export interface SurveyBaseProps {
isRedirectDisabled?: boolean;
prefillResponseData?: TResponseData;
onFileUpload: (file: File, config?: TUploadFileConfig) => Promise<string>;
responseCount?: number;
}
export interface SurveyInlineProps extends SurveyBaseProps {

View File

@@ -1,8 +1,8 @@
import z from "zod";
import { ZPerson, ZPersonAttributes, ZPersonClient } from "./people";
import { ZSurvey } from "./surveys";
import { ZActionClass } from "./actionClasses";
import { ZPerson, ZPersonAttributes, ZPersonClient } from "./people";
import { ZProduct } from "./product";
import { ZSurvey } from "./surveys";
const ZSurveyWithTriggers = ZSurvey.extend({
triggers: z.array(ZActionClass).or(z.array(z.string())),

View File

@@ -27,6 +27,7 @@ export const ZSurveyWelcomeCard = z.object({
fileUrl: z.string().optional(),
buttonLabel: z.string().optional(),
timeToFinish: z.boolean().default(true),
showResponseCount: z.boolean().default(false),
});
export const ZSurveyHiddenFields = z.object({

View File

@@ -20,6 +20,7 @@ interface SurveyProps {
autoFocus?: boolean;
prefillResponseData?: TResponseData;
isRedirectDisabled?: boolean;
responseCount?: number;
}
interface SurveyModalProps extends SurveyProps {
@@ -42,6 +43,7 @@ export const SurveyInline = ({
prefillResponseData,
isRedirectDisabled,
onFileUpload,
responseCount,
}: SurveyProps) => {
const containerId = useMemo(() => createContainerId(), []);
useEffect(() => {
@@ -59,6 +61,7 @@ export const SurveyInline = ({
prefillResponseData,
isRedirectDisabled,
onFileUpload,
responseCount,
});
}, [
activeQuestionId,
@@ -74,6 +77,7 @@ export const SurveyInline = ({
prefillResponseData,
isRedirectDisabled,
onFileUpload,
responseCount,
]);
return <div id={containerId} className="h-full w-full" />;
};
@@ -94,6 +98,7 @@ export const SurveyModal = ({
autoFocus,
isRedirectDisabled,
onFileUpload,
responseCount,
}: SurveyModalProps) => {
useEffect(() => {
renderSurveyModal({
@@ -112,6 +117,7 @@ export const SurveyModal = ({
autoFocus,
isRedirectDisabled,
onFileUpload,
responseCount,
});
}, [
activeQuestionId,
@@ -129,6 +135,7 @@ export const SurveyModal = ({
autoFocus,
isRedirectDisabled,
onFileUpload,
responseCount,
]);
return <div id="formbricks-survey"></div>;
};