mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-08 11:19:30 -05:00
outsource form-engine in own package
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { Button } from "@formbricks/ui";
|
||||
|
||||
interface EngineButtonsProps {
|
||||
allowSkip: boolean;
|
||||
skipAction: () => void;
|
||||
autoSubmit: boolean;
|
||||
}
|
||||
|
||||
export function EngineButtons({ allowSkip, skipAction, autoSubmit }: EngineButtonsProps) {
|
||||
return (
|
||||
<div className="mx-auto mt-8 flex w-full max-w-xl justify-end">
|
||||
{allowSkip && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
className="transition-all ease-in-out hover:scale-105"
|
||||
onClick={() => skipAction()}>
|
||||
Skip
|
||||
</Button>
|
||||
)}
|
||||
{!autoSubmit && (
|
||||
<Button variant="primary" type="submit" className="ml-2 transition-all ease-in-out hover:scale-105">
|
||||
Next
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import clsx from "clsx";
|
||||
import { useMemo } from "react";
|
||||
import { EngineButtons } from "./EngineButtons";
|
||||
import { SurveyElement } from "./engineTypes";
|
||||
|
||||
interface FeatureSelectionProps {
|
||||
@@ -7,21 +10,38 @@ interface FeatureSelectionProps {
|
||||
control: any;
|
||||
onSubmit: () => void;
|
||||
disabled: boolean;
|
||||
allowSkip: boolean;
|
||||
skipAction: () => void;
|
||||
autoSubmit: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export default function FeatureSelection({ element, field, register }: FeatureSelectionProps) {
|
||||
export default function FeatureSelection({
|
||||
element,
|
||||
field,
|
||||
register,
|
||||
allowSkip,
|
||||
skipAction,
|
||||
autoSubmit,
|
||||
loading,
|
||||
}: FeatureSelectionProps) {
|
||||
const shuffledOptions = useMemo(
|
||||
() => (element.options ? getShuffledArray(element.options) : []),
|
||||
[element.options]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-center">
|
||||
<label
|
||||
htmlFor={element.id}
|
||||
className="pb-6 text-center text-lg font-bold text-slate-600 dark:text-slate-300 sm:text-xl md:text-2xl">
|
||||
{element.label}
|
||||
</label>
|
||||
<fieldset className="space-y-5">
|
||||
<legend className="sr-only">{element.label}</legend>
|
||||
<div className=" mx-auto grid max-w-5xl grid-cols-1 gap-6 px-2 sm:grid-cols-2">
|
||||
{element.options &&
|
||||
element.options.map((option) => (
|
||||
<div className={clsx(loading && "formbricks-pulse-animation")}>
|
||||
<div className="flex flex-col justify-center">
|
||||
<label
|
||||
htmlFor={element.id}
|
||||
className="pb-6 text-center text-lg font-bold text-slate-600 dark:text-slate-300 sm:text-xl md:text-2xl">
|
||||
{element.label}
|
||||
</label>
|
||||
<fieldset className="space-y-5">
|
||||
<legend className="sr-only">{element.label}</legend>
|
||||
<div className=" mx-auto grid max-w-5xl grid-cols-1 gap-6 px-2 sm:grid-cols-2">
|
||||
{shuffledOptions.map((option) => (
|
||||
<label htmlFor={`${element.id}-${option.value}`} key={`${element.id}-${option.value}`}>
|
||||
<div className="drop-shadow-card duration-120 relative cursor-default rounded-lg border border-gray-200 bg-white p-6 transition-all ease-in-out hover:scale-105 dark:border-slate-700 dark:bg-slate-700">
|
||||
<div className="absolute right-10">
|
||||
@@ -48,8 +68,19 @@ export default function FeatureSelection({ element, field, register }: FeatureSe
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<EngineButtons allowSkip={allowSkip} skipAction={skipAction} autoSubmit={autoSubmit} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getShuffledArray(array: any[]) {
|
||||
const shuffledArray = [...array];
|
||||
for (let i = shuffledArray.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
|
||||
}
|
||||
return shuffledArray;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CheckCircleIcon } from "@heroicons/react/20/solid";
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import { EngineButtons } from "./EngineButtons";
|
||||
import { SurveyElement } from "./engineTypes";
|
||||
|
||||
interface IconRadioProps {
|
||||
@@ -11,9 +12,22 @@ interface IconRadioProps {
|
||||
control: any;
|
||||
onSubmit: () => void;
|
||||
disabled: boolean;
|
||||
allowSkip: boolean;
|
||||
skipAction: () => void;
|
||||
autoSubmit: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export default function IconRadio({ element, control, onSubmit, disabled }: IconRadioProps) {
|
||||
export default function IconRadio({
|
||||
element,
|
||||
control,
|
||||
onSubmit,
|
||||
disabled,
|
||||
allowSkip,
|
||||
autoSubmit,
|
||||
skipAction,
|
||||
loading,
|
||||
}: IconRadioProps) {
|
||||
const value = useWatch({
|
||||
control,
|
||||
name: element.name!!,
|
||||
@@ -26,78 +40,81 @@ export default function IconRadio({ element, control, onSubmit, disabled }: Icon
|
||||
}, [value, onSubmit, disabled]);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={element.name!}
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ field }: { field: any }) => (
|
||||
<RadioGroup className="flex flex-col justify-center" {...field}>
|
||||
<RadioGroup.Label className="pb-6 text-center text-lg font-bold text-slate-600 dark:text-slate-300 sm:text-xl md:text-2xl">
|
||||
{element.label}
|
||||
</RadioGroup.Label>
|
||||
<div className="mx-auto -mt-3 mb-3 text-center text-sm text-slate-500 dark:text-slate-300 md:max-w-lg">
|
||||
{element.help}
|
||||
</div>
|
||||
<div className={clsx(loading && "formbricks-pulse-animation")}>
|
||||
<Controller
|
||||
name={element.name!}
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ field }: { field: any }) => (
|
||||
<RadioGroup className="flex flex-col justify-center" {...field}>
|
||||
<RadioGroup.Label className="pb-6 text-center text-lg font-bold text-slate-600 dark:text-slate-300 sm:text-xl md:text-2xl">
|
||||
{element.label}
|
||||
</RadioGroup.Label>
|
||||
<div className="mx-auto -mt-3 mb-3 text-center text-sm text-slate-500 dark:text-slate-300 md:max-w-lg">
|
||||
{element.help}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
element.options && element.options.length >= 4
|
||||
? "lg:grid-cols-4"
|
||||
: element.options?.length === 3
|
||||
? "lg:grid-cols-3"
|
||||
: element.options?.length === 2
|
||||
? "lg:grid-cols-2"
|
||||
: "lg:grid-cols-1",
|
||||
"mt-4 grid w-full gap-y-6 sm:gap-x-4"
|
||||
)}>
|
||||
{element.options &&
|
||||
element.options.map((option) => (
|
||||
<RadioGroup.Option
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className={({ checked, active }) =>
|
||||
clsx(
|
||||
checked ? "border-transparent" : "border-slate-200 dark:border-slate-700",
|
||||
active ? "border-brand ring-brand ring-2" : "",
|
||||
"relative flex cursor-pointer rounded-lg border bg-white py-8 shadow-sm transition-all ease-in-out hover:scale-105 focus:outline-none dark:bg-slate-700"
|
||||
)
|
||||
}>
|
||||
{({ checked, active }) => (
|
||||
<>
|
||||
<div className="flex flex-1 flex-col justify-center text-slate-500 hover:text-slate-700 dark:text-slate-400 hover:dark:text-slate-200">
|
||||
{option.frontend?.icon && (
|
||||
<option.frontend.icon
|
||||
className="text-brand mx-auto mb-3 h-8 w-8"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<RadioGroup.Label as="span" className="mx-auto text-sm font-medium ">
|
||||
{option.label}
|
||||
</RadioGroup.Label>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
element.options && element.options.length >= 4
|
||||
? "lg:grid-cols-4"
|
||||
: element.options?.length === 3
|
||||
? "lg:grid-cols-3"
|
||||
: element.options?.length === 2
|
||||
? "lg:grid-cols-2"
|
||||
: "lg:grid-cols-1",
|
||||
"mt-4 grid w-full gap-y-6 sm:gap-x-4"
|
||||
)}>
|
||||
{element.options &&
|
||||
element.options.map((option) => (
|
||||
<RadioGroup.Option
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className={({ checked, active }) =>
|
||||
clsx(
|
||||
checked ? "border-transparent" : "border-slate-200 dark:border-slate-700",
|
||||
active ? "border-brand ring-brand ring-2" : "",
|
||||
"relative flex cursor-pointer rounded-lg border bg-white py-8 shadow-sm transition-all ease-in-out hover:scale-105 focus:outline-none dark:bg-slate-700"
|
||||
)
|
||||
}>
|
||||
{({ checked, active }) => (
|
||||
<>
|
||||
<div className="flex flex-1 flex-col justify-center text-slate-500 hover:text-slate-700 dark:text-slate-400 hover:dark:text-slate-200">
|
||||
{option.frontend?.icon && (
|
||||
<option.frontend.icon
|
||||
className="text-brand mx-auto mb-3 h-8 w-8"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<RadioGroup.Label as="span" className="mx-auto text-sm font-medium ">
|
||||
{option.label}
|
||||
</RadioGroup.Label>
|
||||
</div>
|
||||
|
||||
<CheckCircleIcon
|
||||
className={clsx(
|
||||
!checked ? "invisible" : "",
|
||||
"text-brand absolute -right-2 -top-2 z-10 h-5 w-5 rounded-full bg-white"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
className={clsx(
|
||||
active ? "border" : "border-2",
|
||||
checked ? "border-brand" : "border-transparent",
|
||||
"pointer-events-none absolute -inset-px rounded-lg"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
<CheckCircleIcon
|
||||
className={clsx(
|
||||
!checked ? "invisible" : "",
|
||||
"text-brand absolute -right-2 -top-2 z-10 h-5 w-5 rounded-full bg-white"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
className={clsx(
|
||||
active ? "border" : "border-2",
|
||||
checked ? "border-brand" : "border-transparent",
|
||||
"pointer-events-none absolute -inset-px rounded-lg"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
<EngineButtons allowSkip={allowSkip} skipAction={skipAction} autoSubmit={autoSubmit} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import clsx from "clsx";
|
||||
import { EngineButtons } from "./EngineButtons";
|
||||
import { SurveyElement } from "./engineTypes";
|
||||
|
||||
interface TextareaProps {
|
||||
@@ -5,25 +7,42 @@ interface TextareaProps {
|
||||
field: any;
|
||||
register: any;
|
||||
disabled: boolean;
|
||||
allowSkip: boolean;
|
||||
skipAction: () => void;
|
||||
onSubmit: () => void;
|
||||
autoSubmit: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export default function Input({ element, field, register, disabled, onSubmit }: TextareaProps) {
|
||||
export default function Input({
|
||||
element,
|
||||
field,
|
||||
register,
|
||||
disabled,
|
||||
onSubmit,
|
||||
skipAction,
|
||||
allowSkip,
|
||||
autoSubmit,
|
||||
loading,
|
||||
}: TextareaProps) {
|
||||
return (
|
||||
<div className="flex flex-col justify-center">
|
||||
<label
|
||||
htmlFor={element.id}
|
||||
className="pb-6 text-center text-lg font-bold text-slate-600 dark:text-slate-300 sm:text-xl md:text-2xl">
|
||||
{element.label}
|
||||
</label>
|
||||
<input
|
||||
type={element.frontend?.type || "text"}
|
||||
onBlur=""
|
||||
className="focus:border-brand focus:ring-brand mx-auto mt-4 block w-full max-w-xl rounded-md border-gray-300 text-slate-700 shadow-sm dark:bg-slate-700 dark:text-slate-200 dark:placeholder:text-slate-400 dark:focus:bg-slate-700 dark:active:bg-slate-700 sm:text-sm"
|
||||
placeholder={element.frontend?.placeholder || ""}
|
||||
required={!!element.frontend?.required}
|
||||
{...register(element.name!)}
|
||||
/>
|
||||
<div className={clsx(loading && "formbricks-pulse-animation")}>
|
||||
<div className="flex flex-col justify-center">
|
||||
<label
|
||||
htmlFor={element.id}
|
||||
className="pb-6 text-center text-lg font-bold text-slate-600 dark:text-slate-300 sm:text-xl md:text-2xl">
|
||||
{element.label}
|
||||
</label>
|
||||
<input
|
||||
type={element.frontend?.type || "text"}
|
||||
onBlur=""
|
||||
className="focus:border-brand focus:ring-brand mx-auto mt-4 block w-full max-w-xl rounded-md border-gray-300 text-slate-700 shadow-sm dark:bg-slate-700 dark:text-slate-200 dark:placeholder:text-slate-400 dark:focus:bg-slate-700 dark:active:bg-slate-700 sm:text-sm"
|
||||
placeholder={element.frontend?.placeholder || ""}
|
||||
required={!!element.frontend?.required}
|
||||
{...register(element.name!)}
|
||||
/>
|
||||
</div>
|
||||
<EngineButtons allowSkip={allowSkip} skipAction={skipAction} autoSubmit={autoSubmit} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CheckCircleIcon } from "@heroicons/react/20/solid";
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import { EngineButtons } from "./EngineButtons";
|
||||
import { SurveyElement } from "./engineTypes";
|
||||
|
||||
interface IconRadioProps {
|
||||
@@ -11,9 +12,22 @@ interface IconRadioProps {
|
||||
control: any;
|
||||
onSubmit: () => void;
|
||||
disabled: boolean;
|
||||
allowSkip: boolean;
|
||||
skipAction: () => void;
|
||||
autoSubmit: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export default function Scale({ element, control, onSubmit, disabled }: IconRadioProps) {
|
||||
export default function Scale({
|
||||
element,
|
||||
control,
|
||||
onSubmit,
|
||||
disabled,
|
||||
allowSkip,
|
||||
skipAction,
|
||||
autoSubmit,
|
||||
loading,
|
||||
}: IconRadioProps) {
|
||||
const value = useWatch({
|
||||
control,
|
||||
name: element.name!!,
|
||||
@@ -25,92 +39,95 @@ export default function Scale({ element, control, onSubmit, disabled }: IconRadi
|
||||
}
|
||||
}, [value, onSubmit, disabled]);
|
||||
return (
|
||||
<Controller
|
||||
name={element.name!}
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ field }: { field: any }) => (
|
||||
<RadioGroup className="flex flex-col justify-center" {...field}>
|
||||
<RadioGroup.Label className="pb-6 text-center text-lg font-bold text-slate-600 dark:text-slate-300 sm:text-xl md:text-2xl">
|
||||
{element.label}
|
||||
</RadioGroup.Label>
|
||||
<div
|
||||
className={clsx(
|
||||
element.frontend.max &&
|
||||
element.frontend.min &&
|
||||
element.frontend.max - element.frontend.min + 1 >= 11
|
||||
? "grid-cols-11"
|
||||
: element.frontend.max - element.frontend.min + 1 === 10
|
||||
? "grid-cols-10"
|
||||
: element.frontend.max - element.frontend.min + 1 === 9
|
||||
? "grid-cols-9"
|
||||
: element.frontend.max - element.frontend.min + 1 === 8
|
||||
? "grid-cols-8"
|
||||
: element.frontend.max - element.frontend.min + 1 === 7
|
||||
? "grid-cols-7"
|
||||
: element.frontend.max - element.frontend.min + 1 === 6
|
||||
? "grid-cols-6"
|
||||
: element.frontend.max - element.frontend.min + 1 === 5
|
||||
? "grid-cols-5"
|
||||
: element.frontend.max - element.frontend.min + 1 === 4
|
||||
? "grid-cols-4"
|
||||
: element.frontend.max - element.frontend.min + 1 === 3
|
||||
? "grid-cols-3"
|
||||
: element.frontend.max - element.frontend.min + 1 === 2
|
||||
? "grid-cols-2"
|
||||
: "grid-cols-1",
|
||||
"mt-4 grid w-full gap-x-1 sm:gap-x-2"
|
||||
)}>
|
||||
{Array.from(
|
||||
{ length: element.frontend.max - element.frontend.min + 1 },
|
||||
(_, i) => i + element.frontend.min
|
||||
).map((num) => (
|
||||
<RadioGroup.Option
|
||||
key={num}
|
||||
value={num}
|
||||
className={({ checked, active }) =>
|
||||
clsx(
|
||||
checked ? "border-transparent" : "border-gray-200 dark:border-slate-700",
|
||||
active ? "border-brand ring-brand ring-2" : "",
|
||||
"xs:rounded-lg relative flex cursor-pointer rounded-md border bg-white py-3 shadow-sm transition-all ease-in-out hover:scale-105 focus:outline-none dark:bg-slate-700 sm:p-4"
|
||||
)
|
||||
}>
|
||||
{({ checked, active }) => (
|
||||
<>
|
||||
<div className="flex flex-1 flex-col justify-center">
|
||||
<RadioGroup.Label
|
||||
as="span"
|
||||
className="mx-auto text-sm font-medium text-gray-900 dark:text-gray-200">
|
||||
{num}
|
||||
</RadioGroup.Label>
|
||||
</div>
|
||||
<div className={clsx(loading && "formbricks-pulse-animation")}>
|
||||
<Controller
|
||||
name={element.name!}
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ field }: { field: any }) => (
|
||||
<RadioGroup className="flex flex-col justify-center" {...field}>
|
||||
<RadioGroup.Label className="pb-6 text-center text-lg font-bold text-slate-600 dark:text-slate-300 sm:text-xl md:text-2xl">
|
||||
{element.label}
|
||||
</RadioGroup.Label>
|
||||
<div
|
||||
className={clsx(
|
||||
element.frontend.max &&
|
||||
element.frontend.min &&
|
||||
element.frontend.max - element.frontend.min + 1 >= 11
|
||||
? "grid-cols-11"
|
||||
: element.frontend.max - element.frontend.min + 1 === 10
|
||||
? "grid-cols-10"
|
||||
: element.frontend.max - element.frontend.min + 1 === 9
|
||||
? "grid-cols-9"
|
||||
: element.frontend.max - element.frontend.min + 1 === 8
|
||||
? "grid-cols-8"
|
||||
: element.frontend.max - element.frontend.min + 1 === 7
|
||||
? "grid-cols-7"
|
||||
: element.frontend.max - element.frontend.min + 1 === 6
|
||||
? "grid-cols-6"
|
||||
: element.frontend.max - element.frontend.min + 1 === 5
|
||||
? "grid-cols-5"
|
||||
: element.frontend.max - element.frontend.min + 1 === 4
|
||||
? "grid-cols-4"
|
||||
: element.frontend.max - element.frontend.min + 1 === 3
|
||||
? "grid-cols-3"
|
||||
: element.frontend.max - element.frontend.min + 1 === 2
|
||||
? "grid-cols-2"
|
||||
: "grid-cols-1",
|
||||
"mt-4 grid w-full gap-x-1 sm:gap-x-2"
|
||||
)}>
|
||||
{Array.from(
|
||||
{ length: element.frontend.max - element.frontend.min + 1 },
|
||||
(_, i) => i + element.frontend.min
|
||||
).map((num) => (
|
||||
<RadioGroup.Option
|
||||
key={num}
|
||||
value={num}
|
||||
className={({ checked, active }) =>
|
||||
clsx(
|
||||
checked ? "border-transparent" : "border-gray-200 dark:border-slate-700",
|
||||
active ? "border-brand ring-brand ring-2" : "",
|
||||
"xs:rounded-lg relative flex cursor-pointer rounded-md border bg-white py-3 shadow-sm transition-all ease-in-out hover:scale-105 focus:outline-none dark:bg-slate-700 sm:p-4"
|
||||
)
|
||||
}>
|
||||
{({ checked, active }) => (
|
||||
<>
|
||||
<div className="flex flex-1 flex-col justify-center">
|
||||
<RadioGroup.Label
|
||||
as="span"
|
||||
className="mx-auto text-sm font-medium text-gray-900 dark:text-gray-200">
|
||||
{num}
|
||||
</RadioGroup.Label>
|
||||
</div>
|
||||
|
||||
<CheckCircleIcon
|
||||
className={clsx(
|
||||
!checked ? "invisible" : "",
|
||||
"text-brand absolute -right-2 -top-2 z-10 h-5 w-5 rounded-full bg-white"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
className={clsx(
|
||||
active ? "border" : "border-2",
|
||||
checked ? "border-brand" : "border-transparent",
|
||||
"pointer-events-none absolute -inset-px rounded-lg"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
</div>
|
||||
<div className="xs:text-sm mt-2 flex justify-between text-xs text-gray-700 dark:text-slate-400">
|
||||
<p>{element.frontend.minLabel}</p>
|
||||
<p>{element.frontend.maxLabel}</p>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
<CheckCircleIcon
|
||||
className={clsx(
|
||||
!checked ? "invisible" : "",
|
||||
"text-brand absolute -right-2 -top-2 z-10 h-5 w-5 rounded-full bg-white"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
className={clsx(
|
||||
active ? "border" : "border-2",
|
||||
checked ? "border-brand" : "border-transparent",
|
||||
"pointer-events-none absolute -inset-px rounded-lg"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</RadioGroup.Option>
|
||||
))}
|
||||
</div>
|
||||
<div className="xs:text-sm mt-2 flex justify-between text-xs text-gray-700 dark:text-slate-400">
|
||||
<p>{element.frontend.minLabel}</p>
|
||||
<p>{element.frontend.maxLabel}</p>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
)}
|
||||
/>
|
||||
<EngineButtons allowSkip={allowSkip} skipAction={skipAction} autoSubmit={autoSubmit} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,44 @@
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useState } from "react";
|
||||
import { EngineButtons } from "./EngineButtons";
|
||||
import { SurveyElement } from "./engineTypes";
|
||||
|
||||
interface TextareaProps {
|
||||
element: SurveyElement;
|
||||
register: any;
|
||||
onSubmit: () => void;
|
||||
allowSkip: boolean;
|
||||
skipAction: () => void;
|
||||
autoSubmit: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export default function Textarea({ element, register, onSubmit }: TextareaProps) {
|
||||
export default function Textarea({
|
||||
element,
|
||||
register,
|
||||
onSubmit,
|
||||
allowSkip,
|
||||
skipAction,
|
||||
autoSubmit,
|
||||
loading,
|
||||
}: TextareaProps) {
|
||||
return (
|
||||
<div className="flex flex-col justify-center">
|
||||
<label
|
||||
htmlFor={element.id}
|
||||
className="pb-6 text-center text-lg font-bold text-slate-600 dark:text-slate-300 sm:text-xl md:text-2xl">
|
||||
{element.label}
|
||||
</label>
|
||||
<textarea
|
||||
rows={element.frontend?.rows || 4}
|
||||
className="focus:border-brand focus:ring-brand mx-auto mt-4 block w-full max-w-xl rounded-md border-gray-300 text-slate-700 shadow-sm dark:bg-slate-700 dark:text-slate-200 dark:placeholder:text-slate-400 sm:text-sm"
|
||||
placeholder={element.frontend?.placeholder || ""}
|
||||
required={!!element.frontend?.required}
|
||||
{...register(element.name!)}
|
||||
/>
|
||||
<div className={clsx(loading && "formbricks-pulse-animation")}>
|
||||
<div className="flex flex-col justify-center">
|
||||
<label
|
||||
htmlFor={element.id}
|
||||
className="pb-6 text-center text-lg font-bold text-slate-600 dark:text-slate-300 sm:text-xl md:text-2xl">
|
||||
{element.label}
|
||||
</label>
|
||||
<textarea
|
||||
rows={element.frontend?.rows || 4}
|
||||
className="focus:border-brand focus:ring-brand mx-auto mt-4 block w-full max-w-xl rounded-md border-gray-300 text-slate-700 shadow-sm dark:bg-slate-700 dark:text-slate-200 dark:placeholder:text-slate-400 sm:text-sm"
|
||||
placeholder={element.frontend?.placeholder || ""}
|
||||
required={!!element.frontend?.required}
|
||||
{...register(element.name!)}
|
||||
/>
|
||||
</div>
|
||||
<EngineButtons allowSkip={allowSkip} skipAction={skipAction} autoSubmit={autoSubmit} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@docsearch/react": "^3.3.2",
|
||||
"@formbricks/engine-react": "workspace:*",
|
||||
"@formbricks/pmf": "workspace:*",
|
||||
"@formbricks/react": "workspace:*",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
|
||||
@@ -2,7 +2,7 @@ import FeatureSelection from "@/components/engine/FeatureSelection";
|
||||
import IconRadio from "@/components/engine/IconRadio";
|
||||
import Input from "@/components/engine/Input";
|
||||
import Scale from "@/components/engine/Scale";
|
||||
import { Survey } from "@/components/engine/Survey";
|
||||
import { FormbricksEngine } from "@formbricks/engine-react";
|
||||
import Textarea from "@/components/engine/Textarea";
|
||||
import ThankYouHeading from "@/components/engine/ThankYouHeading";
|
||||
import ThankYouPlans from "@/components/engine/ThankYouPlans";
|
||||
@@ -29,471 +29,480 @@ import {
|
||||
CrossMarkIcon,
|
||||
UserCoupleIcon,
|
||||
} from "@formbricks/ui";
|
||||
import { usePlausible } from "next-plausible";
|
||||
|
||||
const WaitlistPage = () => (
|
||||
<LayoutWaitlist title="Waitlist" description="Join our Waitlist today">
|
||||
<div className="mx-auto w-full max-w-5xl px-6 md:w-3/4">
|
||||
<div className="px-4 pt-20 pb-4">
|
||||
<h1 className="text-3xl font-bold tracking-tight text-slate-800 dark:text-slate-200 sm:text-4xl md:text-5xl">
|
||||
<span className="xl:inline">Get</span>{" "}
|
||||
<span className="from-brand-light to-brand-dark bg-gradient-to-b bg-clip-text text-transparent xl:inline">
|
||||
early
|
||||
</span>{" "}
|
||||
<span className="inline ">access</span>
|
||||
</h1>
|
||||
<p className="mt-3 text-sm text-slate-400 dark:text-slate-300 md:text-base">
|
||||
We are onboarding users continuously. Tell us more about you!
|
||||
</p>
|
||||
</div>
|
||||
const WaitlistPage = () => {
|
||||
const plausible = usePlausible();
|
||||
return (
|
||||
<LayoutWaitlist title="Waitlist" description="Join our Waitlist today">
|
||||
<div className="mx-auto w-full max-w-5xl px-6 md:w-3/4">
|
||||
<div className="px-4 pt-20 pb-4">
|
||||
<h1 className="text-3xl font-bold tracking-tight text-slate-800 dark:text-slate-200 sm:text-4xl md:text-5xl">
|
||||
<span className="xl:inline">Get</span>{" "}
|
||||
<span className="from-brand-light to-brand-dark bg-gradient-to-b bg-clip-text text-transparent xl:inline">
|
||||
early
|
||||
</span>{" "}
|
||||
<span className="inline ">access</span>
|
||||
</h1>
|
||||
<p className="mt-3 text-sm text-slate-400 dark:text-slate-300 md:text-base">
|
||||
We are onboarding users continuously. Tell us more about you!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto my-6 w-full max-w-5xl rounded-xl bg-slate-100 px-8 py-10 dark:bg-slate-800 md:my-12 md:px-16 md:py-20">
|
||||
<Survey
|
||||
formbricksUrl={
|
||||
process.env.NODE_ENV === "production" ? "https://app.formbricks.com" : "http://localhost:3000"
|
||||
}
|
||||
formId={
|
||||
process.env.NODE_ENV === "production" ? "cld37mt2i0000ld08p9q572bc" : "cldonm4ra000019axa4oc440z"
|
||||
}
|
||||
survey={{
|
||||
config: {
|
||||
progressBar: false,
|
||||
},
|
||||
pages: [
|
||||
{
|
||||
id: "rolePage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "role",
|
||||
type: "radio",
|
||||
label: "How would you describe your role?",
|
||||
name: "role",
|
||||
options: [
|
||||
{ label: "Founder", value: "founder", frontend: { icon: FounderIcon } },
|
||||
{
|
||||
label: "Product Manager",
|
||||
value: "productManager",
|
||||
frontend: { icon: LaptopWorkerIcon },
|
||||
},
|
||||
{ label: "Engineer", value: "engineer", frontend: { icon: EngineerIcon } },
|
||||
],
|
||||
component: IconRadio,
|
||||
},
|
||||
],
|
||||
<div className="mx-auto my-6 w-full max-w-5xl rounded-xl bg-slate-100 px-8 py-10 dark:bg-slate-800 md:my-12 md:px-16 md:py-20">
|
||||
<FormbricksEngine
|
||||
formbricksUrl={
|
||||
process.env.NODE_ENV === "production" ? "https://app.formbricks.com" : "http://localhost:3000"
|
||||
}
|
||||
formId={
|
||||
process.env.NODE_ENV === "production"
|
||||
? "cld37mt2i0000ld08p9q572bc"
|
||||
: "cldonm4ra000019axa4oc440z"
|
||||
}
|
||||
onPageSubmit={({ page }) => plausible(`waitlistSubmitPage-${page.id}`)}
|
||||
onFinished={() => plausible("waitlistFinished")}
|
||||
schema={{
|
||||
config: {
|
||||
progressBar: false,
|
||||
},
|
||||
{
|
||||
id: "targetGroupPage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "targetGroup",
|
||||
type: "radio",
|
||||
label: "Who are you serving?",
|
||||
name: "targetGroup",
|
||||
options: [
|
||||
{ label: "Companies", value: "companies", frontend: { icon: SkyscraperIcon } },
|
||||
{ label: "Consumers", value: "consumers", frontend: { icon: UserGroupIcon } },
|
||||
],
|
||||
component: IconRadio,
|
||||
pages: [
|
||||
{
|
||||
id: "rolePage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "emailPage",
|
||||
config: {
|
||||
addFieldsToCustomer: ["email"],
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "email",
|
||||
type: "text",
|
||||
label: "What's your email?",
|
||||
name: "email",
|
||||
frontend: {
|
||||
required: true,
|
||||
type: "email",
|
||||
placeholder: "email@example.com",
|
||||
elements: [
|
||||
{
|
||||
id: "role",
|
||||
type: "radio",
|
||||
label: "How would you describe your role?",
|
||||
name: "role",
|
||||
options: [
|
||||
{ label: "Founder", value: "founder", frontend: { icon: FounderIcon } },
|
||||
{
|
||||
label: "Product Manager",
|
||||
value: "productManager",
|
||||
frontend: { icon: LaptopWorkerIcon },
|
||||
},
|
||||
{ label: "Engineer", value: "engineer", frontend: { icon: EngineerIcon } },
|
||||
],
|
||||
component: IconRadio,
|
||||
},
|
||||
component: Input,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "featureSelectionPage",
|
||||
elements: [
|
||||
{
|
||||
id: "featureSelection",
|
||||
type: "radio",
|
||||
label: "Select the Best Practices you need:",
|
||||
name: "featureSelection",
|
||||
options: [
|
||||
{
|
||||
label: "Onboarding Segmentation",
|
||||
value: "onboardingSegmentation",
|
||||
frontend: {
|
||||
icon: OnboardingIcon,
|
||||
description:
|
||||
"Get to know your users right from the start. Ask a few questions early, let us enrich the profile.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Superhuman PMF Engine",
|
||||
value: "pmf",
|
||||
frontend: {
|
||||
icon: PMFIcon,
|
||||
description:
|
||||
"Find out how disappointed people would be if they could not use your service any more.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Feature Chaser",
|
||||
value: "featureChaser",
|
||||
frontend: {
|
||||
icon: DogChaserIcon,
|
||||
description: "Show a survey about a new feature shown only to people who used it.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Cancel Subscription Flow",
|
||||
value: "cancelSubscriptionFlow",
|
||||
frontend: {
|
||||
icon: CancelSubscriptionIcon,
|
||||
description:
|
||||
"Request users going through a cancel subscription flow before cancelling.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Interview Prompt",
|
||||
value: "interviewPrompt",
|
||||
frontend: {
|
||||
icon: InterviewPromptIcon,
|
||||
description:
|
||||
"Ask high-interest users to book a time in your calendar to get all the juicy details.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Fake Door Follow-Up",
|
||||
value: "fakeDoorFollowUp",
|
||||
frontend: {
|
||||
icon: DoorIcon,
|
||||
description:
|
||||
"Running a fake door experiment? Catch users right when they are full of expectations.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "FeedbackBox",
|
||||
value: "feedbackBox",
|
||||
frontend: {
|
||||
icon: FeedbackIcon,
|
||||
description: "Give users the chance to share feedback in a single click.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Bug Report Form",
|
||||
value: "bugReportForm",
|
||||
frontend: {
|
||||
icon: BugBlueIcon,
|
||||
description: "Catch all bugs in your SaaS with easy and accessible bug reports.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Rage Click Survey",
|
||||
value: "rageClickSurvey",
|
||||
frontend: {
|
||||
icon: AngryBirdRageIcon,
|
||||
description:
|
||||
"Sometimes things don’t work. Trigger this rage click survey to catch users in rage.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Feature Request Widget",
|
||||
value: "featureRequestWidget",
|
||||
frontend: {
|
||||
icon: FeatureRequestIcon,
|
||||
description:
|
||||
"Allow users to request features and pipe it to GitHub projects or Linear.",
|
||||
},
|
||||
},
|
||||
],
|
||||
component: FeatureSelection,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "wauPage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
],
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "wau",
|
||||
type: "radio",
|
||||
label: "How many weekly active users do you have?",
|
||||
name: "wau",
|
||||
options: [
|
||||
{ label: "Not launched", value: "notLaunched", frontend: { icon: CrossMarkIcon } },
|
||||
{ label: "10-100", value: "10-100", frontend: { icon: UserCoupleIcon } },
|
||||
{ label: "100-1.000", value: "100-1000", frontend: { icon: UserGroupIcon } },
|
||||
{ label: "1.000+", value: "10000+", frontend: { icon: UserGroupIcon } },
|
||||
],
|
||||
component: IconRadio,
|
||||
{
|
||||
id: "targetGroupPage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "goalPage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "goal",
|
||||
type: "radio",
|
||||
label: "Want to become a beta user?",
|
||||
help: "Answer 3 open-ended questions and get 50% off in the first year.",
|
||||
name: "goal",
|
||||
options: [
|
||||
{
|
||||
label: "No, just notify me on launch",
|
||||
value: "justNotify",
|
||||
frontend: { icon: BellIcon },
|
||||
},
|
||||
{
|
||||
label: "Yes, take the survey",
|
||||
value: "becomeBetaUser",
|
||||
frontend: { icon: UserCommentIcon },
|
||||
},
|
||||
],
|
||||
component: IconRadio,
|
||||
},
|
||||
],
|
||||
branchingRules: [
|
||||
{
|
||||
type: "value",
|
||||
name: "goal",
|
||||
value: "justNotify",
|
||||
nextPageId: "thankYouPageNotify",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "namePage",
|
||||
elements: [
|
||||
{
|
||||
id: "name",
|
||||
type: "text",
|
||||
label: "First of all, what’s your name?",
|
||||
name: "name",
|
||||
frontend: { placeholder: "First name" },
|
||||
component: Input,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "urgencyPage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "urgency",
|
||||
type: "radio",
|
||||
label: "How urgently do you need this?",
|
||||
name: "urgency",
|
||||
options: [
|
||||
{ label: "1", value: "1" },
|
||||
{ label: "2", value: "2" },
|
||||
{ label: "3", value: "3" },
|
||||
{ label: "4", value: "4" },
|
||||
{ label: "5", value: "5" },
|
||||
{ label: "6", value: "6" },
|
||||
{ label: "7", value: "7" },
|
||||
{ label: "8", value: "8" },
|
||||
{ label: "9", value: "9" },
|
||||
{ label: "10", value: "10" },
|
||||
],
|
||||
frontend: {
|
||||
min: 1,
|
||||
max: 10,
|
||||
minLabel: "I’m just curious",
|
||||
maxLabel: "As soon as possible",
|
||||
elements: [
|
||||
{
|
||||
id: "targetGroup",
|
||||
type: "radio",
|
||||
label: "Who are you serving?",
|
||||
name: "targetGroup",
|
||||
options: [
|
||||
{ label: "Companies", value: "companies", frontend: { icon: SkyscraperIcon } },
|
||||
{ label: "Consumers", value: "consumers", frontend: { icon: UserGroupIcon } },
|
||||
],
|
||||
component: IconRadio,
|
||||
},
|
||||
component: Scale,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "pmfPage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
],
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "pmf",
|
||||
type: "radio",
|
||||
label: "Have you found Product-Market-Fit?",
|
||||
name: "pmf",
|
||||
options: [
|
||||
{
|
||||
label: "Yes",
|
||||
value: "yes",
|
||||
frontend: { icon: CheckMarkIcon },
|
||||
{
|
||||
id: "emailPage",
|
||||
config: {
|
||||
addFieldsToCustomer: ["email"],
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "email",
|
||||
type: "text",
|
||||
label: "What's your email?",
|
||||
name: "email",
|
||||
frontend: {
|
||||
required: true,
|
||||
type: "email",
|
||||
placeholder: "email@example.com",
|
||||
},
|
||||
{
|
||||
label: "No",
|
||||
value: "no",
|
||||
frontend: { icon: CrossMarkIcon },
|
||||
component: Input,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "featureSelectionPage",
|
||||
elements: [
|
||||
{
|
||||
id: "featureSelection",
|
||||
type: "radio",
|
||||
label: "Select the Best Practices you need:",
|
||||
name: "featureSelection",
|
||||
options: [
|
||||
{
|
||||
label: "Onboarding Segmentation",
|
||||
value: "onboardingSegmentation",
|
||||
frontend: {
|
||||
icon: OnboardingIcon,
|
||||
description:
|
||||
"Get to know your users right from the start. Ask a few questions early, let us enrich the profile.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Superhuman PMF Engine",
|
||||
value: "pmf",
|
||||
frontend: {
|
||||
icon: PMFIcon,
|
||||
description:
|
||||
"Find out how disappointed people would be if they could not use your service any more.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Feature Chaser",
|
||||
value: "featureChaser",
|
||||
frontend: {
|
||||
icon: DogChaserIcon,
|
||||
description:
|
||||
"Show a survey about a new feature shown only to people who used it.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Cancel Subscription Flow",
|
||||
value: "cancelSubscriptionFlow",
|
||||
frontend: {
|
||||
icon: CancelSubscriptionIcon,
|
||||
description:
|
||||
"Request users going through a cancel subscription flow before cancelling.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Interview Prompt",
|
||||
value: "interviewPrompt",
|
||||
frontend: {
|
||||
icon: InterviewPromptIcon,
|
||||
description:
|
||||
"Ask high-interest users to book a time in your calendar to get all the juicy details.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Fake Door Follow-Up",
|
||||
value: "fakeDoorFollowUp",
|
||||
frontend: {
|
||||
icon: DoorIcon,
|
||||
description:
|
||||
"Running a fake door experiment? Catch users right when they are full of expectations.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "FeedbackBox",
|
||||
value: "feedbackBox",
|
||||
frontend: {
|
||||
icon: FeedbackIcon,
|
||||
description: "Give users the chance to share feedback in a single click.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Bug Report Form",
|
||||
value: "bugReportForm",
|
||||
frontend: {
|
||||
icon: BugBlueIcon,
|
||||
description: "Catch all bugs in your SaaS with easy and accessible bug reports.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Rage Click Survey",
|
||||
value: "rageClickSurvey",
|
||||
frontend: {
|
||||
icon: AngryBirdRageIcon,
|
||||
description:
|
||||
"Sometimes things don’t work. Trigger this rage click survey to catch users in rage.",
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Feature Request Widget",
|
||||
value: "featureRequestWidget",
|
||||
frontend: {
|
||||
icon: FeatureRequestIcon,
|
||||
description:
|
||||
"Allow users to request features and pipe it to GitHub projects or Linear.",
|
||||
},
|
||||
},
|
||||
],
|
||||
component: FeatureSelection,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "wauPage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "wau",
|
||||
type: "radio",
|
||||
label: "How many weekly active users do you have?",
|
||||
name: "wau",
|
||||
options: [
|
||||
{ label: "Not launched", value: "notLaunched", frontend: { icon: CrossMarkIcon } },
|
||||
{ label: "10-100", value: "10-100", frontend: { icon: UserCoupleIcon } },
|
||||
{ label: "100-1.000", value: "100-1000", frontend: { icon: UserGroupIcon } },
|
||||
{ label: "1.000+", value: "10000+", frontend: { icon: UserGroupIcon } },
|
||||
],
|
||||
component: IconRadio,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "goalPage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "goal",
|
||||
type: "radio",
|
||||
label: "Want to become a beta user?",
|
||||
help: "Answer 3 open-ended questions and get 50% off in the first year.",
|
||||
name: "goal",
|
||||
options: [
|
||||
{
|
||||
label: "No, just notify me on launch",
|
||||
value: "justNotify",
|
||||
frontend: { icon: BellIcon },
|
||||
},
|
||||
{
|
||||
label: "Yes, take the survey",
|
||||
value: "becomeBetaUser",
|
||||
frontend: { icon: UserCommentIcon },
|
||||
},
|
||||
],
|
||||
component: IconRadio,
|
||||
},
|
||||
],
|
||||
branchingRules: [
|
||||
{
|
||||
type: "value",
|
||||
name: "goal",
|
||||
value: "justNotify",
|
||||
nextPageId: "thankYouPageNotify",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "namePage",
|
||||
elements: [
|
||||
{
|
||||
id: "name",
|
||||
type: "text",
|
||||
label: "First of all, what’s your name?",
|
||||
name: "name",
|
||||
frontend: { placeholder: "First name" },
|
||||
component: Input,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "urgencyPage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "urgency",
|
||||
type: "radio",
|
||||
label: "How urgently do you need this?",
|
||||
name: "urgency",
|
||||
options: [
|
||||
{ label: "1", value: "1" },
|
||||
{ label: "2", value: "2" },
|
||||
{ label: "3", value: "3" },
|
||||
{ label: "4", value: "4" },
|
||||
{ label: "5", value: "5" },
|
||||
{ label: "6", value: "6" },
|
||||
{ label: "7", value: "7" },
|
||||
{ label: "8", value: "8" },
|
||||
{ label: "9", value: "9" },
|
||||
{ label: "10", value: "10" },
|
||||
],
|
||||
frontend: {
|
||||
min: 1,
|
||||
max: 10,
|
||||
minLabel: "I’m just curious",
|
||||
maxLabel: "As soon as possible",
|
||||
},
|
||||
],
|
||||
component: IconRadio,
|
||||
},
|
||||
],
|
||||
branchingRules: [
|
||||
{
|
||||
type: "value",
|
||||
name: "pmf",
|
||||
value: "no",
|
||||
nextPageId: "pmfApproachPage",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "scalingResearchPage",
|
||||
elements: [
|
||||
{
|
||||
id: "scalingResearch",
|
||||
type: "text",
|
||||
label: "What is your approach for scaling user research?",
|
||||
name: "scalingResearch",
|
||||
frontend: { required: true, placeholder: "Last time, I..." },
|
||||
component: Textarea,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "userResearchHardestPartPage",
|
||||
config: {
|
||||
allowSkip: true,
|
||||
component: Scale,
|
||||
},
|
||||
],
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "userResearchHardestPart",
|
||||
type: "text",
|
||||
label: "What is the hardest part about it?",
|
||||
name: "userResearchHardestPart",
|
||||
frontend: { required: false, placeholder: "Please tell us about your challenges." },
|
||||
component: Textarea,
|
||||
{
|
||||
id: "pmfPage",
|
||||
config: {
|
||||
autoSubmit: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "toolsMaintainPmfPage",
|
||||
elements: [
|
||||
{
|
||||
id: "toolsMaintainPmf",
|
||||
type: "text",
|
||||
label: "What tools help you maintain Product-Market Fit?",
|
||||
name: "toolsMaintainPmf",
|
||||
frontend: { required: true, placehodler: "Mixpanel, Segment, Intercom..." },
|
||||
component: Textarea,
|
||||
},
|
||||
],
|
||||
branchingRules: [
|
||||
{
|
||||
type: "value",
|
||||
name: "pmf",
|
||||
value: "yes",
|
||||
nextPageId: "thankYouPageBetaUser",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "pmfApproachPage",
|
||||
elements: [
|
||||
{
|
||||
id: "pmfApproach",
|
||||
type: "text",
|
||||
label: "What is your approach for finding Product-Market Fit?",
|
||||
name: "pmfApproach",
|
||||
frontend: { placeholder: "Last time, I..." },
|
||||
component: Textarea,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "pmfHardestPartPage",
|
||||
config: {
|
||||
allowSkip: true,
|
||||
elements: [
|
||||
{
|
||||
id: "pmf",
|
||||
type: "radio",
|
||||
label: "Have you found Product-Market-Fit?",
|
||||
name: "pmf",
|
||||
options: [
|
||||
{
|
||||
label: "Yes",
|
||||
value: "yes",
|
||||
frontend: { icon: CheckMarkIcon },
|
||||
},
|
||||
{
|
||||
label: "No",
|
||||
value: "no",
|
||||
frontend: { icon: CrossMarkIcon },
|
||||
},
|
||||
],
|
||||
component: IconRadio,
|
||||
},
|
||||
],
|
||||
branchingRules: [
|
||||
{
|
||||
type: "value",
|
||||
name: "pmf",
|
||||
value: "no",
|
||||
nextPageId: "pmfApproachPage",
|
||||
},
|
||||
],
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
id: "pmfHardestPart",
|
||||
type: "text",
|
||||
label: "What is the hardest part about it?",
|
||||
name: "pmfHardestPart",
|
||||
frontend: { placeholder: "Please tell us about your challenges." },
|
||||
component: Textarea,
|
||||
{
|
||||
id: "scalingResearchPage",
|
||||
elements: [
|
||||
{
|
||||
id: "scalingResearch",
|
||||
type: "text",
|
||||
label: "What is your approach for scaling user research?",
|
||||
name: "scalingResearch",
|
||||
frontend: { required: true, placeholder: "Last time, I..." },
|
||||
component: Textarea,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "userResearchHardestPartPage",
|
||||
config: {
|
||||
allowSkip: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "pmfFindingToolsPage",
|
||||
elements: [
|
||||
{
|
||||
id: "pmfFindingTools",
|
||||
type: "text",
|
||||
label: "What tools help you finding Product-Market Fit?",
|
||||
name: "pmfFindingTools",
|
||||
frontend: { placeholder: "Mixpanel, Segment, Intercom..." },
|
||||
component: Textarea,
|
||||
elements: [
|
||||
{
|
||||
id: "userResearchHardestPart",
|
||||
type: "text",
|
||||
label: "What is the hardest part about it?",
|
||||
name: "userResearchHardestPart",
|
||||
frontend: { required: false, placeholder: "Please tell us about your challenges." },
|
||||
component: Textarea,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "toolsMaintainPmfPage",
|
||||
elements: [
|
||||
{
|
||||
id: "toolsMaintainPmf",
|
||||
type: "text",
|
||||
label: "What tools help you maintain Product-Market Fit?",
|
||||
name: "toolsMaintainPmf",
|
||||
frontend: { required: true, placehodler: "Mixpanel, Segment, Intercom..." },
|
||||
component: Textarea,
|
||||
},
|
||||
],
|
||||
branchingRules: [
|
||||
{
|
||||
type: "value",
|
||||
name: "pmf",
|
||||
value: "yes",
|
||||
nextPageId: "thankYouPageBetaUser",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "pmfApproachPage",
|
||||
elements: [
|
||||
{
|
||||
id: "pmfApproach",
|
||||
type: "text",
|
||||
label: "What is your approach for finding Product-Market Fit?",
|
||||
name: "pmfApproach",
|
||||
frontend: { placeholder: "Last time, I..." },
|
||||
component: Textarea,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "pmfHardestPartPage",
|
||||
config: {
|
||||
allowSkip: true,
|
||||
},
|
||||
],
|
||||
branchingRules: [
|
||||
{
|
||||
type: "value",
|
||||
name: "pmf",
|
||||
value: "no",
|
||||
nextPageId: "thankYouPageBetaUser",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "thankYouPageNotify",
|
||||
endScreen: true,
|
||||
elements: [
|
||||
{
|
||||
id: "thankYouNotify",
|
||||
type: "html",
|
||||
component: ThankYouHeading,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "thankYouPageBetaUser",
|
||||
endScreen: true,
|
||||
elements: [
|
||||
{
|
||||
id: "thankYouBetaUser",
|
||||
type: "html",
|
||||
component: ThankYouHeading,
|
||||
},
|
||||
{
|
||||
id: "thankYouBetaUser",
|
||||
type: "html",
|
||||
component: ThankYouPlans,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
elements: [
|
||||
{
|
||||
id: "pmfHardestPart",
|
||||
type: "text",
|
||||
label: "What is the hardest part about it?",
|
||||
name: "pmfHardestPart",
|
||||
frontend: { placeholder: "Please tell us about your challenges." },
|
||||
component: Textarea,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "pmfFindingToolsPage",
|
||||
elements: [
|
||||
{
|
||||
id: "pmfFindingTools",
|
||||
type: "text",
|
||||
label: "What tools help you finding Product-Market Fit?",
|
||||
name: "pmfFindingTools",
|
||||
frontend: { placeholder: "Mixpanel, Segment, Intercom..." },
|
||||
component: Textarea,
|
||||
},
|
||||
],
|
||||
branchingRules: [
|
||||
{
|
||||
type: "value",
|
||||
name: "pmf",
|
||||
value: "no",
|
||||
nextPageId: "thankYouPageBetaUser",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "thankYouPageNotify",
|
||||
endScreen: true,
|
||||
elements: [
|
||||
{
|
||||
id: "thankYouNotify",
|
||||
type: "html",
|
||||
component: ThankYouHeading,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "thankYouPageBetaUser",
|
||||
endScreen: true,
|
||||
elements: [
|
||||
{
|
||||
id: "thankYouBetaUser",
|
||||
type: "html",
|
||||
component: ThankYouHeading,
|
||||
},
|
||||
{
|
||||
id: "thankYouBetaUser",
|
||||
type: "html",
|
||||
component: ThankYouPlans,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LayoutWaitlist>
|
||||
);
|
||||
</LayoutWaitlist>
|
||||
);
|
||||
};
|
||||
|
||||
export default WaitlistPage;
|
||||
|
||||
Reference in New Issue
Block a user