mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-22 02:55:04 -05:00
update pmf survey dashboard with quick optimizations
This commit is contained in:
@@ -157,16 +157,16 @@ export default function NewFormModal({ open, setOpen, organisationId }: FormOnbo
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "userSegmentPage",
|
||||
id: "rolePage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "userSegment",
|
||||
id: "role",
|
||||
type: "radio",
|
||||
name: "userSegment",
|
||||
label: "What is your job title?",
|
||||
name: "role",
|
||||
label: "What is your role?",
|
||||
options: [
|
||||
{ label: "Founder", value: "founder" },
|
||||
{ label: "Executive", value: "executive" },
|
||||
@@ -189,12 +189,12 @@ export default function NewFormModal({ open, setOpen, organisationId }: FormOnbo
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "selfSegmentationPage",
|
||||
id: "benefitingUsers",
|
||||
elements: [
|
||||
{
|
||||
id: "selfSegmentation",
|
||||
id: "benefitingUsers",
|
||||
type: "text",
|
||||
name: "selfSegmentation",
|
||||
name: "benefitingUsers",
|
||||
label: "What type of people would benefit most from using our service?",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import EmptyPageFiller from "@/components/EmptyPageFiller";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import { useForm } from "@/lib/forms";
|
||||
import { getOptionLabelMap, useSubmissions } from "@/lib/submissions";
|
||||
import { Pie } from "@formbricks/charts";
|
||||
import { NotDisappointedIcon, SomewhatDisappointedIcon, VeryDisappointedIcon } from "@formbricks/ui";
|
||||
import { InboxIcon } from "@heroicons/react/24/outline";
|
||||
import { useRouter } from "next/router";
|
||||
import { useMemo, useState } from "react";
|
||||
import FilterNavigation from "../shared/FilterNavigation";
|
||||
import { SubmissionCounter } from "../shared/SubmissionCounter";
|
||||
|
||||
export default function OverviewResults() {
|
||||
const router = useRouter();
|
||||
@@ -50,7 +52,7 @@ export default function OverviewResults() {
|
||||
},
|
||||
{
|
||||
label: "What type of people would benefit most from using our service?",
|
||||
name: "selfSegmentation",
|
||||
name: "benefitingUsers",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -59,60 +61,84 @@ export default function OverviewResults() {
|
||||
<div>
|
||||
<section aria-labelledby="filters" className="max-w-8xl mx-auto py-8 pt-6 pb-24 ">
|
||||
<div className="grid grid-cols-1 gap-x-16 gap-y-10 lg:grid-cols-4">
|
||||
<FilterNavigation submissions={submissions} setFilteredSubmissions={setFilteredSubmissions} />
|
||||
<div>
|
||||
<SubmissionCounter
|
||||
numFilteredSubmissions={filteredSubmissions.length}
|
||||
numTotalSubmissions={submissions.length}
|
||||
/>
|
||||
<FilterNavigation submissions={submissions} setFilteredSubmissions={setFilteredSubmissions} />
|
||||
</div>
|
||||
|
||||
{/* Submission grid */}
|
||||
|
||||
<div className="max-w-7xl lg:col-span-3">
|
||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-2">
|
||||
<div className="flex flex-col items-center justify-center rounded-lg bg-white p-2">
|
||||
<h3 className="text-sm font-medium text-slate-800">Overall</h3>
|
||||
<h3 className="text-xs font-light text-slate-800">({submissions.length} submissions)</h3>
|
||||
<Pie submissions={submissions} schema={form.schema} fieldName={"disappointment"} />
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center rounded-lg bg-white p-2">
|
||||
<h3 className="text-sm font-medium text-slate-800">Selected Segment</h3>
|
||||
<h3 className="text-xs font-light text-slate-800">
|
||||
({filteredSubmissions.length} submissions)
|
||||
</h3>
|
||||
<Pie submissions={filteredSubmissions} schema={form.schema} fieldName={"disappointment"} />
|
||||
</div>
|
||||
</div>
|
||||
{questions.map((question) => (
|
||||
<div key={question.name} className="my-4 rounded-lg bg-white">
|
||||
<div className="rounded-t-lg bg-slate-100 p-4 text-lg font-bold text-slate-800">
|
||||
{question.label}
|
||||
{submissions.length === 0 ? (
|
||||
<EmptyPageFiller
|
||||
alertText="You haven't received any submissions yet."
|
||||
hintText="Embed the PMF survey on your website to start gathering insights."
|
||||
borderStyles="border-4 border-dotted border-red">
|
||||
<InboxIcon className="stroke-thin mx-auto h-24 w-24 text-slate-300" />
|
||||
</EmptyPageFiller>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-2">
|
||||
<div className="flex flex-col items-center justify-center rounded-lg bg-white p-2">
|
||||
<h3 className="text-sm font-medium text-slate-800">Disappointment Level</h3>
|
||||
<Pie
|
||||
submissions={filteredSubmissions}
|
||||
schema={form.schema}
|
||||
fieldName={"disappointment"}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center rounded-lg bg-white p-2">
|
||||
<h3 className="text-sm font-medium text-slate-800">Role</h3>
|
||||
<Pie submissions={filteredSubmissions} schema={form.schema} fieldName={"role"} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-5 gap-2 border-t border-slate-200 bg-slate-100 px-4 py-2 text-sm font-semibold text-slate-500">
|
||||
<div className="col-span-3">Response</div>
|
||||
<div>Feeling</div>
|
||||
<div>Segment</div>
|
||||
</div>
|
||||
{filteredSubmissions
|
||||
.filter((s) => question.name in s.data)
|
||||
.map((submission) => (
|
||||
<div
|
||||
key={submission.id}
|
||||
className="grid grid-cols-5 gap-2 border-t border-slate-100 px-4 pt-2 pb-4 text-sm">
|
||||
<div className="col-span-3">{submission.data[question.name]}</div>
|
||||
<div>
|
||||
{submission.data.disappointment === "veryDisappointed" ? (
|
||||
<VeryDisappointedIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||
) : submission.data.disappointment === "notDisappointed" ? (
|
||||
<NotDisappointedIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||
) : submission.data.disappointment === "somewhatDisappointed" ? (
|
||||
<SomewhatDisappointedIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<div className="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-0.5 text-xs text-slate-600">
|
||||
{labelMap[submission.data.userSegment]}
|
||||
</div>
|
||||
</div>
|
||||
{questions.map((question) => (
|
||||
<div key={question.name} className="my-4 rounded-lg bg-white">
|
||||
<div className="rounded-t-lg bg-slate-100 p-4 text-lg font-bold text-slate-800">
|
||||
{question.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
<div className="grid grid-cols-5 gap-2 border-t border-slate-200 bg-slate-100 px-4 py-2 text-sm font-semibold text-slate-500">
|
||||
<div className="col-span-3">Response</div>
|
||||
<div>Disappointment Level</div>
|
||||
<div>Job</div>
|
||||
</div>
|
||||
{console.log(JSON.stringify(question, null, 2))}
|
||||
{filteredSubmissions
|
||||
.filter((s) => question.name in s.data)
|
||||
.map((submission) => (
|
||||
<div
|
||||
key={submission.id}
|
||||
className="grid grid-cols-5 gap-2 border-t border-slate-100 px-4 pt-2 pb-4 text-sm">
|
||||
<div className="col-span-3">{submission.data[question.name]}</div>
|
||||
<div>
|
||||
{submission.data.disappointment === "veryDisappointed" ? (
|
||||
<span className="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800">
|
||||
very disappointed
|
||||
</span>
|
||||
) : submission.data.disappointment === "notDisappointed" ? (
|
||||
<span className="inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800">
|
||||
not disappointed
|
||||
</span>
|
||||
) : submission.data.disappointment === "somewhatDisappointed" ? (
|
||||
<span className="inline-flex items-center rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800">
|
||||
somewhat disappointed
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<div className="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-0.5 text-xs text-slate-600">
|
||||
{labelMap[submission.data.role] || <NotProvided />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -120,3 +146,7 @@ export default function OverviewResults() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NotProvided() {
|
||||
return <span className="text-slate-500">(not provided)</span>;
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import SetupInstructions from "./SetupInstructions";
|
||||
import SuperhumanApproach from "./SuperhumanApproach";
|
||||
|
||||
const tabs = [
|
||||
{ name: "Results", icon: RectangleStackIcon },
|
||||
{ name: "Overview", icon: ChartPieIcon },
|
||||
{ name: "Results", icon: RectangleStackIcon },
|
||||
{ name: "Superhuman Approach", icon: RocketLaunchIcon },
|
||||
{ name: "Data Pipelines", icon: ShareIcon },
|
||||
{ name: "Setup Instructions", icon: InformationCircleIcon },
|
||||
@@ -28,7 +28,7 @@ const tabs = [
|
||||
|
||||
export default function PMFPage() {
|
||||
const router = useRouter();
|
||||
const [currentTab, setCurrentTab] = useState("Results");
|
||||
const [currentTab, setCurrentTab] = useState("Overview");
|
||||
const { form, isLoadingForm, isErrorForm } = useForm(
|
||||
router.query.formId?.toString(),
|
||||
router.query.organisationId?.toString()
|
||||
|
||||
@@ -7,6 +7,7 @@ import { InboxIcon } from "@heroicons/react/24/outline";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import FilterNavigation from "../shared/FilterNavigation";
|
||||
import { SubmissionCounter } from "../shared/SubmissionCounter";
|
||||
import PMFTimeline from "./PMFTimeline";
|
||||
|
||||
export default function PMFResults() {
|
||||
@@ -34,7 +35,13 @@ export default function PMFResults() {
|
||||
<div>
|
||||
<section aria-labelledby="filters" className="max-w-8xl mx-auto py-8 pt-6 pb-24">
|
||||
<div className="grid grid-cols-1 gap-x-16 gap-y-10 lg:grid-cols-4">
|
||||
<FilterNavigation submissions={submissions} setFilteredSubmissions={setFilteredSubmissions} />
|
||||
<div>
|
||||
<SubmissionCounter
|
||||
numFilteredSubmissions={filteredSubmissions.length}
|
||||
numTotalSubmissions={submissions.length}
|
||||
/>
|
||||
<FilterNavigation submissions={submissions} setFilteredSubmissions={setFilteredSubmissions} />
|
||||
</div>
|
||||
|
||||
{/* Submission grid */}
|
||||
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import EmptyPageFiller from "@/components/EmptyPageFiller";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import PMFThumb2 from "@/images/pmfthumb-2.webp";
|
||||
import PMFThumb from "@/images/pmfthumb.webp";
|
||||
import { useForm } from "@/lib/forms";
|
||||
import { getOptionLabelMap, useSubmissions } from "@/lib/submissions";
|
||||
import { Pie } from "@formbricks/charts";
|
||||
import { NotDisappointedIcon, SomewhatDisappointedIcon, VeryDisappointedIcon } from "@formbricks/ui";
|
||||
import { InboxIcon } from "@heroicons/react/24/outline";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useMemo, useState } from "react";
|
||||
import FilterNavigation from "../shared/FilterNavigation";
|
||||
import { SubmissionCounter } from "../shared/SubmissionCounter";
|
||||
|
||||
const limitFields = ["userSegment"];
|
||||
const limitFields = ["role"];
|
||||
|
||||
export default function SegmentResults() {
|
||||
const router = useRouter();
|
||||
@@ -54,16 +56,22 @@ export default function SegmentResults() {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<section aria-labelledby="filters" className="max-w-8xl mx-auto py-8">
|
||||
<section aria-labelledby="filters" className="max-w-8xl mx-auto py-8 pt-6">
|
||||
<div className="grid grid-cols-1 gap-x-16 gap-y-10 lg:grid-cols-4">
|
||||
<div>
|
||||
{/* Segments */}
|
||||
<div>
|
||||
<SubmissionCounter
|
||||
numFilteredSubmissions={filteredSubmissions.length}
|
||||
numTotalSubmissions={submissions.length}
|
||||
/>
|
||||
|
||||
<FilterNavigation
|
||||
submissions={submissions}
|
||||
setFilteredSubmissions={setFilteredSubmissions}
|
||||
limitFields={limitFields}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FilterNavigation
|
||||
submissions={submissions}
|
||||
setFilteredSubmissions={setFilteredSubmissions}
|
||||
limitFields={limitFields}
|
||||
/>
|
||||
<div className="mb-2 flex py-2 text-sm font-bold">
|
||||
<h4 className="text-slate-600">Tutorials</h4>
|
||||
</div>
|
||||
@@ -96,140 +104,174 @@ export default function SegmentResults() {
|
||||
{/* Double down on what they love*/}
|
||||
|
||||
<div className=" lg:col-span-3">
|
||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-2">
|
||||
<div className="flex flex-col items-center justify-center rounded-lg bg-white p-2">
|
||||
<h3 className="text-sm font-medium text-slate-800">Overall</h3>
|
||||
<h3 className="text-xs font-light text-slate-800">({submissions.length} submissions)</h3>
|
||||
<Pie submissions={submissions} schema={form.schema} fieldName={"disappointment"} />
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center rounded-lg bg-white p-2">
|
||||
<h3 className="text-sm font-medium text-slate-800">Selected Segment</h3>
|
||||
<h3 className="text-xs font-light text-slate-800">
|
||||
({filteredSubmissions.length} submissions)
|
||||
</h3>
|
||||
<Pie submissions={filteredSubmissions} schema={form.schema} fieldName={"disappointment"} />
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="mt-10 mb-4 text-2xl font-bold text-slate-500">Double down on what they love</h2>
|
||||
<div className="rounded-md bg-teal-50 p-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<span className="inline-flex items-center rounded-full bg-teal-100 px-2.5 py-0.5 text-xs font-medium text-teal-800">
|
||||
How it works
|
||||
</span>
|
||||
{submissions.length === 0 ? (
|
||||
<EmptyPageFiller
|
||||
alertText="You haven't received any submissions yet."
|
||||
hintText="Embed the PMF survey on your website to start gathering insights."
|
||||
borderStyles="border-4 border-dotted border-red">
|
||||
<InboxIcon className="stroke-thin mx-auto h-24 w-24 text-slate-300" />
|
||||
</EmptyPageFiller>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-2">
|
||||
<div className="flex flex-col items-center justify-center rounded-lg bg-white p-2">
|
||||
<h3 className="text-sm font-medium text-slate-800">All</h3>
|
||||
<h3 className="text-xs font-light text-slate-800">
|
||||
({submissions.length} submissions)
|
||||
</h3>
|
||||
<Pie submissions={submissions} schema={form.schema} fieldName={"disappointment"} />
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center rounded-lg bg-white p-2">
|
||||
<h3 className="text-sm font-medium text-slate-800">Selected Role</h3>
|
||||
<h3 className="text-xs font-light text-slate-800">
|
||||
({filteredSubmissions.length} submissions)
|
||||
</h3>
|
||||
<Pie
|
||||
submissions={filteredSubmissions}
|
||||
schema={form.schema}
|
||||
fieldName={"disappointment"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p className="text-sm text-teal-700">
|
||||
To protect the PMF score from eroding among already very satisfied users, you deepen the
|
||||
value they experience. To do so, you build what they request in the following answers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-4 rounded-lg bg-white">
|
||||
<div className="rounded-t-lg bg-slate-100 p-4 text-lg font-bold text-slate-700">
|
||||
What is the main benefit you receive from our service?
|
||||
</div>
|
||||
<div className="grid grid-cols-5 gap-2 bg-slate-100 px-4 pb-2 text-sm font-semibold text-slate-500">
|
||||
<div className="col-span-3">Response</div>
|
||||
<div>Disappointment</div>
|
||||
<div>Segment</div>
|
||||
</div>
|
||||
{lovers.length === 0 ? (
|
||||
<div className="p-4">
|
||||
<h3 className="text-center text-sm font-bold text-slate-400">
|
||||
You don’t have any submissions that fit this filter
|
||||
</h3>
|
||||
<p className="mt-1 text-center text-xs font-light text-slate-400">
|
||||
Change your filters or come back when you have more submissions.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{lovers.map((submission) => (
|
||||
<div className="grid grid-cols-5 gap-2 px-4 pt-2 pb-4 text-sm">
|
||||
<div className="col-span-3 text-slate-800">
|
||||
{submission.data.mainBenefit || <NotProvided />}
|
||||
</div>
|
||||
<div>
|
||||
{submission.data.disappointment === "veryDisappointed" ? (
|
||||
<VeryDisappointedIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||
) : submission.data.disappointment === "notDisappointed" ? (
|
||||
<NotDisappointedIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||
) : submission.data.disappointment === "somewhatDisappointed" ? (
|
||||
<SomewhatDisappointedIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<div className="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-0.5 text-xs text-slate-600">
|
||||
{labelMap[submission.data.userSegment] || <NotProvided />}
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="mt-10 mb-4 text-2xl font-bold text-slate-500">
|
||||
Double down on what they love
|
||||
</h2>
|
||||
<div className="rounded-md bg-teal-50 p-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<span className="inline-flex items-center rounded-full bg-teal-100 px-2.5 py-0.5 text-xs font-medium text-teal-800">
|
||||
How it works
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<h2 className="mt-10 mb-4 text-2xl font-bold text-slate-500">Fix what’s holding them back</h2>
|
||||
<div className="rounded-md bg-teal-50 p-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<span className="inline-flex items-center rounded-full bg-teal-100 px-2.5 py-0.5 text-xs font-medium text-teal-800">
|
||||
How it works
|
||||
</span>
|
||||
</div>
|
||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p className="text-sm text-teal-700">
|
||||
To make more users “very disappointed” when your product were to go away, you build
|
||||
what’s holding the “somewhat disappointed” users back.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-4 rounded-lg bg-white">
|
||||
<div className="rounded-t-lg bg-slate-100 p-4 text-lg font-bold text-slate-700">
|
||||
How can we improve our service for you?
|
||||
</div>
|
||||
<div className="grid grid-cols-5 gap-2 bg-slate-100 px-4 pb-2 text-sm font-semibold text-slate-500">
|
||||
<div className="col-span-3">Response</div>
|
||||
<div>Disappointment</div>
|
||||
<div>Segment</div>
|
||||
</div>
|
||||
{improvers.length === 0 ? (
|
||||
<div className="p-4">
|
||||
<h3 className="text-center text-sm font-bold text-slate-400">
|
||||
You don’t have any submissions that fit this filter
|
||||
</h3>
|
||||
<p className="mt-1 text-center text-xs font-light text-slate-400">
|
||||
Change your filters or come back when you have more submissions.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{improvers.map((submission) => (
|
||||
<div className="grid grid-cols-5 gap-2 px-4 pt-2 pb-4 text-sm">
|
||||
<div className="col-span-3 text-slate-800">
|
||||
{submission.data.improvement || <NotProvided />}
|
||||
</div>
|
||||
<div>
|
||||
{submission.data.disappointment === "veryDisappointed" ? (
|
||||
<VeryDisappointedIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||
) : submission.data.disappointment === "notDisappointed" ? (
|
||||
<NotDisappointedIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||
) : submission.data.disappointment === "somewhatDisappointed" ? (
|
||||
<SomewhatDisappointedIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<div className="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-0.5 text-xs text-slate-600">
|
||||
{labelMap[submission.data.userSegment] || <NotProvided />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p className="text-sm text-teal-700">
|
||||
To protect the PMF score from eroding among already very satisfied users, you deepen
|
||||
the value they experience. To do so, you build what they request in the following
|
||||
answers.
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-4 rounded-lg bg-white">
|
||||
<div className="rounded-t-lg bg-slate-100 p-4 text-lg font-bold text-slate-700">
|
||||
What is the main benefit you receive from our service?
|
||||
</div>
|
||||
<div className="grid grid-cols-5 gap-2 bg-slate-100 px-4 pb-2 text-sm font-semibold text-slate-500">
|
||||
<div className="col-span-3">Response</div>
|
||||
<div>Disappointment Level</div>
|
||||
<div>Role</div>
|
||||
</div>
|
||||
{lovers.length === 0 ? (
|
||||
<div className="p-4">
|
||||
<h3 className="text-center text-sm font-bold text-slate-400">
|
||||
You don’t have any submissions that fit this filter
|
||||
</h3>
|
||||
<p className="mt-1 text-center text-xs font-light text-slate-400">
|
||||
Change your filters or come back when you have more submissions.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{lovers.map((submission) => (
|
||||
<div className="grid grid-cols-5 gap-2 px-4 pt-2 pb-4 text-sm">
|
||||
<div className="col-span-3 text-slate-800">
|
||||
{submission.data.mainBenefit || <NotProvided />}
|
||||
</div>
|
||||
<div>
|
||||
{submission.data.disappointment === "veryDisappointed" ? (
|
||||
<span className="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800">
|
||||
very disappointed
|
||||
</span>
|
||||
) : submission.data.disappointment === "notDisappointed" ? (
|
||||
<span className="inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800">
|
||||
not disappointed
|
||||
</span>
|
||||
) : submission.data.disappointment === "somewhatDisappointed" ? (
|
||||
<span className="inline-flex items-center rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800">
|
||||
somewhat disappointed
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<div className="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-0.5 text-xs text-slate-600">
|
||||
{labelMap[submission.data.role] || <NotProvided />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<h2 className="mt-10 mb-4 text-2xl font-bold text-slate-500">
|
||||
Fix what’s holding them back
|
||||
</h2>
|
||||
<div className="rounded-md bg-teal-50 p-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<span className="inline-flex items-center rounded-full bg-teal-100 px-2.5 py-0.5 text-xs font-medium text-teal-800">
|
||||
How it works
|
||||
</span>
|
||||
</div>
|
||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p className="text-sm text-teal-700">
|
||||
To make more users “very disappointed” when your product were to go away, you build
|
||||
what’s holding the “somewhat disappointed” users back.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-4 rounded-lg bg-white">
|
||||
<div className="rounded-t-lg bg-slate-100 p-4 text-lg font-bold text-slate-700">
|
||||
How can we improve our service for you?
|
||||
</div>
|
||||
<div className="grid grid-cols-5 gap-2 bg-slate-100 px-4 pb-2 text-sm font-semibold text-slate-500">
|
||||
<div className="col-span-3">Response</div>
|
||||
<div>Disappointment Level</div>
|
||||
<div>Role</div>
|
||||
</div>
|
||||
{improvers.length === 0 ? (
|
||||
<div className="p-4">
|
||||
<h3 className="text-center text-sm font-bold text-slate-400">
|
||||
You don’t have any submissions that fit this filter
|
||||
</h3>
|
||||
<p className="mt-1 text-center text-xs font-light text-slate-400">
|
||||
Change your filters or come back when you have more submissions.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{improvers.map((submission) => (
|
||||
<div className="grid grid-cols-5 gap-2 px-4 pt-2 pb-4 text-sm">
|
||||
<div className="col-span-3 text-slate-800">
|
||||
{submission.data.improvement || <NotProvided />}
|
||||
</div>
|
||||
<div>
|
||||
{submission.data.disappointment === "veryDisappointed" ? (
|
||||
<span className="inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800">
|
||||
very disappointed
|
||||
</span>
|
||||
) : submission.data.disappointment === "notDisappointed" ? (
|
||||
<span className="inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800">
|
||||
not disappointed
|
||||
</span>
|
||||
) : submission.data.disappointment === "somewhatDisappointed" ? (
|
||||
<span className="inline-flex items-center rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800">
|
||||
somewhat disappointed
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<div className="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-0.5 text-xs text-slate-600">
|
||||
{labelMap[submission.data.role] || <NotProvided />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -102,21 +102,39 @@ export default function FilterNavigation({
|
||||
}
|
||||
}, [filters, form, submissions, setFilteredSubmissions]);
|
||||
|
||||
const chooseOptionFilter = (filterName, optionValue) => {
|
||||
const chooseOptionFilter = (filterName, optionValue, optionActive) => {
|
||||
const newFilters = [...filters];
|
||||
const filter = newFilters.find((filter) => filter.name === filterName);
|
||||
|
||||
if (filter) {
|
||||
// reset all previous filter options
|
||||
for (const option of filter.options) {
|
||||
if (option.value === optionValue) {
|
||||
option.active = true;
|
||||
} else {
|
||||
// activate option & deactivate all others
|
||||
if (!optionActive) {
|
||||
// reset all previous filter options
|
||||
for (const option of filter.options) {
|
||||
if (option.value === optionValue) {
|
||||
option.active = true;
|
||||
} else {
|
||||
option.active = false;
|
||||
}
|
||||
// reset all pins when all is selected
|
||||
if (optionValue === "all") {
|
||||
option.pinned = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// deactivate option if already active
|
||||
const option = filter.options.find((option) => option.value === optionValue);
|
||||
if (option) {
|
||||
option.active = false;
|
||||
}
|
||||
// reset all pins when all is selected
|
||||
if (optionValue === "all") {
|
||||
option.pinned = false;
|
||||
// check if something is pinned
|
||||
const pinnedOption = filter.options.find((option) => option.pinned);
|
||||
if (!pinnedOption) {
|
||||
// activate all option if noting is pinned
|
||||
const option = filter.options.find((option) => option.value === "all");
|
||||
if (option) {
|
||||
option.active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,7 +209,7 @@ export default function FilterNavigation({
|
||||
key={option.value}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
chooseOptionFilter(filter.name, option.value);
|
||||
chooseOptionFilter(filter.name, option.value, option.active);
|
||||
}}
|
||||
className={clsx(
|
||||
option.active || option.pinned
|
||||
@@ -205,7 +223,10 @@ export default function FilterNavigation({
|
||||
{!["all", "inbox", "archived"].includes(option.value) && (option.active || option.pinned) && (
|
||||
<button
|
||||
className="ml-auto"
|
||||
onClick={() => pinOptionFilter(filter.name, option.value, !option.pinned)}>
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
pinOptionFilter(filter.name, option.value, !option.pinned);
|
||||
}}>
|
||||
{option.pinned ? (
|
||||
<BsPinFill className="h-4 w-4 text-gray-400" />
|
||||
) : (
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export function SubmissionCounter({ numFilteredSubmissions, numTotalSubmissions }) {
|
||||
return (
|
||||
<div className="mb-4 rounded bg-white p-3">
|
||||
<div className="inline-block text-base font-bold text-slate-600">
|
||||
{numFilteredSubmissions} responses
|
||||
</div>
|
||||
{numFilteredSubmissions !== numTotalSubmissions && (
|
||||
<div className="ml-1 inline-block text-sm font-medium text-slate-400">
|
||||
(out of {numTotalSubmissions})
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -19,9 +19,9 @@ const output = {
|
||||
data: {
|
||||
improvement: "Make it possible to add a note to a transaction",
|
||||
mainBenefit: "The best is that I can get a quick overview of all my transactions",
|
||||
userSegment: "founder",
|
||||
role: "founder",
|
||||
disappointment: "veryDisappointed",
|
||||
selfSegmentation: "other founders",
|
||||
benefitingUsers: "other founders",
|
||||
},
|
||||
meta: {
|
||||
userAgent:
|
||||
@@ -76,14 +76,14 @@ const output = {
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "userSegmentPage",
|
||||
id: "rolePage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "userSegment",
|
||||
name: "userSegment",
|
||||
id: "role",
|
||||
name: "role",
|
||||
type: "radio",
|
||||
label: "What is your job title?",
|
||||
options: [
|
||||
@@ -123,11 +123,11 @@ const output = {
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "selfSegmentationPage",
|
||||
id: "benefitingUsersPage",
|
||||
elements: [
|
||||
{
|
||||
id: "selfSegmentation",
|
||||
name: "selfSegmentation",
|
||||
id: "benefitingUsers",
|
||||
name: "benefitingUsers",
|
||||
type: "text",
|
||||
label: "What type of people would benefit most from using our service?",
|
||||
},
|
||||
|
||||
@@ -47,14 +47,14 @@ const output = {
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "userSegmentPage",
|
||||
id: "rolePage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "userSegment",
|
||||
name: "userSegment",
|
||||
id: "role",
|
||||
name: "role",
|
||||
type: "radio",
|
||||
label: "What is your job title?",
|
||||
options: [
|
||||
@@ -94,11 +94,11 @@ const output = {
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "selfSegmentationPage",
|
||||
id: "benefitingUsersPage",
|
||||
elements: [
|
||||
{
|
||||
id: "selfSegmentation",
|
||||
name: "selfSegmentation",
|
||||
id: "benefitingUsers",
|
||||
name: "benefitingUsers",
|
||||
type: "text",
|
||||
label: "What type of people would benefit most from using our service?",
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ const mainBenefits = [
|
||||
"The notifications when I receive money are great",
|
||||
];
|
||||
|
||||
const userSegments = ["founder", "executive", "productManager", "productOwner", "softwareEngineer"];
|
||||
const roles = ["founder", "executive", "productManager", "productOwner", "softwareEngineer"];
|
||||
|
||||
const disappointments = [
|
||||
"veryDisappointed",
|
||||
@@ -31,7 +31,7 @@ const disappointments = [
|
||||
"notDisappointed",
|
||||
];
|
||||
|
||||
const selfSegmentations = [
|
||||
const benefitingUserss = [
|
||||
"Founders",
|
||||
"Executives",
|
||||
"Product Managers",
|
||||
@@ -65,9 +65,9 @@ export const getPmfSubmissions = () => {
|
||||
data: {
|
||||
improvement: getRandomItem(improvements),
|
||||
mainBenefit: getRandomItem(mainBenefits),
|
||||
userSegment: getRandomItem(userSegments),
|
||||
role: getRandomItem(roles),
|
||||
disappointment: getRandomItem(disappointments),
|
||||
selfSegmentation: getRandomItem(selfSegmentations),
|
||||
benefitingUsers: getRandomItem(benefitingUserss),
|
||||
},
|
||||
meta: {
|
||||
userAgent:
|
||||
|
||||
@@ -71,7 +71,6 @@ function FbPie({ color, submissions, schema, fieldName }: Props) {
|
||||
return (
|
||||
<>
|
||||
<PieChart width={500} height={250}>
|
||||
{/* <Pie dataKey="value" fill={color || "#00C4B8"} /> */}
|
||||
<Pie
|
||||
data={data}
|
||||
dataKey="value"
|
||||
@@ -84,7 +83,7 @@ function FbPie({ color, submissions, schema, fieldName }: Props) {
|
||||
const x = cx + radius * Math.cos(-midAngle * RADIAN);
|
||||
const y = cy + radius * Math.sin(-midAngle * RADIAN);
|
||||
|
||||
return (
|
||||
return value === 0 ? null : (
|
||||
<text
|
||||
x={x}
|
||||
y={y}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@formbricks/pmf",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"source": "src/index.ts",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -58,7 +58,7 @@
|
||||
<div class="formbricks-radio-options">
|
||||
<div class="formbricks-radio-option">
|
||||
<input
|
||||
data-element-name="userSegment"
|
||||
data-element-name="role"
|
||||
data-element-value="founder"
|
||||
id="formbricksPmf-2-0"
|
||||
name="formbricksPmf-2-0"
|
||||
@@ -68,7 +68,7 @@
|
||||
</div>
|
||||
<div class="formbricks-radio-option">
|
||||
<input
|
||||
data-element-name="userSegment"
|
||||
data-element-name="role"
|
||||
data-element-value="executive"
|
||||
id="formbricksPmf-2-1"
|
||||
name="formbricksPmf-2-1"
|
||||
@@ -78,7 +78,7 @@
|
||||
</div>
|
||||
<div class="formbricks-radio-option">
|
||||
<input
|
||||
data-element-name="userSegment"
|
||||
data-element-name="role"
|
||||
data-element-value="productManager"
|
||||
id="formbricksPmf-2-2"
|
||||
name="formbricksPmf-2-2"
|
||||
@@ -88,7 +88,7 @@
|
||||
</div>
|
||||
<div class="formbricks-radio-option">
|
||||
<input
|
||||
data-element-name="userSegment"
|
||||
data-element-name="role"
|
||||
data-element-value="productOwner"
|
||||
id="formbricksPmf-2-3"
|
||||
name="formbricksPmf-2-3"
|
||||
@@ -98,7 +98,7 @@
|
||||
</div>
|
||||
<div class="formbricks-radio-option">
|
||||
<input
|
||||
data-element-name="userSegment"
|
||||
data-element-name="role"
|
||||
data-element-value="softwareEngineer"
|
||||
id="formbricksPmf-2-4"
|
||||
name="formbricksPmf-2-4"
|
||||
@@ -122,8 +122,8 @@
|
||||
|
||||
<div class="formbricks-element formbricks-hidden" id="formbricks-question-4">
|
||||
<p class="formbricks-element-label">What type of people would benefit most from using our service?</p>
|
||||
<form class="formbricks-form" data-element-name="selfSegmentation">
|
||||
<textarea rows="4" name="selfSegmentation" class="formbricks-textarea" required autofocus></textarea>
|
||||
<form class="formbricks-form" data-element-name="benefitingUsers">
|
||||
<textarea rows="4" name="benefitingUsers" class="formbricks-textarea" required autofocus></textarea>
|
||||
<div class="formbricks-next-button-wrapper">
|
||||
<button type="submit" class="formbricks-next-button">Submit</button>
|
||||
</div>
|
||||
|
||||
@@ -132,7 +132,7 @@ async function submitElement(name?: string, value?: string) {
|
||||
const response = await createSubmission(submission);
|
||||
submissionId = response.id;
|
||||
} else {
|
||||
const finished = !!("selfSegmentation" in submission);
|
||||
const finished = !!("benefitingUsers" in submission);
|
||||
await updateSubmission(submissionId, submission, finished);
|
||||
if (finished) {
|
||||
config.onFinished();
|
||||
|
||||
Reference in New Issue
Block a user