mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-07 14:20:31 -06:00
feat: Randomizer for in-app surveys (#1972)
Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
This commit is contained in:
committed by
GitHub
parent
07f6f1d04b
commit
698da4c3a1
@@ -40,9 +40,11 @@ export default function WhenToSendCard({
|
||||
const [isAddEventModalOpen, setAddEventModalOpen] = useState(false);
|
||||
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
||||
const [actionClasses, setActionClasses] = useState<TActionClass[]>(propActionClasses);
|
||||
const [randomizerToggle, setRandomizerToggle] = useState(localSurvey.displayPercentage ? true : false);
|
||||
const { isViewer } = getAccessFlags(membershipRole);
|
||||
|
||||
const autoClose = localSurvey.autoClose !== null;
|
||||
const delay = localSurvey.delay !== 0;
|
||||
|
||||
const addTriggerEvent = useCallback(() => {
|
||||
const updatedSurvey = { ...localSurvey };
|
||||
@@ -71,7 +73,7 @@ export default function WhenToSendCard({
|
||||
setLocalSurvey(updatedSurvey);
|
||||
};
|
||||
|
||||
const handleCheckMark = () => {
|
||||
const handleAutoCloseToggle = () => {
|
||||
if (autoClose) {
|
||||
const updatedSurvey = { ...localSurvey, autoClose: null };
|
||||
setLocalSurvey(updatedSurvey);
|
||||
@@ -81,6 +83,27 @@ export default function WhenToSendCard({
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelayToggle = () => {
|
||||
if (delay) {
|
||||
const updatedSurvey = { ...localSurvey, delay: 0 };
|
||||
setLocalSurvey(updatedSurvey);
|
||||
} else {
|
||||
const updatedSurvey = { ...localSurvey, delay: 5 };
|
||||
setLocalSurvey(updatedSurvey);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisplayPercentageToggle = () => {
|
||||
if (localSurvey.displayPercentage) {
|
||||
const updatedSurvey = { ...localSurvey, displayPercentage: null };
|
||||
setLocalSurvey(updatedSurvey);
|
||||
} else {
|
||||
const updatedSurvey = { ...localSurvey, displayPercentage: 50 };
|
||||
setLocalSurvey(updatedSurvey);
|
||||
}
|
||||
setRandomizerToggle(!randomizerToggle);
|
||||
};
|
||||
|
||||
const handleInputSeconds = (e: any) => {
|
||||
let value = parseInt(e.target.value);
|
||||
|
||||
@@ -96,6 +119,11 @@ export default function WhenToSendCard({
|
||||
setLocalSurvey(updatedSurvey);
|
||||
};
|
||||
|
||||
const handleRandomizerInput = (e) => {
|
||||
const updatedSurvey = { ...localSurvey, displayPercentage: parseInt(e.target.value) };
|
||||
setLocalSurvey(updatedSurvey);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isAddEventModalOpen) return;
|
||||
if (activeIndex !== null) {
|
||||
@@ -155,8 +183,9 @@ export default function WhenToSendCard({
|
||||
</div>
|
||||
</div>
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
<Collapsible.CollapsibleContent className="">
|
||||
<hr className="py-1 text-slate-600" />
|
||||
<hr className="py-1 text-slate-600" />
|
||||
|
||||
<Collapsible.CollapsibleContent className="p-3">
|
||||
{!isAddEventModalOpen &&
|
||||
localSurvey.triggers?.map((triggerEventClass, idx) => (
|
||||
<div className="mt-2" key={idx}>
|
||||
@@ -209,7 +238,14 @@ export default function WhenToSendCard({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="ml-2 flex items-center space-x-1 px-4 pb-4">
|
||||
<div className="ml-2 flex items-center space-x-1 px-4 pb-4"></div>
|
||||
<AdvancedOptionToggle
|
||||
htmlId="delay"
|
||||
isChecked={delay}
|
||||
onToggle={handleDelayToggle}
|
||||
title="Add delay before showing survey"
|
||||
description="Wait a few seconds after the trigger before showing the survey"
|
||||
childBorder={true}>
|
||||
<label
|
||||
htmlFor="triggerDelay"
|
||||
className="flex w-full cursor-pointer items-center rounded-lg border bg-slate-50 p-4">
|
||||
@@ -228,12 +264,11 @@ export default function WhenToSendCard({
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</AdvancedOptionToggle>
|
||||
<AdvancedOptionToggle
|
||||
htmlId="autoClose"
|
||||
isChecked={autoClose}
|
||||
onToggle={handleCheckMark}
|
||||
onToggle={handleAutoCloseToggle}
|
||||
title="Auto close on inactivity"
|
||||
description="Automatically close the survey if the user does not respond after certain number of seconds"
|
||||
childBorder={true}>
|
||||
@@ -252,6 +287,30 @@ export default function WhenToSendCard({
|
||||
</p>
|
||||
</label>
|
||||
</AdvancedOptionToggle>
|
||||
<AdvancedOptionToggle
|
||||
htmlId="randomizer"
|
||||
isChecked={randomizerToggle}
|
||||
onToggle={handleDisplayPercentageToggle}
|
||||
title="Show survey to % of users"
|
||||
description="Only display the survey to a subset of the users"
|
||||
childBorder={true}>
|
||||
<div className="w-full">
|
||||
<div className="flex flex-col justify-center rounded-lg border bg-slate-50 p-6">
|
||||
<h3 className="mb-4 text-sm font-semibold text-slate-700">
|
||||
Show to {localSurvey.displayPercentage}% of targeted users
|
||||
</h3>
|
||||
<input
|
||||
id="small-range"
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
value={localSurvey.displayPercentage ?? 50}
|
||||
onChange={handleRandomizerInput}
|
||||
className="range-sm mb-6 h-1 w-full cursor-pointer appearance-none rounded-lg bg-slate-200 dark:bg-slate-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AdvancedOptionToggle>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.Root>
|
||||
<AddNoCodeActionModal
|
||||
|
||||
@@ -2522,6 +2522,7 @@ export const minimalSurvey: TSurvey = {
|
||||
enabled: false,
|
||||
},
|
||||
delay: 0, // No delay
|
||||
displayPercentage: null,
|
||||
autoComplete: null,
|
||||
closeOnDate: null,
|
||||
surveyClosedMessage: {
|
||||
|
||||
@@ -111,3 +111,12 @@ input[type="search"]::-ms-clear {
|
||||
input[type="search"]::-ms-reveal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
input[type='range']::-webkit-slider-thumb {
|
||||
background: #0f172a;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50%;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Survey" ADD COLUMN "displayPercentage" INTEGER;
|
||||
@@ -300,6 +300,7 @@ model Survey {
|
||||
verifyEmail Json?
|
||||
pin String?
|
||||
resultShareKey String? @unique
|
||||
displayPercentage Int?
|
||||
|
||||
@@index([environmentId])
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ const config = Config.getInstance();
|
||||
|
||||
const intentsToNotCreateOnApp = ["Exit Intent (Desktop)", "50% Scroll"];
|
||||
|
||||
const shouldDisplayBasedOnPercentage = (displayPercentage: number) => {
|
||||
const randomNum = Math.floor(Math.random() * 100) + 1;
|
||||
return randomNum <= displayPercentage;
|
||||
};
|
||||
|
||||
export const trackAction = async (
|
||||
name: string,
|
||||
properties: TJsActionInput["properties"] = {}
|
||||
@@ -64,6 +69,14 @@ export const trackAction = async (
|
||||
|
||||
export const triggerSurvey = async (actionName: string, activeSurveys: TSurvey[]): Promise<void> => {
|
||||
for (const survey of activeSurveys) {
|
||||
// Check if the survey should be displayed based on displayPercentage
|
||||
if (survey.displayPercentage) {
|
||||
const shouldDisplaySurvey = shouldDisplayBasedOnPercentage(survey.displayPercentage);
|
||||
if (!shouldDisplaySurvey) {
|
||||
logger.debug("Survey display skipped based on displayPercentage.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (const trigger of survey.triggers) {
|
||||
if (trigger === actionName) {
|
||||
logger.debug(`Formbricks: survey ${survey.id} triggered by action "${actionName}"`);
|
||||
|
||||
@@ -41,6 +41,7 @@ export const selectSurvey = {
|
||||
autoClose: true,
|
||||
closeOnDate: true,
|
||||
delay: true,
|
||||
displayPercentage: true,
|
||||
autoComplete: true,
|
||||
verifyEmail: true,
|
||||
redirectUrl: true,
|
||||
|
||||
@@ -135,6 +135,7 @@ export const mockSurveyOutput: SurveyMock = {
|
||||
productOverwrites: null,
|
||||
singleUse: null,
|
||||
styling: null,
|
||||
displayPercentage: null,
|
||||
pin: null,
|
||||
resultShareKey: null,
|
||||
...baseSurveyProperties,
|
||||
@@ -157,6 +158,7 @@ export const updateSurveyInput: TSurvey = {
|
||||
productOverwrites: null,
|
||||
styling: null,
|
||||
singleUse: null,
|
||||
displayPercentage: null,
|
||||
pin: null,
|
||||
resultShareKey: null,
|
||||
...commonMockProperties,
|
||||
|
||||
@@ -435,6 +435,7 @@ export const ZSurvey = z.object({
|
||||
verifyEmail: ZSurveyVerifyEmail.nullable(),
|
||||
pin: z.string().nullable().optional(),
|
||||
resultShareKey: z.string().nullable(),
|
||||
displayPercentage: z.number().min(1).max(100).nullable(),
|
||||
});
|
||||
|
||||
export const ZSurveyInput = z.object({
|
||||
|
||||
Reference in New Issue
Block a user